/* 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, HashSet}; use std::fmt::Debug; use anyhow::{Context, Result}; use askama::Template; use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use crate::backend::TemplateExpression; use crate::interface::*; use crate::BindingsConfig; mod callback_interface; mod compounds; mod custom; mod enum_; mod executor; mod external; 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; fn literal(&self, _literal: &Literal, ci: &ComponentInterface) -> String { unimplemented!("Unimplemented for {}", self.type_label(ci)) } /// 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()) } /// A list of imports that are needed if this type is in use. /// Classes are imported exactly once. fn imports(&self) -> Option> { None } /// 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 { package_name: Option, cdylib_name: Option, #[serde(default)] custom_types: HashMap, #[serde(default)] external_packages: HashMap, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct CustomTypeConfig { imports: Option>, type_name: Option, into_custom: TemplateExpression, from_custom: TemplateExpression, } impl Config { pub fn package_name(&self) -> String { if let Some(package_name) = &self.package_name { package_name.clone() } else { "uniffi".into() } } pub fn cdylib_name(&self) -> String { if let Some(cdylib_name) = &self.cdylib_name { cdylib_name.clone() } else { "uniffi".into() } } } impl BindingsConfig for Config { fn update_from_ci(&mut self, ci: &ComponentInterface) { self.package_name .get_or_insert_with(|| format!("uniffi.{}", ci.namespace())); self.cdylib_name .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); } fn update_from_cdylib_name(&mut self, cdylib_name: &str) { self.cdylib_name .get_or_insert_with(|| cdylib_name.to_string()); } fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>) { for (crate_name, config) in config_map { if !self.external_packages.contains_key(crate_name) { self.external_packages .insert(crate_name.to_string(), config.package_name()); } } } } // Generate kotlin bindings for the given ComponentInterface, as a string. pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result { KotlinWrapper::new(config.clone(), ci) .render() .context("failed to render kotlin bindings") } /// 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 included modules for the `include_once()` macro include_once_names: RefCell>, // 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, include_once_names: RefCell::new(HashSet::new()), 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 { // config overrides are keyed by the crate name, default fallback is the namespace. let crate_name = module_path.split("::").next().unwrap(); match self.config.external_packages.get(crate_name) { Some(name) => name.clone(), // unreachable in library mode - all deps are in our config with correct namespace. None => format!("uniffi.{namespace}"), } } // The following methods are used by the `Types.kt` macros. // Helper for the including a template, but only once. // // The first time this is called with a name it will return true, indicating that we should // include the template. Subsequent calls will return false. fn include_once_check(&self, name: &str) -> bool { self.include_once_names .borrow_mut() .insert(name.to_string()) } // 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, has_async_fns: bool, } impl<'a> KotlinWrapper<'a> { pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { let type_renderer = TypeRenderer::new(&config, ci); let type_helper_code = type_renderer.render().unwrap(); let type_imports = type_renderer.imports.into_inner(); Self { config, ci, type_helper_code, type_imports, has_async_fns: ci.has_async_fns(), } } pub fn initialization_fns(&self) -> Vec { self.ci .iter_types() .map(|t| KotlinCodeOracle.find(t)) .filter_map(|ct| ct.initialization_fn()) .chain( self.has_async_fns .then(|| "uniffiRustFutureContinuationCallback.register".into()), ) .collect() } pub fn imports(&self) -> Vec { self.type_imports.iter().cloned().collect() } } #[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. ci.is_name_used_as_error(nm) .then(|| self.convert_error_suffix(&name)) .unwrap_or(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!("`{}`", 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() } fn ffi_type_label_by_value(ffi_type: &FfiType) -> String { match ffi_type { FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)), _ => Self::ffi_type_label(ffi_type), } } fn ffi_type_label(ffi_type: &FfiType) -> 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::RustArcPtr(_) => "Pointer".to_string(), FfiType::RustBuffer(maybe_suffix) => { format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) } FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(), FfiType::ForeignCallback => "ForeignCallback".to_string(), FfiType::ForeignExecutorHandle => "USize".to_string(), FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(), FfiType::RustFutureHandle => "Pointer".to_string(), FfiType::RustFutureContinuationCallback => { "UniFffiRustFutureContinuationCallbackType".to_string() } FfiType::RustFutureContinuationData => "USize".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, .. } => Box::new(object::ObjectCodeType::new(name)), Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), 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::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), } } } mod filters { use super::*; pub use crate::backend::filters::*; pub(super) fn type_name( as_ct: &impl AsCodeType, ci: &ComponentInterface, ) -> Result { Ok(as_ct.as_codetype().type_label(ci)) } pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result { Ok(as_ct.as_codetype().canonical_name()) } pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result { Ok(as_ct.as_codetype().ffi_converter_name()) } pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!( "{}.lower", as_ct.as_codetype().ffi_converter_name() )) } pub(super) fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!( "{}.allocationSize", as_ct.as_codetype().ffi_converter_name() )) } pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!( "{}.write", as_ct.as_codetype().ffi_converter_name() )) } pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name())) } pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name())) } pub fn render_literal( literal: &Literal, as_ct: &impl AsType, ci: &ComponentInterface, ) -> Result { Ok(as_ct.as_codetype().literal(literal, ci)) } pub fn ffi_type_name_by_value(type_: &FfiType) -> Result { Ok(KotlinCodeOracle::ffi_type_label_by_value(type_)) } /// Get the idiomatic Kotlin rendering of a function name. pub fn fn_name(nm: &str) -> Result { Ok(KotlinCodeOracle.fn_name(nm)) } /// Get the idiomatic Kotlin rendering of a variable name. pub fn var_name(nm: &str) -> Result { Ok(KotlinCodeOracle.var_name(nm)) } /// Get a String representing the name used for an individual enum variant. pub fn variant_name(v: &Variant) -> Result { Ok(KotlinCodeOracle.enum_variant_name(v.name())) } pub fn error_variant_name(v: &Variant) -> Result { let name = v.name().to_string().to_upper_camel_case(); Ok(KotlinCodeOracle.convert_error_suffix(&name)) } pub fn async_poll( callable: impl Callable, ci: &ComponentInterface, ) -> Result { let ffi_func = callable.ffi_rust_future_poll(ci); Ok(format!( "{{ future, continuation -> _UniFFILib.INSTANCE.{ffi_func}(future, continuation) }}" )) } pub fn async_complete( callable: impl Callable, ci: &ComponentInterface, ) -> Result { let ffi_func = callable.ffi_rust_future_complete(ci); let call = format!("_UniFFILib.INSTANCE.{ffi_func}(future, continuation)"); let call = match callable.return_type() { Some(Type::External { kind: ExternalKind::DataClass, name, .. }) => { // Need to convert the RustBuffer from our package to the RustBuffer of the external package let suffix = KotlinCodeOracle.class_name(ci, &name); format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}") } _ => call, }; Ok(format!("{{ future, continuation -> {call} }}")) } pub fn async_free( callable: impl Callable, ci: &ComponentInterface, ) -> Result { let ffi_func = callable.ffi_rust_future_free(ci); Ok(format!( "{{ future -> _UniFFILib.INSTANCE.{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: &str) -> Result { Ok(nm.trim_matches('`').to_string()) } }