/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::borrow::Borrow; use std::cell::RefCell; use std::collections::{BTreeSet, HashMap}; use std::fmt::Debug; use askama::Template; use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use crate::{ anyhow, bail, interface::ffi::ExternalFfiMetadata, interface::*, to_askama_error, Context, Result, }; mod callback_interface; mod compounds; mod custom; mod enum_; mod miscellany; mod object; mod primitives; mod record; mod variant; trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in /// method signatures and property declarations. fn type_label(&self, ci: &ComponentInterface) -> String; /// A representation of this type label that can be used as part of another /// identifier. e.g. `read_foo()`, or `FooInternals`. /// /// This is especially useful when creating specialized objects or methods to deal /// with this type only. fn canonical_name(&self) -> String; // default for named types is to assume a ctor exists. fn default(&self, default: &DefaultValue, ci: &ComponentInterface) -> Result { match default { DefaultValue::Default => Ok(format!("{}()", self.type_label(ci))), DefaultValue::Literal(_) => bail!("Literals for named types are not supported"), } } /// Name of the FfiConverter /// /// This is the object that contains the lower, write, lift, and read methods for this type. /// Depending on the binding this will either be a singleton or a class with static methods. /// /// This is the newer way of handling these methods and replaces the lower, write, lift, and /// read CodeType methods. Currently only used by Kotlin, but the plan is to move other /// backends to using this. fn ffi_converter_name(&self) -> String { format!("FfiConverter{}", self.canonical_name()) } /// Function to run at startup fn initialization_fn(&self) -> Option { None } } // config options to customize the generated Kotlin. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Config { pub(super) package_name: Option, pub(super) cdylib_name: Option, generate_immutable_records: Option, #[serde(default)] omit_checksums: bool, #[serde(default)] custom_types: HashMap, #[serde(default)] pub(super) external_packages: HashMap, #[serde(default)] android: bool, #[serde(default)] android_cleaner: Option, #[serde(default)] kotlin_target_version: Option, #[serde(default)] disable_java_cleaner: bool, #[serde(default)] pub(super) rename: toml::Table, } impl Config { pub(crate) fn android_cleaner(&self) -> bool { self.android_cleaner.unwrap_or(self.android) } pub(crate) fn use_enum_entries(&self) -> bool { self.get_kotlin_version() >= KotlinVersion::new(1, 9, 0) } /// Returns a `Version` with the contents of `kotlin_target_version`. /// If `kotlin_target_version` is not defined, version `0.0.0` will be used as a fallback. /// If it's not valid, this function will panic. fn get_kotlin_version(&self) -> KotlinVersion { self.kotlin_target_version .clone() .map(|v| { KotlinVersion::parse(&v).unwrap_or_else(|_| { panic!("Provided Kotlin target version is not valid: {}", v) }) }) .unwrap_or(KotlinVersion::new(0, 0, 0)) } // Get the package name for an external type fn external_package_name(&self, module_path: &str, namespace: Option<&str>) -> String { // config overrides are keyed by the crate name, default fallback is the namespace. let crate_name = module_path.split("::").next().unwrap(); match self.external_packages.get(crate_name) { Some(name) => name.clone(), // If the module path is not in `external_packages`, we need to fall back to a default // with the namespace, which we hopefully have. This is quite fragile, but it's // unreachable in library mode - all deps get an entry in `external_packages` with the // correct namespace. None => format!("uniffi.{}", namespace.unwrap_or(module_path)), } } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct KotlinVersion((u16, u16, u16)); impl KotlinVersion { fn new(major: u16, minor: u16, patch: u16) -> Self { Self((major, minor, patch)) } fn parse(version: &str) -> Result { let components = version .split('.') .map(|n| { n.parse::() .map_err(|_| anyhow!("Invalid version string ({n} is not an integer)")) }) .collect::>>()?; match components.as_slice() { [major, minor, patch] => Ok(Self((*major, *minor, *patch))), [major, minor] => Ok(Self((*major, *minor, 0))), [major] => Ok(Self((*major, 0, 0))), _ => bail!("Invalid version string (expected 1-3 components): {version}"), } } } #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(default)] pub struct CustomTypeConfig { imports: Option>, type_name: Option, into_custom: String, // b/w compat alias for lift lift: String, from_custom: String, // b/w compat alias for lower lower: String, } // functions replace literal "{}" in strings with a specified value. impl CustomTypeConfig { fn lift(&self, name: &str) -> String { let converter = if self.lift.is_empty() { &self.into_custom } else { &self.lift }; converter.replace("{}", name) } fn lower(&self, name: &str) -> String { let converter = if self.lower.is_empty() { &self.from_custom } else { &self.lower }; converter.replace("{}", name) } } impl Config { // We insist someone has already configured us - any defaults we supply would be wrong. pub fn package_name(&self) -> String { self.package_name .as_ref() .expect("package name should have been set in update_component_configs") .clone() } pub fn cdylib_name(&self) -> String { self.cdylib_name .as_ref() .expect("cdylib name should have been set in update_component_configs") .clone() } /// Whether to generate immutable records (`val` instead of `var`) pub fn generate_immutable_records(&self) -> bool { self.generate_immutable_records.unwrap_or(false) } pub fn disable_java_cleaner(&self) -> bool { self.disable_java_cleaner } } // Generate kotlin bindings for the given ComponentInterface, as a string. pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result { ensure_flat_enum_trait_methods_supported(ci)?; KotlinWrapper::new(config.clone(), ci) .context("failed to create a binding generator")? .render() .context("failed to render kotlin bindings") } fn ensure_flat_enum_trait_methods_supported(ci: &ComponentInterface) -> Result<()> { for enum_def in ci.enum_definitions() { if ci.is_name_used_as_error(enum_def.name()) || !enum_def.is_flat() { continue; } let trait_methods = enum_def.uniffi_trait_methods(); if trait_methods.eq_eq.is_some() || trait_methods.hash_hash.is_some() || trait_methods.ord_cmp.is_some() { anyhow::bail!( "Kotlin bindings do not support exporting Eq/Ord/Hash for flat enum `{}`", enum_def.name() ); } } Ok(()) } /// A struct to record a Kotlin import statement. #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum ImportRequirement { /// The name we are importing. Import { name: String }, /// Import the name with the specified local name. ImportAs { name: String, as_name: String }, } impl ImportRequirement { /// Render the Kotlin import statement. fn render(&self) -> String { match &self { ImportRequirement::Import { name } => format!("import {name}"), ImportRequirement::ImportAs { name, as_name } => { format!("import {name} as {as_name}") } } } } /// Renders Kotlin helper code for all types /// /// This template is a bit different than others in that it stores internal state from the render /// process. Make sure to only call `render()` once. #[derive(Template)] #[template(syntax = "kt", escape = "none", path = "Types.kt")] pub struct TypeRenderer<'a> { config: &'a Config, ci: &'a ComponentInterface, // Track imports added with the `add_import()` macro imports: RefCell>, } impl<'a> TypeRenderer<'a> { fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self { Self { config, ci, imports: RefCell::new(BTreeSet::new()), } } // Get the package name for an external type fn external_type_package_name(&self, module_path: &str, namespace: &str) -> String { self.config .external_package_name(module_path, Some(namespace)) } // The following methods are used by the `Types.kt` macros. // Helper to add an import statement // // Call this inside your template to cause an import statement to be added at the top of the // file. Imports will be sorted and de-deuped. // // Returns an empty string so that it can be used inside an askama `{{ }}` block. fn add_import(&self, name: &str) -> &str { self.imports.borrow_mut().insert(ImportRequirement::Import { name: name.to_owned(), }); "" } // Like add_import, but arranges for `import name as as_name` fn add_import_as(&self, name: &str, as_name: &str) -> &str { self.imports .borrow_mut() .insert(ImportRequirement::ImportAs { name: name.to_owned(), as_name: as_name.to_owned(), }); "" } } #[derive(Template)] #[template(syntax = "kt", escape = "none", path = "wrapper.kt")] pub struct KotlinWrapper<'a> { config: Config, ci: &'a ComponentInterface, type_helper_code: String, type_imports: BTreeSet, } impl<'a> KotlinWrapper<'a> { pub fn new(config: Config, ci: &'a ComponentInterface) -> Result { let type_renderer = TypeRenderer::new(&config, ci); let type_helper_code = type_renderer.render()?; let type_imports = type_renderer.imports.into_inner(); Ok(Self { config, ci, type_helper_code, type_imports, }) } pub fn initialization_fns(&self, ci: &ComponentInterface) -> Vec { let init_fns = self .ci .iter_local_types() .map(|t| KotlinCodeOracle.find(t)) .filter_map(|ct| ct.initialization_fn()) .map(|fn_name| format!("{fn_name}(this)")); // Also call global initialization function for any external type we use. // For example, we need to make sure that all callback interface vtables are registered // (#2343). let extern_module_init_fns = self .ci .iter_external_types() .filter_map(|ty| ty.crate_name()) .map(|crate_name| { let namespace = ci.namespace_for_module_path(crate_name).unwrap(); let package_name = self .config .external_package_name(crate_name, Some(namespace)); format!("{package_name}.uniffiEnsureInitialized()") }) // Collect into a btree set to de-dup and order .collect::>(); init_fns.chain(extern_module_init_fns).collect() } pub fn imports(&self) -> Vec { self.type_imports.iter().cloned().collect() } } /// Get the name of the interface and class name for a trait. /// /// For a regular `struct Foo` or `trait Foo`, there's `FooInterface` with `Foo` as /// the name of the (Rust implemented) object. But if it's a foreign trait: /// * The name `Foo` is the name of the interface used by a the Kotlin implementation of the trait. /// * The Rust implemented object is `FooImpl`. /// /// This all impacts what types `FfiConverter.lower()` inputs. If it's a "foreign trait" /// `lower` must lower anything that implements the interface (ie, a kotlin implementation). /// If not, then lower only lowers the concrete class (ie, our simple instance with the pointer). fn object_interface_name(ci: &ComponentInterface, obj: &Object) -> String { let class_name = KotlinCodeOracle.class_name(ci, obj.name()); if obj.has_callback_interface() { class_name } else { format!("{class_name}Interface") } } // *sigh* - same thing for a trait, which might be either Object or CallbackInterface. // (we should either fold it into object or kill it!) fn trait_interface_name(ci: &ComponentInterface, trait_ty: &Type) -> Result { let Some(crate_name) = trait_ty.crate_name() else { bail!("Invalid trait_type: {trait_ty:?}"); }; let Some(ci_look) = ci.find_component_interface(crate_name) else { anyhow::bail!("no interface with crate_name: {}", crate_name); }; let (obj_name, has_callback_interface) = match trait_ty { Type::Object { name, .. } => { let Some(obj) = ci_look.get_object_definition(name) else { bail!("trait interface not found: {}", name); }; (name, obj.has_callback_interface()) } Type::CallbackInterface { name, .. } => (name, true), _ => { bail!("Invalid trait_type: {trait_ty:?}") } }; let class_name = KotlinCodeOracle.class_name(ci_look, obj_name); if has_callback_interface { Ok(class_name) } else { Ok(format!("{class_name}Interface")) } } // The name of the object exposing a Rust implementation. fn object_impl_name(ci: &ComponentInterface, obj: &Object) -> String { let class_name = KotlinCodeOracle.class_name(ci, obj.name()); if obj.has_callback_interface() { format!("{class_name}Impl") } else { class_name } } #[derive(Clone)] pub struct KotlinCodeOracle; impl KotlinCodeOracle { fn find(&self, type_: &Type) -> Box { type_.clone().as_type().as_codetype() } /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc). fn class_name(&self, ci: &ComponentInterface, nm: &str) -> String { let name = nm.to_string().to_upper_camel_case(); // fixup errors. if ci.is_name_used_as_error(nm) { self.convert_error_suffix(&name) } else { name } } fn convert_error_suffix(&self, nm: &str) -> String { match nm.strip_suffix("Error") { None => nm.to_string(), Some(stripped) => format!("{stripped}Exception"), } } /// Get the idiomatic Kotlin rendering of a function name. fn fn_name(&self, nm: &str) -> String { format!("`{}`", nm.to_string().to_lower_camel_case()) } /// Get the idiomatic Kotlin rendering of a variable name. fn var_name(&self, nm: &str) -> String { format!("`{}`", self.var_name_raw(nm)) } /// `var_name` without the backticks. Useful for using in `@Structure.FieldOrder`. pub fn var_name_raw(&self, nm: &str) -> String { nm.to_string().to_lower_camel_case() } /// Get the idiomatic Kotlin rendering of an individual enum variant. fn enum_variant_name(&self, nm: &str) -> String { nm.to_string().to_shouty_snake_case() } /// Get the idiomatic Kotlin rendering of an FFI callback function name fn ffi_callback_name(&self, nm: &str) -> String { format!("Uniffi{}", nm.to_upper_camel_case()) } /// Get the idiomatic Kotlin rendering of an FFI struct name fn ffi_struct_name(&self, nm: &str) -> String { format!("Uniffi{}", nm.to_upper_camel_case()) } fn ffi_type_label_by_value(&self, ffi_type: &FfiType, ci: &ComponentInterface) -> String { match ffi_type { FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type, ci)), FfiType::Struct(name) => format!("{}.UniffiByValue", self.ffi_struct_name(name)), _ => self.ffi_type_label(ffi_type, ci), } } /// FFI type name to use inside structs /// /// The main requirement here is that all types must have default values or else the struct /// won't work in some JNA contexts. fn ffi_type_label_for_ffi_struct(&self, ffi_type: &FfiType, ci: &ComponentInterface) -> String { match ffi_type { // Make callbacks function pointers nullable. This matches the semantics of a C // function pointer better and allows for `null` as a default value. FfiType::Callback(name) => format!("{}?", self.ffi_callback_name(name)), _ => self.ffi_type_label_by_value(ffi_type, ci), } } /// Default values for FFI /// /// This is used to: /// - Set a default return value for error results /// - Set a default for structs, which JNA sometimes requires fn ffi_default_value(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::UInt8 | FfiType::Int8 => "0.toByte()".to_owned(), FfiType::UInt16 | FfiType::Int16 => "0.toShort()".to_owned(), FfiType::UInt32 | FfiType::Int32 => "0".to_owned(), FfiType::UInt64 | FfiType::Int64 => "0.toLong()".to_owned(), FfiType::Float32 => "0.0f".to_owned(), FfiType::Float64 => "0.0".to_owned(), FfiType::Handle => "0.toLong()".to_owned(), FfiType::RustBuffer(_) => "RustBuffer.ByValue()".to_owned(), FfiType::Callback(_) => "null".to_owned(), FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue()".to_owned(), _ => unimplemented!("ffi_default_value: {ffi_type:?}"), } } fn ffi_type_label_by_reference(&self, ffi_type: &FfiType, ci: &ComponentInterface) -> String { match ffi_type { FfiType::Int8 | FfiType::UInt8 | FfiType::Int16 | FfiType::UInt16 | FfiType::Int32 | FfiType::UInt32 | FfiType::Int64 | FfiType::UInt64 | FfiType::Float32 | FfiType::Float64 | FfiType::Handle => format!("{}ByReference", self.ffi_type_label(ffi_type, ci)), // JNA structs default to ByReference FfiType::RustBuffer(_) | FfiType::Struct(_) => self.ffi_type_label(ffi_type, ci), _ => panic!("{ffi_type:?} by reference is not implemented"), } } fn ffi_type_label(&self, ffi_type: &FfiType, ci: &ComponentInterface) -> String { match ffi_type { // Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not // support them yet. Thus, we use the signed variants to represent both signed and unsigned // types from the component API. FfiType::Int8 | FfiType::UInt8 => "Byte".to_string(), FfiType::Int16 | FfiType::UInt16 => "Short".to_string(), FfiType::Int32 | FfiType::UInt32 => "Int".to_string(), FfiType::Int64 | FfiType::UInt64 => "Long".to_string(), FfiType::Float32 => "Float".to_string(), FfiType::Float64 => "Double".to_string(), FfiType::Handle => "Long".to_string(), FfiType::RustBuffer(maybe_external) => match maybe_external { Some(external_meta) if external_meta.crate_name() != ci.crate_name() => { format!("RustBuffer{}", external_meta.name) } _ => "RustBuffer".to_string(), }, FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue".to_string(), FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(), FfiType::Callback(name) => self.ffi_callback_name(name), FfiType::Struct(name) => self.ffi_struct_name(name), FfiType::Reference(inner) | FfiType::MutReference(inner) => { self.ffi_type_label_by_reference(inner, ci) } FfiType::VoidPointer => "Pointer".to_string(), } } } trait AsCodeType { fn as_codetype(&self) -> Box; } impl AsCodeType for T { fn as_codetype(&self) -> Box { // Map `Type` instances to a `Box` for that type. // // There is a companion match in `templates/Types.kt` which performs a similar function for the // template code. // // - When adding additional types here, make sure to also add a match arm to the `Types.kt` template. // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches match self.as_type() { Type::UInt8 => Box::new(primitives::UInt8CodeType), Type::Int8 => Box::new(primitives::Int8CodeType), Type::UInt16 => Box::new(primitives::UInt16CodeType), Type::Int16 => Box::new(primitives::Int16CodeType), Type::UInt32 => Box::new(primitives::UInt32CodeType), Type::Int32 => Box::new(primitives::Int32CodeType), Type::UInt64 => Box::new(primitives::UInt64CodeType), Type::Int64 => Box::new(primitives::Int64CodeType), Type::Float32 => Box::new(primitives::Float32CodeType), Type::Float64 => Box::new(primitives::Float64CodeType), Type::Boolean => Box::new(primitives::BooleanCodeType), Type::String => Box::new(primitives::StringCodeType), Type::Bytes => Box::new(primitives::BytesCodeType), Type::Timestamp => Box::new(miscellany::TimestampCodeType), Type::Duration => Box::new(miscellany::DurationCodeType), Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)), Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) } Type::Sequence { inner_type } => { Box::new(compounds::SequenceCodeType::new(*inner_type)) } Type::Map { key_type, value_type, } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)), Type::Custom { name, builtin, .. } => { Box::new(custom::CustomCodeType::new(name, builtin.as_codetype())) } } } } mod filters { use super::*; use uniffi_meta::LiteralMetadata; pub(super) fn type_name( as_ct: &impl AsCodeType, _: &dyn askama::Values, ci: &ComponentInterface, ) -> Result { Ok(as_ct.as_codetype().type_label(ci)) } pub(super) fn canonical_name( as_ct: &impl AsCodeType, _: &dyn askama::Values, ) -> Result { Ok(as_ct.as_codetype().canonical_name()) } pub(super) fn qualified_type_name( as_type: &T, _: &dyn askama::Values, ci: &ComponentInterface, config: &Config, ) -> Result where T: AsCodeType + AsType, { fully_qualified_type_label(&as_type.as_type(), ci, config) .map_err(|err| to_askama_error(&err)) } pub(super) fn ffi_converter_name( as_ct: &impl AsCodeType, _: &dyn askama::Values, ) -> Result { Ok(as_ct.as_codetype().ffi_converter_name()) } pub(super) fn ffi_type( type_: &impl AsType, _: &dyn askama::Values, ) -> askama::Result { Ok(type_.as_type().into()) } pub(super) fn lower_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, ) -> Result { Ok(format!( "{}.lower", as_ct.as_codetype().ffi_converter_name() )) } pub(super) fn allocation_size_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, ) -> Result { Ok(format!( "{}.allocationSize", as_ct.as_codetype().ffi_converter_name() )) } pub(super) fn write_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, ) -> Result { Ok(format!( "{}.write", as_ct.as_codetype().ffi_converter_name() )) } pub(super) fn lift_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, ) -> Result { Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name())) } pub(super) fn read_fn( as_ct: &impl AsCodeType, _: &dyn askama::Values, ) -> Result { Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name())) } fn fully_qualified_type_label( ty: &Type, ci: &ComponentInterface, config: &Config, ) -> Result { match ty { Type::Optional { inner_type } => Ok(format!( "{}?", fully_qualified_type_label(inner_type, ci, config)? )), Type::Sequence { inner_type } => Ok(format!( "List<{}>", fully_qualified_type_label(inner_type, ci, config)? )), Type::Map { key_type, value_type, } => Ok(format!( "Map<{}, {}>", fully_qualified_type_label(key_type, ci, config)?, fully_qualified_type_label(value_type, ci, config)? )), Type::Enum { .. } | Type::Record { .. } | Type::Object { .. } | Type::CallbackInterface { .. } | Type::Custom { .. } => { let class_name = ty .name() .map(|nm| KotlinCodeOracle.class_name(ci, nm)) .ok_or_else(|| anyhow::anyhow!("type {:?} has no name", ty))?; let package_name = package_for_type(ty, ci, config)?; Ok(format!("{package_name}.{class_name}")) } _ => Ok(KotlinCodeOracle.find(ty).type_label(ci)), } } fn package_for_type(ty: &Type, ci: &ComponentInterface, config: &Config) -> Result { if ci.is_external(ty) { let module_path = ty .module_path() .ok_or_else(|| anyhow::anyhow!("external type {:?} missing module path", ty))?; let namespace = ci.namespace_for_module_path(module_path)?; Ok(config.external_package_name(module_path, Some(namespace))) } else { Ok(config.package_name()) } } pub fn render_default( default: &DefaultValue, _: &dyn askama::Values, as_ct: &impl AsType, ci: &ComponentInterface, ) -> Result { as_ct .as_codetype() .default(default, ci) .map_err(|e| to_askama_error(&e)) } // Get the idiomatic Kotlin rendering of an integer. fn int_literal(t: &Option, base10: String) -> Result { if let Some(t) = t { match t { Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => Ok(base10), Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => Ok(base10 + "u"), _ => Err(to_askama_error(&format!( "Only ints are supported for enum literals: {t:?}" ))), } } else { Err(to_askama_error(&format!( "Enum hasn't defined a repr: {t:?}" ))) } } // Get the idiomatic Kotlin rendering of an individual enum variant's discriminant pub fn variant_discr_literal( e: &Enum, _: &dyn askama::Values, index: &usize, ) -> Result { let literal = e.variant_discr(*index).expect("invalid index"); match literal { // Kotlin doesn't convert between signed and unsigned by default // so we'll need to make sure we define the type as appropriately LiteralMetadata::UInt(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()), LiteralMetadata::Int(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()), _ => Err(to_askama_error(&format!( "Only ints are supported: {literal:?}" ))), } } pub fn ffi_type_name_by_value( type_: &FfiType, _: &dyn askama::Values, ci: &ComponentInterface, ) -> Result { Ok(KotlinCodeOracle.ffi_type_label_by_value(type_, ci)) } pub fn ffi_type_name_for_ffi_struct( type_: &FfiType, _: &dyn askama::Values, ci: &ComponentInterface, ) -> Result { Ok(KotlinCodeOracle.ffi_type_label_for_ffi_struct(type_, ci)) } pub fn ffi_default_value( type_: FfiType, _: &dyn askama::Values, ) -> Result { Ok(KotlinCodeOracle.ffi_default_value(&type_)) } /// Get the idiomatic Kotlin rendering of a function name. pub fn class_name>( nm: S, _: &dyn askama::Values, ci: &ComponentInterface, ) -> Result { Ok(KotlinCodeOracle.class_name(ci, nm.as_ref())) } /// Get the idiomatic Kotlin rendering of a function name. pub fn fn_name>(nm: S, _: &dyn askama::Values) -> Result { Ok(KotlinCodeOracle.fn_name(nm.as_ref())) } /// Get the idiomatic Kotlin rendering of a variable name. pub fn var_name>(nm: S, _: &dyn askama::Values) -> Result { Ok(KotlinCodeOracle.var_name(nm.as_ref())) } /// Get the idiomatic Kotlin rendering of a variable name. pub fn var_name_raw>( nm: S, _: &dyn askama::Values, ) -> Result { Ok(KotlinCodeOracle.var_name_raw(nm.as_ref())) } /// Get a String representing the name used for an individual enum variant. pub fn variant_name(v: &Variant, _: &dyn askama::Values) -> Result { Ok(KotlinCodeOracle.enum_variant_name(v.name())) } pub fn error_variant_name( v: &Variant, _: &dyn askama::Values, ) -> Result { let name = v.name().to_string().to_upper_camel_case(); Ok(KotlinCodeOracle.convert_error_suffix(&name)) } /// Get the idiomatic Kotlin rendering of an FFI callback function name pub fn ffi_callback_name>( nm: S, _: &dyn askama::Values, ) -> Result { Ok(KotlinCodeOracle.ffi_callback_name(nm.as_ref())) } /// Get the idiomatic Kotlin rendering of an FFI struct name pub fn ffi_struct_name>( nm: S, _: &dyn askama::Values, ) -> Result { Ok(KotlinCodeOracle.ffi_struct_name(nm.as_ref())) } pub fn async_poll( callable: impl Callable, _: &dyn askama::Values, ci: &ComponentInterface, ) -> Result { let ffi_func = callable.ffi_rust_future_poll(ci); Ok(format!( "{{ future, callback, continuation -> UniffiLib.{ffi_func}(future, callback, continuation) }}" )) } pub fn async_complete( callable: impl Callable, _: &dyn askama::Values, ci: &ComponentInterface, ) -> Result { let ffi_func = callable.ffi_rust_future_complete(ci); let call = format!("UniffiLib.{ffi_func}(future, continuation)"); // May need to convert the RustBuffer from our package to the RustBuffer of the external package let call = match callable.return_type() { Some(return_type) if ci.is_external(return_type) => { let ffi_type = FfiType::from(return_type); match ffi_type { FfiType::RustBuffer(Some(ExternalFfiMetadata { name, .. })) => { let suffix = KotlinCodeOracle.class_name(ci, &name); format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity.toULong(), it.len.toULong(), it.data) }}") } _ => call, } } _ => call, }; Ok(format!("{{ future, continuation -> {call} }}")) } pub fn async_free( callable: impl Callable, _: &dyn askama::Values, ci: &ComponentInterface, ) -> Result { let ffi_func = callable.ffi_rust_future_free(ci); Ok(format!("{{ future -> UniffiLib.{ffi_func}(future) }}")) } /// Remove the "`" chars we put around function/variable names /// /// These are used to avoid name clashes with kotlin identifiers, but sometimes you want to /// render the name unquoted. One example is the message property for errors where we want to /// display the name for the user. pub fn unquote>(nm: S, _: &dyn askama::Values) -> Result { Ok(nm.as_ref().trim_matches('`').to_string()) } /// Get the idiomatic Kotlin rendering of docstring pub fn docstring>( docstring: S, _: &dyn askama::Values, spaces: &i32, ) -> Result { let middle = textwrap::indent(&textwrap::dedent(docstring.as_ref()), " * "); let wrapped = format!("/**\n{middle}\n */"); let spaces = usize::try_from(*spaces).unwrap_or_default(); Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) } } #[cfg(test)] mod test { use super::*; #[test] fn test_kotlin_version() { assert_eq!( KotlinVersion::parse("1.2.3").unwrap(), KotlinVersion::new(1, 2, 3) ); assert_eq!( KotlinVersion::parse("2.3").unwrap(), KotlinVersion::new(2, 3, 0), ); assert_eq!( KotlinVersion::parse("2").unwrap(), KotlinVersion::new(2, 0, 0), ); assert!(KotlinVersion::parse("2.").is_err()); assert!(KotlinVersion::parse("").is_err()); assert!(KotlinVersion::parse("A.B.C").is_err()); assert!(KotlinVersion::new(1, 2, 3) > KotlinVersion::new(0, 1, 2)); assert!(KotlinVersion::new(1, 2, 3) > KotlinVersion::new(0, 100, 0)); assert!(KotlinVersion::new(10, 0, 0) > KotlinVersion::new(1, 10, 0)); } }