1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 use std::borrow::Borrow;
6 use std::cell::RefCell;
7 use std::collections::{BTreeSet, HashMap, HashSet};
10 use anyhow::{Context, Result};
12 use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
13 use serde::{Deserialize, Serialize};
15 use crate::backend::TemplateExpression;
16 use crate::interface::*;
17 use crate::BindingsConfig;
19 mod callback_interface;
31 trait CodeType: Debug {
32 /// The language specific label used to reference this type. This will be used in
33 /// method signatures and property declarations.
34 fn type_label(&self, ci: &ComponentInterface) -> String;
36 /// A representation of this type label that can be used as part of another
37 /// identifier. e.g. `read_foo()`, or `FooInternals`.
39 /// This is especially useful when creating specialized objects or methods to deal
40 /// with this type only.
41 fn canonical_name(&self) -> String;
43 fn literal(&self, _literal: &Literal, ci: &ComponentInterface) -> String {
44 unimplemented!("Unimplemented for {}", self.type_label(ci))
47 /// Name of the FfiConverter
49 /// This is the object that contains the lower, write, lift, and read methods for this type.
50 /// Depending on the binding this will either be a singleton or a class with static methods.
52 /// This is the newer way of handling these methods and replaces the lower, write, lift, and
53 /// read CodeType methods. Currently only used by Kotlin, but the plan is to move other
54 /// backends to using this.
55 fn ffi_converter_name(&self) -> String {
56 format!("FfiConverter{}", self.canonical_name())
59 /// A list of imports that are needed if this type is in use.
60 /// Classes are imported exactly once.
61 fn imports(&self) -> Option<Vec<String>> {
65 /// Function to run at startup
66 fn initialization_fn(&self) -> Option<String> {
71 // config options to customize the generated Kotlin.
72 #[derive(Debug, Default, Clone, Serialize, Deserialize)]
74 package_name: Option<String>,
75 cdylib_name: Option<String>,
77 custom_types: HashMap<String, CustomTypeConfig>,
79 external_packages: HashMap<String, String>,
82 #[derive(Debug, Default, Clone, Serialize, Deserialize)]
83 pub struct CustomTypeConfig {
84 imports: Option<Vec<String>>,
85 type_name: Option<String>,
86 into_custom: TemplateExpression,
87 from_custom: TemplateExpression,
91 pub fn package_name(&self) -> String {
92 if let Some(package_name) = &self.package_name {
99 pub fn cdylib_name(&self) -> String {
100 if let Some(cdylib_name) = &self.cdylib_name {
108 impl BindingsConfig for Config {
109 fn update_from_ci(&mut self, ci: &ComponentInterface) {
111 .get_or_insert_with(|| format!("uniffi.{}", ci.namespace()));
113 .get_or_insert_with(|| format!("uniffi_{}", ci.namespace()));
116 fn update_from_cdylib_name(&mut self, cdylib_name: &str) {
118 .get_or_insert_with(|| cdylib_name.to_string());
121 fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>) {
122 for (crate_name, config) in config_map {
123 if !self.external_packages.contains_key(crate_name) {
124 self.external_packages
125 .insert(crate_name.to_string(), config.package_name());
131 // Generate kotlin bindings for the given ComponentInterface, as a string.
132 pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
133 KotlinWrapper::new(config.clone(), ci)
135 .context("failed to render kotlin bindings")
138 /// A struct to record a Kotlin import statement.
139 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
140 pub enum ImportRequirement {
141 /// The name we are importing.
142 Import { name: String },
143 /// Import the name with the specified local name.
144 ImportAs { name: String, as_name: String },
147 impl ImportRequirement {
148 /// Render the Kotlin import statement.
149 fn render(&self) -> String {
151 ImportRequirement::Import { name } => format!("import {name}"),
152 ImportRequirement::ImportAs { name, as_name } => {
153 format!("import {name} as {as_name}")
159 /// Renders Kotlin helper code for all types
161 /// This template is a bit different than others in that it stores internal state from the render
162 /// process. Make sure to only call `render()` once.
164 #[template(syntax = "kt", escape = "none", path = "Types.kt")]
165 pub struct TypeRenderer<'a> {
167 ci: &'a ComponentInterface,
168 // Track included modules for the `include_once()` macro
169 include_once_names: RefCell<HashSet<String>>,
170 // Track imports added with the `add_import()` macro
171 imports: RefCell<BTreeSet<ImportRequirement>>,
174 impl<'a> TypeRenderer<'a> {
175 fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self {
179 include_once_names: RefCell::new(HashSet::new()),
180 imports: RefCell::new(BTreeSet::new()),
184 // Get the package name for an external type
185 fn external_type_package_name(&self, module_path: &str, namespace: &str) -> String {
186 // config overrides are keyed by the crate name, default fallback is the namespace.
187 let crate_name = module_path.split("::").next().unwrap();
188 match self.config.external_packages.get(crate_name) {
189 Some(name) => name.clone(),
190 // unreachable in library mode - all deps are in our config with correct namespace.
191 None => format!("uniffi.{namespace}"),
195 // The following methods are used by the `Types.kt` macros.
197 // Helper for the including a template, but only once.
199 // The first time this is called with a name it will return true, indicating that we should
200 // include the template. Subsequent calls will return false.
201 fn include_once_check(&self, name: &str) -> bool {
202 self.include_once_names
204 .insert(name.to_string())
207 // Helper to add an import statement
209 // Call this inside your template to cause an import statement to be added at the top of the
210 // file. Imports will be sorted and de-deuped.
212 // Returns an empty string so that it can be used inside an askama `{{ }}` block.
213 fn add_import(&self, name: &str) -> &str {
214 self.imports.borrow_mut().insert(ImportRequirement::Import {
215 name: name.to_owned(),
220 // Like add_import, but arranges for `import name as as_name`
221 fn add_import_as(&self, name: &str, as_name: &str) -> &str {
224 .insert(ImportRequirement::ImportAs {
225 name: name.to_owned(),
226 as_name: as_name.to_owned(),
233 #[template(syntax = "kt", escape = "none", path = "wrapper.kt")]
234 pub struct KotlinWrapper<'a> {
236 ci: &'a ComponentInterface,
237 type_helper_code: String,
238 type_imports: BTreeSet<ImportRequirement>,
242 impl<'a> KotlinWrapper<'a> {
243 pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
244 let type_renderer = TypeRenderer::new(&config, ci);
245 let type_helper_code = type_renderer.render().unwrap();
246 let type_imports = type_renderer.imports.into_inner();
252 has_async_fns: ci.has_async_fns(),
256 pub fn initialization_fns(&self) -> Vec<String> {
259 .map(|t| KotlinCodeOracle.find(t))
260 .filter_map(|ct| ct.initialization_fn())
263 .then(|| "uniffiRustFutureContinuationCallback.register".into()),
268 pub fn imports(&self) -> Vec<ImportRequirement> {
269 self.type_imports.iter().cloned().collect()
274 pub struct KotlinCodeOracle;
276 impl KotlinCodeOracle {
277 fn find(&self, type_: &Type) -> Box<dyn CodeType> {
278 type_.clone().as_type().as_codetype()
281 /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
282 fn class_name(&self, ci: &ComponentInterface, nm: &str) -> String {
283 let name = nm.to_string().to_upper_camel_case();
285 ci.is_name_used_as_error(nm)
286 .then(|| self.convert_error_suffix(&name))
290 fn convert_error_suffix(&self, nm: &str) -> String {
291 match nm.strip_suffix("Error") {
292 None => nm.to_string(),
293 Some(stripped) => format!("{stripped}Exception"),
297 /// Get the idiomatic Kotlin rendering of a function name.
298 fn fn_name(&self, nm: &str) -> String {
299 format!("`{}`", nm.to_string().to_lower_camel_case())
302 /// Get the idiomatic Kotlin rendering of a variable name.
303 fn var_name(&self, nm: &str) -> String {
304 format!("`{}`", nm.to_string().to_lower_camel_case())
307 /// Get the idiomatic Kotlin rendering of an individual enum variant.
308 fn enum_variant_name(&self, nm: &str) -> String {
309 nm.to_string().to_shouty_snake_case()
312 fn ffi_type_label_by_value(ffi_type: &FfiType) -> String {
314 FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)),
315 _ => Self::ffi_type_label(ffi_type),
319 fn ffi_type_label(ffi_type: &FfiType) -> String {
321 // Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not
322 // support them yet. Thus, we use the signed variants to represent both signed and unsigned
323 // types from the component API.
324 FfiType::Int8 | FfiType::UInt8 => "Byte".to_string(),
325 FfiType::Int16 | FfiType::UInt16 => "Short".to_string(),
326 FfiType::Int32 | FfiType::UInt32 => "Int".to_string(),
327 FfiType::Int64 | FfiType::UInt64 => "Long".to_string(),
328 FfiType::Float32 => "Float".to_string(),
329 FfiType::Float64 => "Double".to_string(),
330 FfiType::RustArcPtr(_) => "Pointer".to_string(),
331 FfiType::RustBuffer(maybe_suffix) => {
332 format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
334 FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
335 FfiType::ForeignCallback => "ForeignCallback".to_string(),
336 FfiType::ForeignExecutorHandle => "USize".to_string(),
337 FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(),
338 FfiType::RustFutureHandle => "Pointer".to_string(),
339 FfiType::RustFutureContinuationCallback => {
340 "UniFffiRustFutureContinuationCallbackType".to_string()
342 FfiType::RustFutureContinuationData => "USize".to_string(),
348 fn as_codetype(&self) -> Box<dyn CodeType>;
351 impl<T: AsType> AsCodeType for T {
352 fn as_codetype(&self) -> Box<dyn CodeType> {
353 // Map `Type` instances to a `Box<dyn CodeType>` for that type.
355 // There is a companion match in `templates/Types.kt` which performs a similar function for the
358 // - When adding additional types here, make sure to also add a match arm to the `Types.kt` template.
359 // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
360 match self.as_type() {
361 Type::UInt8 => Box::new(primitives::UInt8CodeType),
362 Type::Int8 => Box::new(primitives::Int8CodeType),
363 Type::UInt16 => Box::new(primitives::UInt16CodeType),
364 Type::Int16 => Box::new(primitives::Int16CodeType),
365 Type::UInt32 => Box::new(primitives::UInt32CodeType),
366 Type::Int32 => Box::new(primitives::Int32CodeType),
367 Type::UInt64 => Box::new(primitives::UInt64CodeType),
368 Type::Int64 => Box::new(primitives::Int64CodeType),
369 Type::Float32 => Box::new(primitives::Float32CodeType),
370 Type::Float64 => Box::new(primitives::Float64CodeType),
371 Type::Boolean => Box::new(primitives::BooleanCodeType),
372 Type::String => Box::new(primitives::StringCodeType),
373 Type::Bytes => Box::new(primitives::BytesCodeType),
375 Type::Timestamp => Box::new(miscellany::TimestampCodeType),
376 Type::Duration => Box::new(miscellany::DurationCodeType),
378 Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
379 Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
380 Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
381 Type::CallbackInterface { name, .. } => {
382 Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
384 Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
385 Type::Optional { inner_type } => {
386 Box::new(compounds::OptionalCodeType::new(*inner_type))
388 Type::Sequence { inner_type } => {
389 Box::new(compounds::SequenceCodeType::new(*inner_type))
394 } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)),
395 Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
396 Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
403 pub use crate::backend::filters::*;
405 pub(super) fn type_name(
406 as_ct: &impl AsCodeType,
407 ci: &ComponentInterface,
408 ) -> Result<String, askama::Error> {
409 Ok(as_ct.as_codetype().type_label(ci))
412 pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
413 Ok(as_ct.as_codetype().canonical_name())
416 pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
417 Ok(as_ct.as_codetype().ffi_converter_name())
420 pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
423 as_ct.as_codetype().ffi_converter_name()
427 pub(super) fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
430 as_ct.as_codetype().ffi_converter_name()
434 pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
437 as_ct.as_codetype().ffi_converter_name()
441 pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
442 Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name()))
445 pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
446 Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name()))
449 pub fn render_literal(
452 ci: &ComponentInterface,
453 ) -> Result<String, askama::Error> {
454 Ok(as_ct.as_codetype().literal(literal, ci))
457 pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> {
458 Ok(KotlinCodeOracle::ffi_type_label_by_value(type_))
461 /// Get the idiomatic Kotlin rendering of a function name.
462 pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
463 Ok(KotlinCodeOracle.fn_name(nm))
466 /// Get the idiomatic Kotlin rendering of a variable name.
467 pub fn var_name(nm: &str) -> Result<String, askama::Error> {
468 Ok(KotlinCodeOracle.var_name(nm))
471 /// Get a String representing the name used for an individual enum variant.
472 pub fn variant_name(v: &Variant) -> Result<String, askama::Error> {
473 Ok(KotlinCodeOracle.enum_variant_name(v.name()))
476 pub fn error_variant_name(v: &Variant) -> Result<String, askama::Error> {
477 let name = v.name().to_string().to_upper_camel_case();
478 Ok(KotlinCodeOracle.convert_error_suffix(&name))
482 callable: impl Callable,
483 ci: &ComponentInterface,
484 ) -> Result<String, askama::Error> {
485 let ffi_func = callable.ffi_rust_future_poll(ci);
487 "{{ future, continuation -> _UniFFILib.INSTANCE.{ffi_func}(future, continuation) }}"
491 pub fn async_complete(
492 callable: impl Callable,
493 ci: &ComponentInterface,
494 ) -> Result<String, askama::Error> {
495 let ffi_func = callable.ffi_rust_future_complete(ci);
496 let call = format!("_UniFFILib.INSTANCE.{ffi_func}(future, continuation)");
497 let call = match callable.return_type() {
498 Some(Type::External {
499 kind: ExternalKind::DataClass,
503 // Need to convert the RustBuffer from our package to the RustBuffer of the external package
504 let suffix = KotlinCodeOracle.class_name(ci, &name);
505 format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}")
509 Ok(format!("{{ future, continuation -> {call} }}"))
513 callable: impl Callable,
514 ci: &ComponentInterface,
515 ) -> Result<String, askama::Error> {
516 let ffi_func = callable.ffi_rust_future_free(ci);
518 "{{ future -> _UniFFILib.INSTANCE.{ffi_func}(future) }}"
522 /// Remove the "`" chars we put around function/variable names
524 /// These are used to avoid name clashes with kotlin identifiers, but sometimes you want to
525 /// render the name unquoted. One example is the message property for errors where we want to
526 /// display the name for the user.
527 pub fn unquote(nm: &str) -> Result<String, askama::Error> {
528 Ok(nm.trim_matches('`').to_string())