diff options
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings/kotlin')
51 files changed, 3224 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs new file mode 100644 index 0000000000..e20020e87c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs @@ -0,0 +1,31 @@ +/* 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 super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct CallbackInterfaceCodeType { + id: String, +} + +impl CallbackInterfaceCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for CallbackInterfaceCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn initialization_fn(&self) -> Option<String> { + Some(format!("{}.register", self.ffi_converter_name())) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs new file mode 100644 index 0000000000..4329f32f4c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs @@ -0,0 +1,98 @@ +/* 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 super::{AsCodeType, CodeType}; +use crate::backend::{Literal, Type}; +use crate::ComponentInterface; +use paste::paste; + +fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String { + match literal { + Literal::Null => "null".into(), + Literal::EmptySequence => "listOf()".into(), + Literal::EmptyMap => "mapOf()".into(), + + // For optionals + _ => super::KotlinCodeOracle.find(inner).literal(literal, ci), + } +} + +macro_rules! impl_code_type_for_compound { + ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => { + paste! { + #[derive(Debug)] + pub struct $T { + inner: Type, + } + + impl $T { + pub fn new(inner: Type) -> Self { + Self { inner } + } + fn inner(&self) -> &Type { + &self.inner + } + } + + impl CodeType for $T { + fn type_label(&self, ci: &ComponentInterface) -> String { + format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label(ci)) + } + + fn canonical_name(&self) -> String { + format!($canonical_name_pattern, super::KotlinCodeOracle.find(self.inner()).canonical_name()) + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(literal, self.inner(), ci) + } + } + } + } + } + +impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}"); +impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}"); + +#[derive(Debug)] +pub struct MapCodeType { + key: Type, + value: Type, +} + +impl MapCodeType { + pub fn new(key: Type, value: Type) -> Self { + Self { key, value } + } + + fn key(&self) -> &Type { + &self.key + } + + fn value(&self) -> &Type { + &self.value + } +} + +impl CodeType for MapCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + format!( + "Map<{}, {}>", + super::KotlinCodeOracle.find(self.key()).type_label(ci), + super::KotlinCodeOracle.find(self.value()).type_label(ci), + ) + } + + fn canonical_name(&self) -> String { + format!( + "Map{}{}", + self.key().as_codetype().canonical_name(), + self.value().as_codetype().canonical_name(), + ) + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(literal, &self.value, ci) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs new file mode 100644 index 0000000000..137cd0d8d9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs @@ -0,0 +1,27 @@ +/* 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 super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct CustomCodeType { + name: String, +} + +impl CustomCodeType { + pub fn new(name: String) -> Self { + CustomCodeType { name } + } +} + +impl CodeType for CustomCodeType { + fn type_label(&self, _ci: &ComponentInterface) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs new file mode 100644 index 0000000000..f5300c10ee --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs @@ -0,0 +1,40 @@ +/* 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 super::CodeType; +use crate::backend::Literal; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct EnumCodeType { + id: String, +} + +impl EnumCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for EnumCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + if let Literal::Enum(v, _) = literal { + format!( + "{}.{}", + self.type_label(ci), + super::KotlinCodeOracle.enum_variant_name(v) + ) + } else { + unreachable!(); + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs new file mode 100644 index 0000000000..154e12a381 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs @@ -0,0 +1,24 @@ +/* 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 super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct ForeignExecutorCodeType; + +impl CodeType for ForeignExecutorCodeType { + fn type_label(&self, _ci: &ComponentInterface) -> String { + // Kotlin uses a CoroutineScope for ForeignExecutor + "CoroutineScope".into() + } + + fn canonical_name(&self) -> String { + "ForeignExecutor".into() + } + + fn initialization_fn(&self) -> Option<String> { + Some("FfiConverterForeignExecutor.register".into()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs new file mode 100644 index 0000000000..3ecf09d47f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs @@ -0,0 +1,27 @@ +/* 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 super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct ExternalCodeType { + name: String, +} + +impl ExternalCodeType { + pub fn new(name: String) -> Self { + Self { name } + } +} + +impl CodeType for ExternalCodeType { + fn type_label(&self, _ci: &ComponentInterface) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs new file mode 100644 index 0000000000..17331ff511 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs @@ -0,0 +1,30 @@ +/* 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 super::CodeType; +use crate::ComponentInterface; +use paste::paste; + +macro_rules! impl_code_type_for_miscellany { + ($T:ty, $class_name:literal, $canonical_name:literal) => { + paste! { + #[derive(Debug)] + pub struct $T; + + impl CodeType for $T { + fn type_label(&self, _ci: &ComponentInterface) -> String { + $class_name.into() + } + + fn canonical_name(&self) -> String { + $canonical_name.into() + } + } + } + }; +} + +impl_code_type_for_miscellany!(TimestampCodeType, "java.time.Instant", "Timestamp"); + +impl_code_type_for_miscellany!(DurationCodeType, "java.time.Duration", "Duration"); diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs new file mode 100644 index 0000000000..1ed0575a9a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -0,0 +1,530 @@ +/* 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<Vec<String>> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option<String> { + None + } +} + +// config options to customize the generated Kotlin. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Config { + package_name: Option<String>, + cdylib_name: Option<String>, + #[serde(default)] + custom_types: HashMap<String, CustomTypeConfig>, + #[serde(default)] + external_packages: HashMap<String, String>, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CustomTypeConfig { + imports: Option<Vec<String>>, + type_name: Option<String>, + 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<String> { + 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<HashSet<String>>, + // Track imports added with the `add_import()` macro + imports: RefCell<BTreeSet<ImportRequirement>>, +} + +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<ImportRequirement>, + 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<String> { + 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<ImportRequirement> { + self.type_imports.iter().cloned().collect() + } +} + +#[derive(Clone)] +pub struct KotlinCodeOracle; + +impl KotlinCodeOracle { + fn find(&self, type_: &Type) -> Box<dyn CodeType> { + 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<dyn CodeType>; +} + +impl<T: AsType> AsCodeType for T { + fn as_codetype(&self) -> Box<dyn CodeType> { + // Map `Type` instances to a `Box<dyn CodeType>` 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<String, askama::Error> { + Ok(as_ct.as_codetype().type_label(ci)) + } + + pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().canonical_name()) + } + + pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().ffi_converter_name()) + } + + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!( + "{}.lower", + as_ct.as_codetype().ffi_converter_name() + )) + } + + pub(super) fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!( + "{}.allocationSize", + as_ct.as_codetype().ffi_converter_name() + )) + } + + pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!( + "{}.write", + as_ct.as_codetype().ffi_converter_name() + )) + } + + pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name())) + } + + pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name())) + } + + pub fn render_literal( + literal: &Literal, + as_ct: &impl AsType, + ci: &ComponentInterface, + ) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().literal(literal, ci)) + } + + pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle::ffi_type_label_by_value(type_)) + } + + /// Get the idiomatic Kotlin rendering of a function name. + pub fn fn_name(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.fn_name(nm)) + } + + /// Get the idiomatic Kotlin rendering of a variable name. + pub fn var_name(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.var_name(nm)) + } + + /// Get a String representing the name used for an individual enum variant. + pub fn variant_name(v: &Variant) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.enum_variant_name(v.name())) + } + + pub fn error_variant_name(v: &Variant) -> Result<String, askama::Error> { + 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<String, askama::Error> { + 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<String, askama::Error> { + 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<String, askama::Error> { + 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<String, askama::Error> { + Ok(nm.trim_matches('`').to_string()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs new file mode 100644 index 0000000000..c39ae59cce --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs @@ -0,0 +1,27 @@ +/* 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 super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct ObjectCodeType { + id: String, +} + +impl ObjectCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ObjectCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs new file mode 100644 index 0000000000..22495fa209 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs @@ -0,0 +1,86 @@ +/* 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 super::CodeType; +use crate::backend::Literal; +use crate::interface::{ComponentInterface, Radix, Type}; +use paste::paste; + +fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String { + fn typed_number(type_: &Type, num_str: String) -> String { + match type_ { + // Bytes, Shorts and Ints can all be inferred from the type. + Type::Int8 | Type::Int16 | Type::Int32 => num_str, + Type::Int64 => format!("{num_str}L"), + + Type::UInt8 | Type::UInt16 | Type::UInt32 => format!("{num_str}u"), + Type::UInt64 => format!("{num_str}uL"), + + Type::Float32 => format!("{num_str}f"), + Type::Float64 => num_str, + _ => panic!("Unexpected literal: {num_str} is not a number"), + } + } + + match literal { + Literal::Boolean(v) => format!("{v}"), + Literal::String(s) => format!("\"{s}\""), + Literal::Int(i, radix, type_) => typed_number( + type_, + match radix { + Radix::Octal => format!("{i:#x}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::UInt(i, radix, type_) => typed_number( + type_, + match radix { + Radix::Octal => format!("{i:#x}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::Float(string, type_) => typed_number(type_, string.clone()), + + _ => unreachable!("Literal"), + } +} + +macro_rules! impl_code_type_for_primitive { + ($T:ty, $class_name:literal) => { + paste! { + #[derive(Debug)] + pub struct $T; + + impl CodeType for $T { + fn type_label(&self, _ci: &ComponentInterface) -> String { + $class_name.into() + } + + fn canonical_name(&self) -> String { + $class_name.into() + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(&literal, ci) + } + } + } + }; +} + +impl_code_type_for_primitive!(BooleanCodeType, "Boolean"); +impl_code_type_for_primitive!(StringCodeType, "String"); +impl_code_type_for_primitive!(BytesCodeType, "ByteArray"); +impl_code_type_for_primitive!(Int8CodeType, "Byte"); +impl_code_type_for_primitive!(Int16CodeType, "Short"); +impl_code_type_for_primitive!(Int32CodeType, "Int"); +impl_code_type_for_primitive!(Int64CodeType, "Long"); +impl_code_type_for_primitive!(UInt8CodeType, "UByte"); +impl_code_type_for_primitive!(UInt16CodeType, "UShort"); +impl_code_type_for_primitive!(UInt32CodeType, "UInt"); +impl_code_type_for_primitive!(UInt64CodeType, "ULong"); +impl_code_type_for_primitive!(Float32CodeType, "Float"); +impl_code_type_for_primitive!(Float64CodeType, "Double"); diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs new file mode 100644 index 0000000000..17781c2220 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs @@ -0,0 +1,27 @@ +/* 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 super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct RecordCodeType { + id: String, +} + +impl RecordCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for RecordCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs new file mode 100644 index 0000000000..c7673882d9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs @@ -0,0 +1,33 @@ +/* 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 super::{AsCodeType, CodeType, KotlinCodeOracle}; +use crate::interface::{ComponentInterface, Variant}; + +#[derive(Debug)] +pub(super) struct VariantCodeType { + pub v: Variant, +} + +impl CodeType for VariantCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + KotlinCodeOracle.class_name(ci, self.v.name()) + } + + fn canonical_name(&self) -> String { + self.v.name().to_string() + } +} + +impl AsCodeType for Variant { + fn as_codetype(&self) -> Box<dyn CodeType> { + Box::new(VariantCodeType { v: self.clone() }) + } +} + +impl AsCodeType for &Variant { + fn as_codetype(&self) -> Box<dyn CodeType> { + Box::new(VariantCodeType { v: (*self).clone() }) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs new file mode 100644 index 0000000000..466fe77879 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs @@ -0,0 +1,41 @@ +/* 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 anyhow::Result; +use camino::{Utf8Path, Utf8PathBuf}; +use fs_err as fs; +use std::process::Command; + +pub mod gen_kotlin; +pub use gen_kotlin::{generate_bindings, Config}; +mod test; + +use super::super::interface::ComponentInterface; +pub use test::{run_script, run_test}; + +pub fn write_bindings( + config: &Config, + ci: &ComponentInterface, + out_dir: &Utf8Path, + try_format_code: bool, +) -> Result<()> { + let mut kt_file = full_bindings_path(config, out_dir); + fs::create_dir_all(&kt_file)?; + kt_file.push(format!("{}.kt", ci.namespace())); + fs::write(&kt_file, generate_bindings(config, ci)?)?; + if try_format_code { + if let Err(e) = Command::new("ktlint").arg("-F").arg(&kt_file).output() { + println!( + "Warning: Unable to auto-format {} using ktlint: {e:?}", + kt_file.file_name().unwrap(), + ); + } + } + Ok(()) +} + +fn full_bindings_path(config: &Config, out_dir: &Utf8Path) -> Utf8PathBuf { + let package_path: Utf8PathBuf = config.package_name().split('.').collect(); + Utf8PathBuf::from(out_dir).join(package_path) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt new file mode 100644 index 0000000000..c6a32655f2 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -0,0 +1,44 @@ +// Async return type handlers + +internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort() +internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort() + +internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>() + +// FFI type for Rust future continuations +internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType { + override fun callback(continuationHandle: USize, pollResult: Short) { + uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult) + } + + internal fun register(lib: _UniFFILib) { + lib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(this) + } +} + +internal suspend fun<T, F, E: Exception> uniffiRustCallAsync( + rustFuture: Pointer, + pollFunc: (Pointer, USize) -> Unit, + completeFunc: (Pointer, RustCallStatus) -> F, + freeFunc: (Pointer) -> Unit, + liftFunc: (F) -> T, + errorHandler: CallStatusErrorHandler<E> +): T { + try { + do { + val pollResult = suspendCancellableCoroutine<Short> { continuation -> + pollFunc( + rustFuture, + uniffiContinuationHandleMap.insert(continuation) + ) + } + } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY); + + return liftFunc( + rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }) + ) + } finally { + freeFunc(rustFuture) + } +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt new file mode 100644 index 0000000000..8cfa2ce000 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt @@ -0,0 +1,19 @@ +public object FfiConverterBoolean: FfiConverter<Boolean, Byte> { + override fun lift(value: Byte): Boolean { + return value.toInt() != 0 + } + + override fun read(buf: ByteBuffer): Boolean { + return lift(buf.get()) + } + + override fun lower(value: Boolean): Byte { + return if (value) 1.toByte() else 0.toByte() + } + + override fun allocationSize(value: Boolean) = 1 + + override fun write(value: Boolean, buf: ByteBuffer) { + buf.put(lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt new file mode 100644 index 0000000000..4840a199b4 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt @@ -0,0 +1,15 @@ +public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> { + override fun read(buf: ByteBuffer): ByteArray { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr + } + override fun allocationSize(value: ByteArray): Int { + return 4 + value.size + } + override fun write(value: ByteArray, buf: ByteBuffer) { + buf.putInt(value.size) + buf.put(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt new file mode 100644 index 0000000000..62a71e02f1 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt @@ -0,0 +1,78 @@ +internal typealias Handle = Long +internal class ConcurrentHandleMap<T>( + private val leftMap: MutableMap<Handle, T> = mutableMapOf(), + private val rightMap: MutableMap<T, Handle> = mutableMapOf() +) { + private val lock = java.util.concurrent.locks.ReentrantLock() + private val currentHandle = AtomicLong(0L) + private val stride = 1L + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: + currentHandle.getAndAdd(stride) + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } +} + +interface ForeignCallback : com.sun.jna.Callback { + public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +internal const val IDX_CALLBACK_FREE = 0 +// Callback return codes +internal const val UNIFFI_CALLBACK_SUCCESS = 0 +internal const val UNIFFI_CALLBACK_ERROR = 1 +internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 + +public abstract class FfiConverterCallbackInterface<CallbackInterface>( + protected val foreignCallback: ForeignCallback +): FfiConverter<CallbackInterface, Handle> { + private val handleMap = ConcurrentHandleMap<CallbackInterface>() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal abstract fun register(lib: _UniFFILib) + + fun drop(handle: Handle): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + override fun lift(value: Handle): CallbackInterface { + return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + } + + override fun read(buf: ByteBuffer) = lift(buf.getLong()) + + override fun lower(value: CallbackInterface) = + handleMap.insert(value).also { + assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + override fun allocationSize(value: CallbackInterface) = 8 + + override fun write(value: CallbackInterface, buf: ByteBuffer) { + buf.putLong(lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt new file mode 100644 index 0000000000..5a29f0acc3 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -0,0 +1,129 @@ +{%- let cbi = ci|get_callback_interface_definition(name) %} +{%- let type_name = cbi|type_name(ci) %} +{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %} + +{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %} +{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }} +{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }} +{{- self.add_import("kotlin.concurrent.withLock") }} + +// Declaration and FfiConverters for {{ type_name }} Callback Interface + +public interface {{ type_name }} { + {% for meth in cbi.methods() -%} + fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) + {%- match meth.return_type() -%} + {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} + {%- else -%} + {%- endmatch %} + {% endfor %} + companion object +} + +// The ForeignCallback that is passed to Rust. +internal class {{ foreign_callback }} : ForeignCallback { + @Suppress("TooGenericExceptionCaught") + override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { + val cb = {{ ffi_converter_name }}.lift(handle) + return when (method) { + IDX_CALLBACK_FREE -> { + {{ ffi_converter_name }}.drop(handle) + // Successful return + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + UNIFFI_CALLBACK_SUCCESS + } + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} + {{ loop.index }} -> { + // Call the method, write to outBuf and return a status code + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info + try { + this.{{ method_name }}(cb, argsData, argsLen, outBuf) + } catch (e: Throwable) { + // Unexpected error + try { + // Try to serialize the error into a string + outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString())) + } catch (e: Throwable) { + // If that fails, then it's time to give up and just return + } + UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + } + {% endfor %} + else -> { + // An unexpected error happened. + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + try { + // Try to serialize the error into a string + outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index")) + } catch (e: Throwable) { + // If that fails, then it's time to give up and just return + } + UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + } + } + + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name %} + @Suppress("UNUSED_PARAMETER") + private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { + {%- if meth.arguments().len() > 0 %} + val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also { + it.order(ByteOrder.BIG_ENDIAN) + } + {%- endif %} + + {%- match meth.return_type() %} + {%- when Some with (return_type) %} + fun makeCall() : Int { + val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}( + {%- for arg in meth.arguments() %} + {{ arg|read_fn }}(argsBuf) + {% if !loop.last %}, {% endif %} + {%- endfor %} + ) + outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue)) + return UNIFFI_CALLBACK_SUCCESS + } + {%- when None %} + fun makeCall() : Int { + kotlinCallbackInterface.{{ meth.name()|fn_name }}( + {%- for arg in meth.arguments() %} + {{ arg|read_fn }}(argsBuf) + {%- if !loop.last %}, {% endif %} + {%- endfor %} + ) + return UNIFFI_CALLBACK_SUCCESS + } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + fun makeCallAndHandleError() : Int = makeCall() + {%- when Some(error_type) %} + fun makeCallAndHandleError() : Int = try { + makeCall() + } catch (e: {{ error_type|type_name(ci) }}) { + // Expected error, serialize it into outBuf + outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e)) + UNIFFI_CALLBACK_ERROR + } + {%- endmatch %} + + return makeCallAndHandleError() + } + {% endfor %} +} + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>( + foreignCallback = {{ foreign_callback }}() +) { + override fun register(lib: _UniFFILib) { + rustCall() { status -> + lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status) + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt new file mode 100644 index 0000000000..04150c5d78 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt @@ -0,0 +1,62 @@ +{%- match config.custom_types.get(name.as_str()) %} +{%- when None %} +{#- Define the type using typealiases to the builtin #} +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias {{ name }} = {{ builtin|type_name(ci) }} +public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }} + +{%- when Some with (config) %} + +{%- let ffi_type_name=builtin|ffi_type|ffi_type_name_by_value %} + +{# When the config specifies a different type name, create a typealias for it #} +{%- match config.type_name %} +{%- when Some(concrete_type_name) %} +/** + * Typealias from the type name used in the UDL file to the custom type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias {{ name }} = {{ concrete_type_name }} +{%- else %} +{%- endmatch %} + +{%- match config.imports %} +{%- when Some(imports) %} +{%- for import_name in imports %} +{{ self.add_import(import_name) }} +{%- endfor %} +{%- else %} +{%- endmatch %} + +public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_name }}> { + override fun lift(value: {{ ffi_type_name }}): {{ name }} { + val builtinValue = {{ builtin|lift_fn }}(value) + return {{ config.into_custom.render("builtinValue") }} + } + + override fun lower(value: {{ name }}): {{ ffi_type_name }} { + val builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|lower_fn }}(builtinValue) + } + + override fun read(buf: ByteBuffer): {{ name }} { + val builtinValue = {{ builtin|read_fn }}(buf) + return {{ config.into_custom.render("builtinValue") }} + } + + override fun allocationSize(value: {{ name }}): Int { + val builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|allocation_size_fn }}(builtinValue) + } + + override fun write(value: {{ name }}, buf: ByteBuffer) { + val builtinValue = {{ config.from_custom.render("value") }} + {{ builtin|write_fn }}(builtinValue, buf) + } +} +{%- endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt new file mode 100644 index 0000000000..4237c6f9a8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt @@ -0,0 +1,36 @@ +public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> { + override fun read(buf: ByteBuffer): java.time.Duration { + // Type mismatch (should be u64) but we check for overflow/underflow below + val seconds = buf.getLong() + // Type mismatch (should be u32) but we check for overflow/underflow below + val nanoseconds = buf.getInt().toLong() + if (seconds < 0) { + throw java.time.DateTimeException("Duration exceeds minimum or maximum value supported by uniffi") + } + if (nanoseconds < 0) { + throw java.time.DateTimeException("Duration nanoseconds exceed minimum or maximum supported by uniffi") + } + return java.time.Duration.ofSeconds(seconds, nanoseconds) + } + + // 8 bytes for seconds, 4 bytes for nanoseconds + override fun allocationSize(value: java.time.Duration) = 12 + + override fun write(value: java.time.Duration, buf: ByteBuffer) { + if (value.seconds < 0) { + // Rust does not support negative Durations + throw IllegalArgumentException("Invalid duration, must be non-negative") + } + + if (value.nano < 0) { + // Java docs provide guarantee that nano will always be positive, so this should be impossible + // See: https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html + throw IllegalArgumentException("Invalid duration, nano value must be non-negative") + } + + // Type mismatch (should be u64) but since Rust doesn't support negative durations we should be OK + buf.putLong(value.seconds) + // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK + buf.putInt(value.nano) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt new file mode 100644 index 0000000000..d4c4a1684a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt @@ -0,0 +1,110 @@ +{# +// Kotlin's `enum class` construct doesn't support variants with associated data, +// but is a little nicer for consumers than its `sealed class` enum pattern. +// So, we switch here, using `enum class` for enums with no associated data +// and `sealed class` for the general case. +#} + +{%- if e.is_flat() %} + +enum class {{ type_name }} { + {% for variant in e.variants() -%} + {{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %} + {%- endfor %} + companion object +} + +public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { + override fun read(buf: ByteBuffer) = try { + {{ type_name }}.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: {{ type_name }}) = 4 + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + +{% else %} + +sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} { + {% for variant in e.variants() -%} + {% if !variant.has_fields() -%} + object {{ variant|type_name(ci) }} : {{ type_name }}() + {% else -%} + data class {{ variant|type_name(ci) }}( + {% for field in variant.fields() -%} + val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} + {% endfor -%} + ) : {{ type_name }}() { + companion object + } + {%- endif %} + {% endfor %} + + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + when(this) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|type_name(ci) }} -> { + {%- if variant.has_fields() %} + {% call kt::destroy_fields(variant) %} + {% else -%} + // Nothing to destroy + {%- endif %} + } + {%- endfor %} + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + {% endif %} + companion object +} + +public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}>{ + override fun read(buf: ByteBuffer): {{ type_name }} { + return when(buf.getInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant|type_name(ci) }}{% if variant.has_fields() %}( + {% for field in variant.fields() -%} + {{ field|read_fn }}(buf), + {% endfor -%} + ){%- endif -%} + {%- endfor %} + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: {{ type_name }}) = when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|type_name(ci) }} -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4 + {%- for field in variant.fields() %} + + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + ) + } + {%- endfor %} + } + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|type_name(ci) }} -> { + buf.putInt({{ loop.index }}) + {%- for field in variant.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + Unit + } + {%- endfor %} + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +{% endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt new file mode 100644 index 0000000000..986db5424d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt @@ -0,0 +1,111 @@ +{%- let type_name = type_|type_name(ci) %} +{%- let ffi_converter_name = type_|ffi_converter_name %} +{%- let canonical_type_name = type_|canonical_name %} + +{% if e.is_flat() %} +sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} { + // Each variant is a nested class + // Flat enums carries a string error message, so no special implementation is necessary. + {% for variant in e.variants() -%} + class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message) + {% endfor %} + + companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf) + } +} +{%- else %} +sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} { + // Each variant is a nested class + {% for variant in e.variants() -%} + {%- let variant_name = variant|error_variant_name %} + class {{ variant_name }}( + {% for field in variant.fields() -%} + val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} + {% endfor -%} + ) : {{ type_name }}() { + override val message + get() = "{%- for field in variant.fields() %}{{ field.name()|var_name|unquote }}=${ {{field.name()|var_name }} }{% if !loop.last %}, {% endif %}{% endfor %}" + } + {% endfor %} + + companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf) + } + + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + when(this) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|error_variant_name }} -> { + {%- if variant.has_fields() %} + {% call kt::destroy_fields(variant) %} + {% else -%} + // Nothing to destroy + {%- endif %} + } + {%- endfor %} + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + {% endif %} +} +{%- endif %} + +public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}> { + override fun read(buf: ByteBuffer): {{ type_name }} { + {% if e.is_flat() %} + return when(buf.getInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({{ Type::String.borrow()|read_fn }}(buf)) + {%- endfor %} + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + {% else %} + + return when(buf.getInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({% if variant.has_fields() %} + {% for field in variant.fields() -%} + {{ field|read_fn }}(buf), + {% endfor -%} + {%- endif -%}) + {%- endfor %} + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + {%- endif %} + } + + override fun allocationSize(value: {{ type_name }}): Int { + {%- if e.is_flat() %} + return 4 + {%- else %} + return when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|error_variant_name }} -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4 + {%- for field in variant.fields() %} + + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + ) + {%- endfor %} + } + {%- endif %} + } + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|error_variant_name }} -> { + buf.putInt({{ loop.index }}) + {%- for field in variant.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + Unit + } + {%- endfor %} + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt new file mode 100644 index 0000000000..0fade7a0bc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt @@ -0,0 +1,9 @@ +{%- let package_name=self.external_type_package_name(module_path, namespace) %} +{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %} +{%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %} +{%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %} +{%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %} + +{{- self.add_import(fully_qualified_type_name) }} +{{- self.add_import(fully_qualified_ffi_converter_name) }} +{{ self.add_import_as(fully_qualified_rustbuffer_name, local_rustbuffer_name) }} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt new file mode 100644 index 0000000000..3b2c9d225a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt @@ -0,0 +1,71 @@ +// The FfiConverter interface handles converter types to and from the FFI +// +// All implementing objects should be public to support external types. When a +// type is external we need to import it's FfiConverter. +public interface FfiConverter<KotlinType, FfiType> { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): Int + + // Write a Kotlin type to a `ByteBuffer` + fun write(value: KotlinType, buf: ByteBuffer) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +// FfiConverter that uses `RustBuffer` as the FfiType +public interface FfiConverterRustBuffer<KotlinType>: FfiConverter<KotlinType, RustBuffer.ByValue> { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt new file mode 100644 index 0000000000..eafec5d122 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterFloat: FfiConverter<Float, Float> { + override fun lift(value: Float): Float { + return value + } + + override fun read(buf: ByteBuffer): Float { + return buf.getFloat() + } + + override fun lower(value: Float): Float { + return value + } + + override fun allocationSize(value: Float) = 4 + + override fun write(value: Float, buf: ByteBuffer) { + buf.putFloat(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt new file mode 100644 index 0000000000..9fc2892c95 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterDouble: FfiConverter<Double, Double> { + override fun lift(value: Double): Double { + return value + } + + override fun read(buf: ByteBuffer): Double { + return buf.getDouble() + } + + override fun lower(value: Double): Double { + return value + } + + override fun allocationSize(value: Double) = 8 + + override fun write(value: Double, buf: ByteBuffer) { + buf.putDouble(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt new file mode 100644 index 0000000000..3544b2f9e6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt @@ -0,0 +1,83 @@ +{{ self.add_import("kotlinx.coroutines.CoroutineScope") }} +{{ self.add_import("kotlinx.coroutines.delay") }} +{{ self.add_import("kotlinx.coroutines.isActive") }} +{{ self.add_import("kotlinx.coroutines.launch") }} + +internal const val UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0.toByte() +internal const val UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1.toByte() +internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0.toByte() +internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED = 1.toByte() +internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2.toByte() + +// Callback function to execute a Rust task. The Kotlin code schedules these in a coroutine then +// invokes them. +internal interface UniFfiRustTaskCallback : com.sun.jna.Callback { + fun callback(rustTaskData: Pointer?, statusCode: Byte) +} + +internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback { + fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte { + if (rustTask == null) { + FfiConverterForeignExecutor.drop(handle) + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + } else { + val coroutineScope = FfiConverterForeignExecutor.lift(handle) + if (coroutineScope.isActive) { + val job = coroutineScope.launch { + if (delayMs > 0) { + delay(delayMs.toLong()) + } + rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS) + } + job.invokeOnCompletion { cause -> + if (cause != null) { + rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_CANCELLED) + } + } + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + } else { + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED + } + } + } +} + +public object FfiConverterForeignExecutor: FfiConverter<CoroutineScope, USize> { + internal val handleMap = UniFfiHandleMap<CoroutineScope>() + + internal fun drop(handle: USize) { + handleMap.remove(handle) + } + + internal fun register(lib: _UniFFILib) { + {%- match ci.ffi_foreign_executor_callback_set() %} + {%- when Some with (fn) %} + lib.{{ fn.name() }}(UniFfiForeignExecutorCallback) + {%- when None %} + {#- No foreign executor, we don't set anything #} + {% endmatch %} + } + + // Number of live handles, exposed so we can test the memory management + public fun handleCount() : Int { + return handleMap.size + } + + override fun allocationSize(value: CoroutineScope) = USize.size + + override fun lift(value: USize): CoroutineScope { + return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift") + } + + override fun read(buf: ByteBuffer): CoroutineScope { + return lift(USize.readFromBuffer(buf)) + } + + override fun lower(value: CoroutineScope): USize { + return handleMap.insert(value) + } + + override fun write(value: CoroutineScope, buf: ByteBuffer) { + lower(value).writeToBuffer(buf) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt new file mode 100644 index 0000000000..382a5f7413 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -0,0 +1,161 @@ +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. +// Error runtime. +@Structure.FieldOrder("code", "error_buf") +internal open class RustCallStatus : Structure() { + @JvmField var code: Byte = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + class ByValue: RustCallStatus(), Structure.ByValue + + fun isSuccess(): Boolean { + return code == 0.toByte() + } + + fun isError(): Boolean { + return code == 1.toByte() + } + + fun isPanic(): Boolean { + return code == 2.toByte() + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface CallStatusErrorHandler<E> { + fun lift(error_buf: RustBuffer.ByValue): E; +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + checkCallStatus(errorHandler, status) + return return_value +} + +// Check RustCallStatus and throw an error if the call wasn't successful +private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E>, status: RustCallStatus) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException({{ Type::String.borrow()|lift_fn }}(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); +} + +// IntegerType that matches Rust's `usize` / C's `size_t` +public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) { + // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin. + override fun toByte() = toInt().toByte() + // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed. + @Deprecated("`toInt().toChar()` is deprecated") + override fun toChar() = toInt().toChar() + override fun toShort() = toInt().toShort() + + fun writeToBuffer(buf: ByteBuffer) { + // Make sure we always write usize integers using native byte-order, since they may be + // casted to pointer values + buf.order(ByteOrder.nativeOrder()) + try { + when (Native.SIZE_T_SIZE) { + 4 -> buf.putInt(toInt()) + 8 -> buf.putLong(toLong()) + else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") + } + } finally { + buf.order(ByteOrder.BIG_ENDIAN) + } + } + + companion object { + val size: Int + get() = Native.SIZE_T_SIZE + + fun readFromBuffer(buf: ByteBuffer) : USize { + // Make sure we always read usize integers using native byte-order, since they may be + // casted from pointer values + buf.order(ByteOrder.nativeOrder()) + try { + return when (Native.SIZE_T_SIZE) { + 4 -> USize(buf.getInt().toLong()) + 8 -> USize(buf.getLong()) + else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") + } + } finally { + buf.order(ByteOrder.BIG_ENDIAN) + } + } + } +} + + +// Map handles to objects +// +// This is used when the Rust code expects an opaque pointer to represent some foreign object. +// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an +// object reference , nor does it support leaking a reference to Rust. +// +// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to +// Rust when it needs an opaque pointer. +// +// TODO: refactor callbacks to use this class +internal class UniFfiHandleMap<T: Any> { + private val map = ConcurrentHashMap<USize, T>() + // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible + // values seems like enough. If somehow we generate 4 billion handles, then this will wrap + // around back to zero and we can assume the first handle generated will have been dropped by + // then. + private val counter = java.util.concurrent.atomic.AtomicInteger(0) + + val size: Int + get() = map.size + + fun insert(obj: T): USize { + val handle = USize(counter.getAndAdd(1).toLong()) + map.put(handle, obj) + return handle + } + + fun get(handle: USize): T? { + return map.get(handle) + } + + fun remove(handle: USize): T? { + return map.remove(handle) + } +} + +// FFI type for Rust future continuations +internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback { + fun callback(continuationHandle: USize, pollResult: Short); +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt new file mode 100644 index 0000000000..75564276be --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterShort: FfiConverter<Short, Short> { + override fun lift(value: Short): Short { + return value + } + + override fun read(buf: ByteBuffer): Short { + return buf.getShort() + } + + override fun lower(value: Short): Short { + return value + } + + override fun allocationSize(value: Short) = 2 + + override fun write(value: Short, buf: ByteBuffer) { + buf.putShort(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt new file mode 100644 index 0000000000..b7a8131c8b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterInt: FfiConverter<Int, Int> { + override fun lift(value: Int): Int { + return value + } + + override fun read(buf: ByteBuffer): Int { + return buf.getInt() + } + + override fun lower(value: Int): Int { + return value + } + + override fun allocationSize(value: Int) = 4 + + override fun write(value: Int, buf: ByteBuffer) { + buf.putInt(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt new file mode 100644 index 0000000000..601cfc7c2c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterLong: FfiConverter<Long, Long> { + override fun lift(value: Long): Long { + return value + } + + override fun read(buf: ByteBuffer): Long { + return buf.getLong() + } + + override fun lower(value: Long): Long { + return value + } + + override fun allocationSize(value: Long) = 8 + + override fun write(value: Long, buf: ByteBuffer) { + buf.putLong(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt new file mode 100644 index 0000000000..9237768dbf --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterByte: FfiConverter<Byte, Byte> { + override fun lift(value: Byte): Byte { + return value + } + + override fun read(buf: ByteBuffer): Byte { + return buf.get() + } + + override fun lower(value: Byte): Byte { + return value + } + + override fun allocationSize(value: Byte) = 1 + + override fun write(value: Byte, buf: ByteBuffer) { + buf.put(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt new file mode 100644 index 0000000000..776c402727 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt @@ -0,0 +1,34 @@ +{%- let key_type_name = key_type|type_name(ci) %} +{%- let value_type_name = value_type|type_name(ci) %} +public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_name }}, {{ value_type_name }}>> { + override fun read(buf: ByteBuffer): Map<{{ key_type_name }}, {{ value_type_name }}> { + val len = buf.getInt() + return buildMap<{{ key_type_name }}, {{ value_type_name }}>(len) { + repeat(len) { + val k = {{ key_type|read_fn }}(buf) + val v = {{ value_type|read_fn }}(buf) + this[k] = v + } + } + } + + override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int { + val spaceForMapSize = 4 + val spaceForChildren = value.map { (k, v) -> + {{ key_type|allocation_size_fn }}(k) + + {{ value_type|allocation_size_fn }}(v) + }.sum() + return spaceForMapSize + spaceForChildren + } + + override fun write(value: Map<{{ key_type_name }}, {{ value_type_name }}>, buf: ByteBuffer) { + buf.putInt(value.size) + // The parens on `(k, v)` here ensure we're calling the right method, + // which is important for compatibility with older android devices. + // Ref https://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/ + value.forEach { (k, v) -> + {{ key_type|write_fn }}(k, buf) + {{ value_type|write_fn }}(v, buf) + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt new file mode 100644 index 0000000000..6a3aeada35 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt @@ -0,0 +1,57 @@ +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "{{ config.cdylib_name() }}" +} + +private inline fun <reified Lib : Library> loadIndirect( + componentName: String +): Lib { + return Native.load<Lib>(findLibraryName(componentName), Lib::class.java) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}") + .also { lib: _UniFFILib -> + uniffiCheckContractApiVersion(lib) + uniffiCheckApiChecksums(lib) + {% for fn in self.initialization_fns() -%} + {{ fn }}(lib) + {% endfor -%} + } + } + } + + {% for func in ci.iter_ffi_function_definitions() -%} + fun {{ func.name() }}( + {%- call kt::arg_list_ffi_decl(func) %} + ): {% match func.return_type() %}{% when Some with (return_type) %}{{ return_type.borrow()|ffi_type_name_by_value }}{% when None %}Unit{% endmatch %} + {% endfor %} +} + +private fun uniffiCheckContractApiVersion(lib: _UniFFILib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = {{ ci.uniffi_contract_version() }} + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.{{ ci.ffi_uniffi_contract_version().name() }}() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} + +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: _UniFFILib) { + {%- for (name, expected_checksum) in ci.iter_checksums() %} + if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + {%- endfor %} +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt new file mode 100644 index 0000000000..b9352c690f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt @@ -0,0 +1,161 @@ +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance<Disposable>() + .forEach(Disposable::destroy) + } + } +} + +inline fun <T : Disposable?, R> T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +// The base class for all UniFFI Object types. +// +// This class provides core operations for working with the Rust `Arc<T>` pointer to +// the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an `FFIObject` instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so will +// leak the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// In the future we may be able to replace some of this with automatic finalization logic, such as using +// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is +// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also +// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], +// so there would still be some complexity here). +// +// Sigh...all of this for want of a robust finalization mechanism. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// +abstract class FFIObject( + protected val pointer: Pointer +): Disposable, AutoCloseable { + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt new file mode 100644 index 0000000000..8ce27a5d04 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -0,0 +1,138 @@ +{%- let obj = ci|get_object_definition(name) %} +{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %} +{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }} +{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }} + +public interface {{ type_name }}Interface { + {% for meth in obj.methods() -%} + {%- match meth.throws_type() -%} + {%- when Some with (throwable) -%} + @Throws({{ throwable|type_name(ci) }}::class) + {%- when None -%} + {%- endmatch %} + {% if meth.is_async() -%} + suspend fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) + {%- else -%} + fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) + {%- endif %} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} + {%- when None -%} + {%- endmatch -%} + + {% endfor %} + companion object +} + +class {{ type_name }}( + pointer: Pointer +) : FFIObject(pointer), {{ type_name }}Interface { + + {%- match obj.primary_constructor() %} + {%- when Some with (cons) %} + constructor({% call kt::arg_list_decl(cons) -%}) : + this({% call kt::to_ffi_call(cons) %}) + {%- when None %} + {%- endmatch %} + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status) + } + } + + {% for meth in obj.methods() -%} + {%- match meth.throws_type() -%} + {%- when Some with (throwable) %} + @Throws({{ throwable|type_name(ci) }}::class) + {%- else -%} + {%- endmatch -%} + {%- if meth.is_async() %} + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}( + thisPtr, + {% call kt::arg_list_lowered(meth) %} + ) + }, + {{ meth|async_poll(ci) }}, + {{ meth|async_complete(ci) }}, + {{ meth|async_free(ci) }}, + // lift function + {%- match meth.return_type() %} + {%- when Some(return_type) %} + { {{ return_type|lift_fn }}(it) }, + {%- when None %} + { Unit }, + {% endmatch %} + // Error FFI converter + {%- match meth.throws_type() %} + {%- when Some(e) %} + {{ e|type_name(ci) }}.ErrorHandler, + {%- when None %} + NullCallStatusErrorHandler, + {%- endmatch %} + ) + } + {%- else -%} + {%- match meth.return_type() -%} + {%- when Some with (return_type) -%} + override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} = + callWithPointer { + {%- call kt::to_ffi_call_with_prefix("it", meth) %} + }.let { + {{ return_type|lift_fn }}(it) + } + + {%- when None -%} + override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) = + callWithPointer { + {%- call kt::to_ffi_call_with_prefix("it", meth) %} + } + {% endmatch %} + {% endif %} + {% endfor %} + + {% if !obj.alternate_constructors().is_empty() -%} + companion object { + {% for cons in obj.alternate_constructors() -%} + fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ type_name }} = + {{ type_name }}({% call kt::to_ffi_call(cons) %}) + {% endfor %} + } + {% else %} + companion object + {% endif %} +} + +public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { + override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): {{ type_name }} { + return {{ type_name }}(value) + } + + override fun read(buf: ByteBuffer): {{ type_name }} { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: {{ type_name }}) = 8 + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt new file mode 100644 index 0000000000..56cb5f87a5 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt @@ -0,0 +1,27 @@ +{%- let inner_type_name = inner_type|type_name(ci) %} + +public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_name }}?> { + override fun read(buf: ByteBuffer): {{ inner_type_name }}? { + if (buf.get().toInt() == 0) { + return null + } + return {{ inner_type|read_fn }}(buf) + } + + override fun allocationSize(value: {{ inner_type_name }}?): Int { + if (value == null) { + return 1 + } else { + return 1 + {{ inner_type|allocation_size_fn }}(value) + } + } + + override fun write(value: {{ inner_type_name }}?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + {{ inner_type|write_fn }}(value, buf) + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt new file mode 100644 index 0000000000..b588ca1398 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt @@ -0,0 +1,42 @@ +{%- let rec = ci|get_record_definition(name) %} + +data class {{ type_name }} ( + {%- for field in rec.fields() %} + var {{ field.name()|var_name }}: {{ field|type_name(ci) -}} + {%- match field.default_value() %} + {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }} + {%- else %} + {%- endmatch -%} + {% if !loop.last %}, {% endif %} + {%- endfor %} +) {% if contains_object_references %}: Disposable {% endif %}{ + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + {% call kt::destroy_fields(rec) %} + } + {% endif %} + companion object +} + +public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { + override fun read(buf: ByteBuffer): {{ type_name }} { + return {{ type_name }}( + {%- for field in rec.fields() %} + {{ field|read_fn }}(buf), + {%- endfor %} + ) + } + + override fun allocationSize(value: {{ type_name }}) = ( + {%- for field in rec.fields() %} + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%} + {%- endfor %} + ) + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + {%- for field in rec.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt new file mode 100644 index 0000000000..dfbea24074 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt @@ -0,0 +1,87 @@ +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue: RustBuffer(), Structure.ByValue + class ByReference: RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status) + }.also { + if(it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + } + } + + internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue { + var buf = RustBuffer.ByValue() + buf.capacity = capacity + buf.len = len + buf.data = data + return buf + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } + + /** + * Get a `RustBuffer.ByValue` from this reference. + */ + fun getValue(): RustBuffer.ByValue { + val pointer = getPointer() + val value = RustBuffer.ByValue() + value.writeField("capacity", pointer.getInt(0)) + value.writeField("len", pointer.getInt(4)) + value.writeField("data", pointer.getPointer(8)) + + return value + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt new file mode 100644 index 0000000000..876d1bc05e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt @@ -0,0 +1,23 @@ +{%- let inner_type_name = inner_type|type_name(ci) %} + +public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_type_name }}>> { + override fun read(buf: ByteBuffer): List<{{ inner_type_name }}> { + val len = buf.getInt() + return List<{{ inner_type_name }}>(len) { + {{ inner_type|read_fn }}(buf) + } + } + + override fun allocationSize(value: List<{{ inner_type_name }}>): Int { + val sizeForLength = 4 + val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) { + buf.putInt(value.size) + value.forEach { + {{ inner_type|write_fn }}(it, buf) + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt new file mode 100644 index 0000000000..68324be4f9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt @@ -0,0 +1,53 @@ +public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + fun toUtf8(value: String): ByteBuffer { + // Make sure we don't have invalid UTF-16, check for lone surrogates. + return Charsets.UTF_8.newEncoder().run { + onMalformedInput(CodingErrorAction.REPORT) + encode(CharBuffer.wrap(value)) + } + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteBuf = toUtf8(value) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteBuf.limit()) + rbuf.asByteBuffer()!!.put(byteBuf) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per UTF-16 code unit which will always be + // enough. + override fun allocationSize(value: String): Int { + val sizeForLength = 4 + val sizeForString = value.length * 3 + return sizeForLength + sizeForString + } + + override fun write(value: String, buf: ByteBuffer) { + val byteBuf = toUtf8(value) + buf.putInt(byteBuf.limit()) + buf.put(byteBuf) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt new file mode 100644 index 0000000000..21069d7ce8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt @@ -0,0 +1,38 @@ +public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> { + override fun read(buf: ByteBuffer): java.time.Instant { + val seconds = buf.getLong() + // Type mismatch (should be u32) but we check for overflow/underflow below + val nanoseconds = buf.getInt().toLong() + if (nanoseconds < 0) { + throw java.time.DateTimeException("Instant nanoseconds exceed minimum or maximum supported by uniffi") + } + if (seconds >= 0) { + return java.time.Instant.EPOCH.plus(java.time.Duration.ofSeconds(seconds, nanoseconds)) + } else { + return java.time.Instant.EPOCH.minus(java.time.Duration.ofSeconds(-seconds, nanoseconds)) + } + } + + // 8 bytes for seconds, 4 bytes for nanoseconds + override fun allocationSize(value: java.time.Instant) = 12 + + override fun write(value: java.time.Instant, buf: ByteBuffer) { + var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value) + + var sign = 1 + if (epochOffset.isNegative()) { + sign = -1 + epochOffset = epochOffset.negated() + } + + if (epochOffset.nano < 0) { + // Java docs provide guarantee that nano will always be positive, so this should be impossible + // See: https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html + throw IllegalArgumentException("Invalid timestamp, nano value must be non-negative") + } + + buf.putLong(sign * epochOffset.seconds) + // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK + buf.putInt(epochOffset.nano) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt new file mode 100644 index 0000000000..6a841d3484 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt @@ -0,0 +1,51 @@ +{%- if func.is_async() %} +{%- match func.throws_type() -%} +{%- when Some with (throwable) %} +@Throws({{ throwable|type_name(ci) }}::class) +{%- else -%} +{%- endmatch %} + +@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") +suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { + return uniffiRustCallAsync( + _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}), + {{ func|async_poll(ci) }}, + {{ func|async_complete(ci) }}, + {{ func|async_free(ci) }}, + // lift function + {%- match func.return_type() %} + {%- when Some(return_type) %} + { {{ return_type|lift_fn }}(it) }, + {%- when None %} + { Unit }, + {% endmatch %} + // Error FFI converter + {%- match func.throws_type() %} + {%- when Some(e) %} + {{ e|type_name(ci) }}.ErrorHandler, + {%- when None %} + NullCallStatusErrorHandler, + {%- endmatch %} + ) +} + +{%- else %} +{%- match func.throws_type() -%} +{%- when Some with (throwable) %} +@Throws({{ throwable|type_name(ci) }}::class) +{%- else -%} +{%- endmatch -%} + +{%- match func.return_type() -%} +{%- when Some with (return_type) %} + +fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} { + return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %}) +} +{% when None %} + +fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) = + {% call kt::to_ffi_call(func) %} + +{% endmatch %} +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt new file mode 100644 index 0000000000..103d444ea3 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt @@ -0,0 +1,109 @@ +{%- import "macros.kt" as kt %} + +{%- for type_ in ci.iter_types() %} +{%- let type_name = type_|type_name(ci) %} +{%- let ffi_converter_name = type_|ffi_converter_name %} +{%- let canonical_type_name = type_|canonical_name %} +{%- let contains_object_references = ci.item_contains_object_references(type_) %} + +{# + # Map `Type` instances to an include statement for that type. + # + # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the + # Rust code. + # + # - When adding additional types here, make sure to also add a match arm to that function. + # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + #} +{%- match type_ %} + +{%- when Type::Boolean %} +{%- include "BooleanHelper.kt" %} + +{%- when Type::Int8 %} +{%- include "Int8Helper.kt" %} + +{%- when Type::Int16 %} +{%- include "Int16Helper.kt" %} + +{%- when Type::Int32 %} +{%- include "Int32Helper.kt" %} + +{%- when Type::Int64 %} +{%- include "Int64Helper.kt" %} + +{%- when Type::UInt8 %} +{%- include "UInt8Helper.kt" %} + +{%- when Type::UInt16 %} +{%- include "UInt16Helper.kt" %} + +{%- when Type::UInt32 %} +{%- include "UInt32Helper.kt" %} + +{%- when Type::UInt64 %} +{%- include "UInt64Helper.kt" %} + +{%- when Type::Float32 %} +{%- include "Float32Helper.kt" %} + +{%- when Type::Float64 %} +{%- include "Float64Helper.kt" %} + +{%- when Type::String %} +{%- include "StringHelper.kt" %} + +{%- when Type::Bytes %} +{%- include "ByteArrayHelper.kt" %} + +{%- when Type::Enum { name, module_path } %} +{%- let e = ci.get_enum_definition(name).unwrap() %} +{%- if !ci.is_name_used_as_error(name) %} +{% include "EnumTemplate.kt" %} +{%- else %} +{% include "ErrorTemplate.kt" %} +{%- endif -%} + +{%- when Type::Object { module_path, name, imp } %} +{% include "ObjectTemplate.kt" %} + +{%- when Type::Record { name, module_path } %} +{% include "RecordTemplate.kt" %} + +{%- when Type::Optional { inner_type } %} +{% include "OptionalTemplate.kt" %} + +{%- when Type::Sequence { inner_type } %} +{% include "SequenceTemplate.kt" %} + +{%- when Type::Map { key_type, value_type } %} +{% include "MapTemplate.kt" %} + +{%- when Type::CallbackInterface { module_path, name } %} +{% include "CallbackInterfaceTemplate.kt" %} + +{%- when Type::ForeignExecutor %} +{% include "ForeignExecutorTemplate.kt" %} + +{%- when Type::Timestamp %} +{% include "TimestampHelper.kt" %} + +{%- when Type::Duration %} +{% include "DurationHelper.kt" %} + +{%- when Type::Custom { module_path, name, builtin } %} +{% include "CustomTypeTemplate.kt" %} + +{%- when Type::External { module_path, name, namespace, kind, tagged } %} +{% include "ExternalTypeTemplate.kt" %} + +{%- else %} +{%- endmatch %} +{%- endfor %} + +{%- if ci.has_async_fns() %} +{# Import types needed for async support #} +{{ self.add_import("kotlin.coroutines.resume") }} +{{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }} +{{ self.add_import("kotlinx.coroutines.CancellableContinuation") }} +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt new file mode 100644 index 0000000000..279a8fa91b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterUShort: FfiConverter<UShort, Short> { + override fun lift(value: Short): UShort { + return value.toUShort() + } + + override fun read(buf: ByteBuffer): UShort { + return lift(buf.getShort()) + } + + override fun lower(value: UShort): Short { + return value.toShort() + } + + override fun allocationSize(value: UShort) = 2 + + override fun write(value: UShort, buf: ByteBuffer) { + buf.putShort(value.toShort()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt new file mode 100644 index 0000000000..da7b5b28d6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterUInt: FfiConverter<UInt, Int> { + override fun lift(value: Int): UInt { + return value.toUInt() + } + + override fun read(buf: ByteBuffer): UInt { + return lift(buf.getInt()) + } + + override fun lower(value: UInt): Int { + return value.toInt() + } + + override fun allocationSize(value: UInt) = 4 + + override fun write(value: UInt, buf: ByteBuffer) { + buf.putInt(value.toInt()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt new file mode 100644 index 0000000000..44d27ad36b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterULong: FfiConverter<ULong, Long> { + override fun lift(value: Long): ULong { + return value.toULong() + } + + override fun read(buf: ByteBuffer): ULong { + return lift(buf.getLong()) + } + + override fun lower(value: ULong): Long { + return value.toLong() + } + + override fun allocationSize(value: ULong) = 8 + + override fun write(value: ULong, buf: ByteBuffer) { + buf.putLong(value.toLong()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt new file mode 100644 index 0000000000..b6d176603e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterUByte: FfiConverter<UByte, Byte> { + override fun lift(value: Byte): UByte { + return value.toUByte() + } + + override fun read(buf: ByteBuffer): UByte { + return lift(buf.get()) + } + + override fun lower(value: UByte): Byte { + return value.toByte() + } + + override fun allocationSize(value: UByte) = 1 + + override fun write(value: UByte, buf: ByteBuffer) { + buf.put(value.toByte()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt new file mode 100644 index 0000000000..6a95d6a66d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt @@ -0,0 +1,77 @@ +{# +// Template to call into rust. Used in several places. +// Variable names in `arg_list_decl` should match up with arg lists +// passed to rust via `arg_list_lowered` +#} + +{%- macro to_ffi_call(func) -%} + {%- match func.throws_type() %} + {%- when Some with (e) %} + rustCallWithError({{ e|type_name(ci) }}) + {%- else %} + rustCall() + {%- endmatch %} { _status -> + _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} _status) +} +{%- endmacro -%} + +{%- macro to_ffi_call_with_prefix(prefix, func) %} + {%- match func.throws_type() %} + {%- when Some with (e) %} + rustCallWithError({{ e|type_name(ci) }}) + {%- else %} + rustCall() + {%- endmatch %} { _status -> + _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}( + {{- prefix }}, + {% call arg_list_lowered(func) %} + _status) +} +{%- endmacro %} + +{%- macro arg_list_lowered(func) %} + {%- for arg in func.arguments() %} + {{- arg|lower_fn }}({{ arg.name()|var_name }}), + {%- endfor %} +{%- endmacro -%} + +{#- +// Arglist as used in kotlin declarations of methods, functions and constructors. +// Note the var_name and type_name filters. +-#} + +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() -%} + {{ arg.name()|var_name }}: {{ arg|type_name(ci) }} + {%- match arg.default_value() %} + {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }} + {%- else %} + {%- endmatch %} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} + +{% macro arg_list_protocol(func) %} + {%- for arg in func.arguments() -%} + {{ arg.name()|var_name }}: {{ arg|type_name(ci) }} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} +{#- +// Arglist as used in the _UniFFILib function declarations. +// Note unfiltered name but ffi_type_name filters. +-#} +{%- macro arg_list_ffi_decl(func) %} + {%- for arg in func.arguments() %} + {{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value -}}, + {%- endfor %} + {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %} +{%- endmacro -%} + +// Macro for destroying fields +{%- macro destroy_fields(member) %} + Disposable.destroy( + {%- for field in member.fields() %} + this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%} + {% endfor -%}) +{%- endmacro -%} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt new file mode 100644 index 0000000000..9ee4229018 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt @@ -0,0 +1,57 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package {{ config.package_name() }}; + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the details of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.IntegerType +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.Callback +import com.sun.jna.ptr.* +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.CharBuffer +import java.nio.charset.CodingErrorAction +import java.util.concurrent.ConcurrentHashMap + +{%- for req in self.imports() %} +{{ req.render() }} +{%- endfor %} + +{% include "RustBufferTemplate.kt" %} +{% include "FfiConverterTemplate.kt" %} +{% include "Helpers.kt" %} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +{% include "NamespaceLibraryTemplate.kt" %} + +// Async support +{%- if ci.has_async_fns() %} +{% include "Async.kt" %} +{%- endif %} + +// Public interface members begin here. +{{ type_helper_code }} + +{%- for func in ci.function_definitions() %} +{%- include "TopLevelFunctionTemplate.kt" %} +{%- endfor %} + +{% import "macros.kt" as kt %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs new file mode 100644 index 0000000000..7b78540741 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs @@ -0,0 +1,131 @@ +/* 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 crate::{ + bindings::{RunScriptOptions, TargetLanguage}, + library_mode::generate_bindings, +}; +use anyhow::{bail, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use std::env; +use std::process::Command; +use uniffi_testing::UniFFITestHelper; + +/// Run Kotlin tests for a UniFFI test fixture +pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { + run_script( + tmp_dir, + fixture_name, + script_file, + vec![], + &RunScriptOptions::default(), + ) +} + +/// Run a Kotlin script +/// +/// This function will set things up so that the script can import the UniFFI bindings for a crate +pub fn run_script( + tmp_dir: &str, + crate_name: &str, + script_file: &str, + args: Vec<String>, + options: &RunScriptOptions, +) -> Result<()> { + let script_path = Utf8Path::new(".").join(script_file); + let test_helper = UniFFITestHelper::new(crate_name)?; + let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; + generate_bindings( + &cdylib_path, + None, + &[TargetLanguage::Kotlin], + &out_dir, + false, + )?; + let jar_file = build_jar(crate_name, &out_dir, options)?; + + let mut command = kotlinc_command(options); + command + .arg("-classpath") + .arg(calc_classpath(vec![&out_dir, &jar_file])) + // Enable runtime assertions, for easy testing etc. + .arg("-J-ea") + // Our test scripts should not produce any warnings. + .arg("-Werror") + .arg("-script") + .arg(script_path) + .args(if args.is_empty() { + vec![] + } else { + std::iter::once(String::from("--")).chain(args).collect() + }); + + let status = command + .spawn() + .context("Failed to spawn `kotlinc` to run Kotlin script")? + .wait() + .context("Failed to wait for `kotlinc` when running Kotlin script")?; + if !status.success() { + anyhow::bail!("running `kotlinc` failed") + } + Ok(()) +} + +/// Generate kotlin bindings for the given namespace, then use the kotlin +/// command-line tools to compile them into a .jar file. +fn build_jar( + crate_name: &str, + out_dir: &Utf8Path, + options: &RunScriptOptions, +) -> Result<Utf8PathBuf> { + let mut jar_file = Utf8PathBuf::from(out_dir); + jar_file.push(format!("{crate_name}.jar")); + let sources = glob::glob(out_dir.join("**/*.kt").as_str())? + .flatten() + .map(|p| String::from(p.to_string_lossy())) + .collect::<Vec<String>>(); + if sources.is_empty() { + bail!("No kotlin sources found in {out_dir}") + } + + let mut command = kotlinc_command(options); + command + // Our generated bindings should not produce any warnings; fail tests if they do. + .arg("-Werror") + .arg("-d") + .arg(&jar_file) + .arg("-classpath") + .arg(calc_classpath(vec![])) + .args(sources); + + let status = command + .spawn() + .context("Failed to spawn `kotlinc` to compile the bindings")? + .wait() + .context("Failed to wait for `kotlinc` when compiling the bindings")?; + if !status.success() { + bail!("running `kotlinc` failed") + } + Ok(jar_file) +} + +fn kotlinc_command(options: &RunScriptOptions) -> Command { + let mut command = Command::new("kotlinc"); + if !options.show_compiler_messages { + command.arg("-nowarn"); + } + command +} + +fn calc_classpath(extra_paths: Vec<&Utf8Path>) -> String { + extra_paths + .into_iter() + .map(|p| p.to_string()) + // Add the system classpath as a component, using the fact that env::var returns an Option, + // which implement Iterator + .chain(env::var("CLASSPATH")) + .collect::<Vec<String>>() + .join(":") +} |