diff options
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings')
132 files changed, 3164 insertions, 1979 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 index e20020e87c..ae4bffc973 100644 --- 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 @@ -26,6 +26,6 @@ impl CodeType for CallbackInterfaceCodeType { } fn initialization_fn(&self) -> Option<String> { - Some(format!("{}.register", self.ffi_converter_name())) + Some(format!("uniffiCallbackInterface{}.register", self.id)) } } 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 index 4329f32f4c..8d075bbedb 100644 --- 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 @@ -5,55 +5,81 @@ 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(), +#[derive(Debug)] +pub struct OptionalCodeType { + inner: Type, +} - // For optionals - _ => super::KotlinCodeOracle.find(inner).literal(literal, ci), +impl OptionalCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } + fn inner(&self) -> &Type { + &self.inner } } -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 CodeType for OptionalCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + format!( + "{}?", + super::KotlinCodeOracle.find(self.inner()).type_label(ci) + ) + } + + fn canonical_name(&self) -> String { + format!( + "Optional{}", + super::KotlinCodeOracle.find(self.inner()).canonical_name() + ) + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + match literal { + Literal::None => "null".into(), + Literal::Some { inner } => super::KotlinCodeOracle.find(&self.inner).literal(inner, ci), + _ => panic!("Invalid literal for Optional type: {literal:?}"), } } - } +} -impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}"); -impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}"); +#[derive(Debug)] +pub struct SequenceCodeType { + inner: Type, +} + +impl SequenceCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } + fn inner(&self) -> &Type { + &self.inner + } +} + +impl CodeType for SequenceCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + format!( + "List<{}>", + super::KotlinCodeOracle.find(self.inner()).type_label(ci) + ) + } + + fn canonical_name(&self) -> String { + format!( + "Sequence{}", + super::KotlinCodeOracle.find(self.inner()).canonical_name() + ) + } + + fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String { + match literal { + Literal::EmptySequence => "listOf()".into(), + _ => panic!("Invalid literal for List type: {literal:?}"), + } + } +} #[derive(Debug)] pub struct MapCodeType { @@ -92,7 +118,10 @@ impl CodeType for MapCodeType { ) } - fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { - render_literal(literal, &self.value, ci) + fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String { + match literal { + Literal::EmptyMap => "mapOf()".into(), + _ => panic!("Invalid literal for Map type: {literal:?}"), + } } } 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 deleted file mode 100644 index 154e12a381..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs +++ /dev/null @@ -1,24 +0,0 @@ -/* 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 index 3ecf09d47f..d55c78f760 100644 --- 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 @@ -17,8 +17,8 @@ impl ExternalCodeType { } impl CodeType for ExternalCodeType { - fn type_label(&self, _ci: &ComponentInterface) -> String { - self.name.clone() + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.name) } fn canonical_name(&self) -> String { 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 index 1ed0575a9a..c4fc8e0ed6 100644 --- 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 @@ -7,20 +7,21 @@ use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use askama::Template; +use camino::Utf8Path; use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use crate::backend::TemplateExpression; +use crate::bindings::kotlin; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; mod callback_interface; mod compounds; mod custom; mod enum_; -mod executor; mod external; mod miscellany; mod object; @@ -28,6 +29,28 @@ mod primitives; mod record; mod variant; +pub struct KotlinBindingGenerator; +impl BindingGenerator for KotlinBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + kotlin::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + if cdylib_name.is_none() { + bail!("Generate bindings for Kotlin requires a cdylib, but {library_path} was given"); + } + Ok(()) + } +} + trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in /// method signatures and property declarations. @@ -73,10 +96,21 @@ trait CodeType: Debug { pub struct Config { package_name: Option<String>, cdylib_name: Option<String>, + generate_immutable_records: Option<bool>, #[serde(default)] custom_types: HashMap<String, CustomTypeConfig>, #[serde(default)] external_packages: HashMap<String, String>, + #[serde(default)] + android: bool, + #[serde(default)] + android_cleaner: Option<bool>, +} + +impl Config { + pub(crate) fn android_cleaner(&self) -> bool { + self.android_cleaner.unwrap_or(self.android) + } } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -103,6 +137,11 @@ impl Config { "uniffi".into() } } + + /// Whether to generate immutable records (`val` instead of `var`) + pub fn generate_immutable_records(&self) -> bool { + self.generate_immutable_records.unwrap_or(false) + } } impl BindingsConfig for Config { @@ -236,7 +275,6 @@ pub struct KotlinWrapper<'a> { ci: &'a ComponentInterface, type_helper_code: String, type_imports: BTreeSet<ImportRequirement>, - has_async_fns: bool, } impl<'a> KotlinWrapper<'a> { @@ -249,7 +287,6 @@ impl<'a> KotlinWrapper<'a> { ci, type_helper_code, type_imports, - has_async_fns: ci.has_async_fns(), } } @@ -258,10 +295,6 @@ impl<'a> KotlinWrapper<'a> { .iter_types() .map(|t| KotlinCodeOracle.find(t)) .filter_map(|ct| ct.initialization_fn()) - .chain( - self.has_async_fns - .then(|| "uniffiRustFutureContinuationCallback.register".into()), - ) .collect() } @@ -301,7 +334,12 @@ impl KotlinCodeOracle { /// Get the idiomatic Kotlin rendering of a variable name. fn var_name(&self, nm: &str) -> String { - format!("`{}`", nm.to_string().to_lower_camel_case()) + format!("`{}`", self.var_name_raw(nm)) + } + + /// `var_name` without the backticks. Useful for using in `@Structure.FieldOrder`. + pub fn var_name_raw(&self, nm: &str) -> String { + nm.to_string().to_lower_camel_case() } /// Get the idiomatic Kotlin rendering of an individual enum variant. @@ -309,14 +347,78 @@ impl KotlinCodeOracle { nm.to_string().to_shouty_snake_case() } - fn ffi_type_label_by_value(ffi_type: &FfiType) -> String { + /// Get the idiomatic Kotlin rendering of an FFI callback function name + fn ffi_callback_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + /// Get the idiomatic Kotlin rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + fn ffi_type_label_by_value(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type)), + FfiType::Struct(name) => format!("{}.UniffiByValue", self.ffi_struct_name(name)), + _ => self.ffi_type_label(ffi_type), + } + } + + /// FFI type name to use inside structs + /// + /// The main requirement here is that all types must have default values or else the struct + /// won't work in some JNA contexts. + fn ffi_type_label_for_ffi_struct(&self, ffi_type: &FfiType) -> String { + match ffi_type { + // Make callbacks function pointers nullable. This matches the semantics of a C + // function pointer better and allows for `null` as a default value. + FfiType::Callback(name) => format!("{}?", self.ffi_callback_name(name)), + _ => self.ffi_type_label_by_value(ffi_type), + } + } + + /// Default values for FFI + /// + /// This is used to: + /// - Set a default return value for error results + /// - Set a default for structs, which JNA sometimes requires + fn ffi_default_value(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::UInt8 | FfiType::Int8 => "0.toByte()".to_owned(), + FfiType::UInt16 | FfiType::Int16 => "0.toShort()".to_owned(), + FfiType::UInt32 | FfiType::Int32 => "0".to_owned(), + FfiType::UInt64 | FfiType::Int64 => "0.toLong()".to_owned(), + FfiType::Float32 => "0.0f".to_owned(), + FfiType::Float64 => "0.0".to_owned(), + FfiType::RustArcPtr(_) => "Pointer.NULL".to_owned(), + FfiType::RustBuffer(_) => "RustBuffer.ByValue()".to_owned(), + FfiType::Callback(_) => "null".to_owned(), + FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue()".to_owned(), + _ => unimplemented!("ffi_default_value: {ffi_type:?}"), + } + } + + fn ffi_type_label_by_reference(&self, ffi_type: &FfiType) -> String { match ffi_type { - FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)), - _ => Self::ffi_type_label(ffi_type), + FfiType::Int8 + | FfiType::UInt8 + | FfiType::Int16 + | FfiType::UInt16 + | FfiType::Int32 + | FfiType::UInt32 + | FfiType::Int64 + | FfiType::UInt64 + | FfiType::Float32 + | FfiType::Float64 => format!("{}ByReference", self.ffi_type_label(ffi_type)), + FfiType::RustArcPtr(_) => "PointerByReference".to_owned(), + // JNA structs default to ByReference + FfiType::RustBuffer(_) | FfiType::Struct(_) => self.ffi_type_label(ffi_type), + _ => panic!("{ffi_type:?} by reference is not implemented"), } } - fn ffi_type_label(ffi_type: &FfiType) -> String { + fn ffi_type_label(&self, 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 @@ -327,19 +429,35 @@ impl KotlinCodeOracle { FfiType::Int64 | FfiType::UInt64 => "Long".to_string(), FfiType::Float32 => "Float".to_string(), FfiType::Float64 => "Double".to_string(), + FfiType::Handle => "Long".to_string(), FfiType::RustArcPtr(_) => "Pointer".to_string(), FfiType::RustBuffer(maybe_suffix) => { format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) } + FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue".to_string(), 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(), + FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_name(name), + FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner), + FfiType::VoidPointer => "Pointer".to_string(), + } + } + + /// Get the name of the interface and class name for an object. + /// + /// If we support callback interfaces, the interface name is the object name, and the class name is derived from that. + /// Otherwise, the class name is the object name and the interface name is derived from that. + /// + /// This split determines what types `FfiConverter.lower()` inputs. If we support callback + /// interfaces, `lower` must lower anything that implements the interface. If not, then lower + /// only lowers the concrete class. + fn object_names(&self, ci: &ComponentInterface, obj: &Object) -> (String, String) { + let class_name = self.class_name(ci, obj.name()); + if obj.has_callback_interface() { + let impl_name = format!("{class_name}Impl"); + (class_name, impl_name) + } else { + (format!("{class_name}Interface"), class_name) } } } @@ -376,12 +494,11 @@ impl<T: AsType> AsCodeType for T { 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::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)), Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } - Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) } @@ -401,6 +518,7 @@ impl<T: AsType> AsCodeType for T { mod filters { use super::*; pub use crate::backend::filters::*; + use uniffi_meta::LiteralMetadata; pub(super) fn type_name( as_ct: &impl AsCodeType, @@ -454,8 +572,52 @@ mod filters { Ok(as_ct.as_codetype().literal(literal, ci)) } + // Get the idiomatic Kotlin rendering of an integer. + fn int_literal(t: &Option<Type>, base10: String) -> Result<String, askama::Error> { + if let Some(t) = t { + match t { + Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => Ok(base10), + Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => Ok(base10 + "u"), + _ => Err(askama::Error::Custom(Box::new(UniFFIError::new( + "Only ints are supported.".to_string(), + )))), + } + } else { + Err(askama::Error::Custom(Box::new(UniFFIError::new( + "Enum hasn't defined a repr".to_string(), + )))) + } + } + + // Get the idiomatic Kotlin rendering of an individual enum variant's discriminant + pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> { + let literal = e.variant_discr(*index).expect("invalid index"); + match literal { + // Kotlin doesn't convert between signed and unsigned by default + // so we'll need to make sure we define the type as appropriately + LiteralMetadata::UInt(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()), + LiteralMetadata::Int(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()), + _ => Err(askama::Error::Custom(Box::new(UniFFIError::new( + "Only ints are supported.".to_string(), + )))), + } + } + pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> { - Ok(KotlinCodeOracle::ffi_type_label_by_value(type_)) + Ok(KotlinCodeOracle.ffi_type_label_by_value(type_)) + } + + pub fn ffi_type_name_for_ffi_struct(type_: &FfiType) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.ffi_type_label_for_ffi_struct(type_)) + } + + pub fn ffi_default_value(type_: FfiType) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.ffi_default_value(&type_)) + } + + /// Get the idiomatic Kotlin rendering of a function name. + pub fn class_name(nm: &str, ci: &ComponentInterface) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.class_name(ci, nm)) } /// Get the idiomatic Kotlin rendering of a function name. @@ -468,6 +630,11 @@ mod filters { Ok(KotlinCodeOracle.var_name(nm)) } + /// Get the idiomatic Kotlin rendering of a variable name. + pub fn var_name_raw(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.var_name_raw(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())) @@ -478,13 +645,30 @@ mod filters { Ok(KotlinCodeOracle.convert_error_suffix(&name)) } + /// Get the idiomatic Kotlin rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.ffi_callback_name(nm)) + } + + /// Get the idiomatic Kotlin rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.ffi_struct_name(nm)) + } + + pub fn object_names( + obj: &Object, + ci: &ComponentInterface, + ) -> Result<(String, String), askama::Error> { + Ok(KotlinCodeOracle.object_names(ci, obj)) + } + 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) }}" + "{{ future, callback, continuation -> UniffiLib.INSTANCE.{ffi_func}(future, callback, continuation) }}" )) } @@ -493,7 +677,7 @@ mod filters { 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 = format!("UniffiLib.INSTANCE.{ffi_func}(future, continuation)"); let call = match callable.return_type() { Some(Type::External { kind: ExternalKind::DataClass, @@ -502,7 +686,7 @@ mod filters { }) => { // 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) }}") + format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity.toULong(), it.len.toULong(), it.data) }}") } _ => call, }; @@ -515,7 +699,7 @@ mod filters { ) -> Result<String, askama::Error> { let ffi_func = callable.ffi_rust_future_free(ci); Ok(format!( - "{{ future -> _UniFFILib.INSTANCE.{ffi_func}(future) }}" + "{{ future -> UniffiLib.INSTANCE.{ffi_func}(future) }}" )) } @@ -527,4 +711,13 @@ mod filters { pub fn unquote(nm: &str) -> Result<String, askama::Error> { Ok(nm.trim_matches('`').to_string()) } + + /// Get the idiomatic Kotlin rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> { + let middle = textwrap::indent(&textwrap::dedent(docstring), " * "); + let wrapped = format!("/**\n{middle}\n */"); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } } 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 index c39ae59cce..5a4305d14a 100644 --- 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 @@ -3,25 +3,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::CodeType; -use crate::ComponentInterface; +use crate::{interface::ObjectImpl, ComponentInterface}; #[derive(Debug)] pub struct ObjectCodeType { - id: String, + name: String, + imp: ObjectImpl, } impl ObjectCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(name: String, imp: ObjectImpl) -> Self { + Self { name, imp } } } impl CodeType for ObjectCodeType { fn type_label(&self, ci: &ComponentInterface) -> String { - super::KotlinCodeOracle.class_name(ci, &self.id) + super::KotlinCodeOracle.class_name(ci, &self.name) } fn canonical_name(&self) -> String { - format!("Type{}", self.id) + format!("Type{}", self.name) + } + + fn initialization_fn(&self) -> Option<String> { + self.imp + .has_callback_interface() + .then(|| format!("uniffiCallbackInterface{}.register", self.name)) } } 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 index 22495fa209..0bc5a5d99e 100644 --- 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 @@ -9,7 +9,11 @@ use paste::paste; fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String { fn typed_number(type_: &Type, num_str: String) -> String { - match type_ { + let unwrapped_type = match type_ { + Type::Optional { inner_type } => inner_type, + t => t, + }; + match unwrapped_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"), @@ -19,7 +23,7 @@ fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String { Type::Float32 => format!("{num_str}f"), Type::Float64 => num_str, - _ => panic!("Unexpected literal: {num_str} is not a number"), + _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"), } } @@ -56,7 +60,7 @@ macro_rules! impl_code_type_for_primitive { impl CodeType for $T { fn type_label(&self, _ci: &ComponentInterface) -> String { - $class_name.into() + format!("kotlin.{}", $class_name) } fn canonical_name(&self) -> String { 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 index c6a32655f2..b28fbd2c80 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -1,44 +1,117 @@ // 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 const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte() +internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte() -internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>() +internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>() // 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 object uniffiRustFutureContinuationCallbackImpl: UniffiRustFutureContinuationCallback { + override fun callback(data: Long, pollResult: Byte) { + uniffiContinuationHandleMap.remove(data).resume(pollResult) } } internal suspend fun<T, F, E: Exception> uniffiRustCallAsync( - rustFuture: Pointer, - pollFunc: (Pointer, USize) -> Unit, - completeFunc: (Pointer, RustCallStatus) -> F, - freeFunc: (Pointer) -> Unit, + rustFuture: Long, + pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit, + completeFunc: (Long, UniffiRustCallStatus) -> F, + freeFunc: (Long) -> Unit, liftFunc: (F) -> T, - errorHandler: CallStatusErrorHandler<E> + errorHandler: UniffiRustCallStatusErrorHandler<E> ): T { try { do { - val pollResult = suspendCancellableCoroutine<Short> { continuation -> + val pollResult = suspendCancellableCoroutine<Byte> { continuation -> pollFunc( rustFuture, + uniffiRustFutureContinuationCallbackImpl, uniffiContinuationHandleMap.insert(continuation) ) } } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY); return liftFunc( - rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }) + uniffiRustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }) ) } finally { freeFunc(rustFuture) } } +{%- if ci.has_async_callback_interface_definition() %} +internal inline fun<T> uniffiTraitInterfaceCallAsync( + crossinline makeCall: suspend () -> T, + crossinline handleSuccess: (T) -> Unit, + crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit, +): UniffiForeignFuture { + // Using `GlobalScope` is labeled as a "delicate API" and generally discouraged in Kotlin programs, since it breaks structured concurrency. + // However, our parent task is a Rust future, so we're going to need to break structure concurrency in any case. + // + // Uniffi does its best to support structured concurrency across the FFI. + // If the Rust future is dropped, `uniffiForeignFutureFreeImpl` is called, which will cancel the Kotlin coroutine if it's still running. + @OptIn(DelicateCoroutinesApi::class) + val job = GlobalScope.launch { + try { + handleSuccess(makeCall()) + } catch(e: Exception) { + handleError( + UniffiRustCallStatus.create( + UNIFFI_CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(e.toString()), + ) + ) + } + } + val handle = uniffiForeignFutureHandleMap.insert(job) + return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl) +} + +internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallAsyncWithError( + crossinline makeCall: suspend () -> T, + crossinline handleSuccess: (T) -> Unit, + crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit, + crossinline lowerError: (E) -> RustBuffer.ByValue, +): UniffiForeignFuture { + // See uniffiTraitInterfaceCallAsync for details on `DelicateCoroutinesApi` + @OptIn(DelicateCoroutinesApi::class) + val job = GlobalScope.launch { + try { + handleSuccess(makeCall()) + } catch(e: Exception) { + if (e is E) { + handleError( + UniffiRustCallStatus.create( + UNIFFI_CALL_ERROR, + lowerError(e), + ) + ) + } else { + handleError( + UniffiRustCallStatus.create( + UNIFFI_CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(e.toString()), + ) + ) + } + } + } + val handle = uniffiForeignFutureHandleMap.insert(job) + return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl) +} + +internal val uniffiForeignFutureHandleMap = UniffiHandleMap<Job>() + +internal object uniffiForeignFutureFreeImpl: UniffiForeignFutureFree { + override fun callback(handle: Long) { + val job = uniffiForeignFutureHandleMap.remove(handle) + if (!job.isCompleted) { + job.cancel() + } + } +} + +// For testing +public fun uniffiForeignFutureHandleCount() = uniffiForeignFutureHandleMap.size + +{%- endif %} 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 index 8cfa2ce000..c6b266066d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt @@ -11,7 +11,7 @@ public object FfiConverterBoolean: FfiConverter<Boolean, Byte> { return if (value) 1.toByte() else 0.toByte() } - override fun allocationSize(value: Boolean) = 1 + override fun allocationSize(value: Boolean) = 1UL 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 index 4840a199b4..c9449069e2 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt @@ -5,8 +5,8 @@ public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> { buf.get(byteArr) return byteArr } - override fun allocationSize(value: ByteArray): Int { - return 4 + value.size + override fun allocationSize(value: ByteArray): ULong { + return 4UL + value.size.toULong() } override fun write(value: ByteArray, buf: ByteBuffer) { buf.putInt(value.size) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt new file mode 100644 index 0000000000..30a39d9afb --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt @@ -0,0 +1,117 @@ +{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %} + +{%- let trait_impl=format!("uniffiCallbackInterface{}", name) %} + +// Put the implementation in an object so we don't pollute the top-level namespace +internal object {{ trait_impl }} { + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + internal object {{ meth.name()|var_name }}: {{ ffi_callback.name()|ffi_callback_name }} { + override fun callback( + {%- for arg in ffi_callback.arguments() -%} + {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }}, + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() -%} + uniffiCallStatus: UniffiRustCallStatus, + {%- endif -%} + ) + {%- match ffi_callback.return_type() %} + {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }}, + {%- when None %} + {%- endmatch %} { + val uniffiObj = {{ ffi_converter_name }}.handleMap.get(uniffiHandle) + val makeCall = {% if meth.is_async() %}suspend {% endif %}{ -> + uniffiObj.{{ meth.name()|fn_name() }}( + {%- for arg in meth.arguments() %} + {{ arg|lift_fn }}({{ arg.name()|var_name }}), + {%- endfor %} + ) + } + {%- if !meth.is_async() %} + + {%- match meth.return_type() %} + {%- when Some(return_type) %} + val writeReturn = { value: {{ return_type|type_name(ci) }} -> uniffiOutReturn.setValue({{ return_type|lower_fn }}(value)) } + {%- when None %} + val writeReturn = { _: Unit -> Unit } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) + {%- when Some(error_type) %} + uniffiTraitInterfaceCallWithError( + uniffiCallStatus, + makeCall, + writeReturn, + { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) } + ) + {%- endmatch %} + + {%- else %} + val uniffiHandleSuccess = { {% if meth.return_type().is_some() %}returnValue{% else %}_{% endif %}: {% match meth.return_type() %}{%- when Some(return_type) %}{{ return_type|type_name(ci) }}{%- when None %}Unit{% endmatch %} -> + val uniffiResult = {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ return_type|lower_fn }}(returnValue), + {%- when None %} + {%- endmatch %} + UniffiRustCallStatus.ByValue() + ) + uniffiResult.write() + uniffiFutureCallback.callback(uniffiCallbackData, uniffiResult) + } + val uniffiHandleError = { callStatus: UniffiRustCallStatus.ByValue -> + uniffiFutureCallback.callback( + uniffiCallbackData, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ return_type.into()|ffi_default_value }}, + {%- when None %} + {%- endmatch %} + callStatus, + ), + ) + } + + uniffiOutReturn.uniffiSetValue( + {%- match meth.throws_type() %} + {%- when None %} + uniffiTraitInterfaceCallAsync( + makeCall, + uniffiHandleSuccess, + uniffiHandleError + ) + {%- when Some(error_type) %} + uniffiTraitInterfaceCallAsyncWithError( + makeCall, + uniffiHandleSuccess, + uniffiHandleError, + { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) } + ) + {%- endmatch %} + ) + {%- endif %} + } + } + {%- endfor %} + + internal object uniffiFree: {{ "CallbackInterfaceFree"|ffi_callback_name }} { + override fun callback(handle: Long) { + {{ ffi_converter_name }}.handleMap.remove(handle) + } + } + + internal var vtable = {{ vtable|ffi_type_name_by_value }}( + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + {{ meth.name()|var_name() }}, + {%- endfor %} + uniffiFree, + ) + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal fun register(lib: UniffiLib) { + lib.{{ ffi_init_callback.name() }}(vtable) + } +} 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 index 62a71e02f1..d58a651e24 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt @@ -1,43 +1,3 @@ -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 @@ -46,31 +6,22 @@ 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) +public abstract class FfiConverterCallbackInterface<CallbackInterface: Any>: FfiConverter<CallbackInterface, Long> { + internal val handleMap = UniffiHandleMap<CallbackInterface>() - fun drop(handle: Handle): RustBuffer.ByValue { - return handleMap.remove(handle).let { RustBuffer.ByValue() } + internal fun drop(handle: Long) { + handleMap.remove(handle) } - override fun lift(value: Handle): CallbackInterface { - return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + override fun lift(value: Long): CallbackInterface { + return handleMap.get(value) } 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 lower(value: CallbackInterface) = handleMap.insert(value) - override fun allocationSize(value: CallbackInterface) = 8 + override fun allocationSize(value: CallbackInterface) = 8UL 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 index 5a29f0acc3..d2cdee4f33 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -1,129 +1,13 @@ {%- 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) - } - } -} +{%- let ffi_init_callback = cbi.ffi_init_callback() %} +{%- let interface_name = cbi|type_name(ci) %} +{%- let interface_docstring = cbi.docstring() %} +{%- let methods = cbi.methods() %} +{%- let vtable = cbi.vtable() %} +{%- let vtable_methods = cbi.vtable_methods() %} + +{% include "Interface.kt" %} +{% include "CallbackInterfaceImpl.kt" %} + +// The ffiConverter which transforms the Callbacks in to handles to pass to Rust. +public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ interface_name }}>() 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 index 04150c5d78..aeb5f58002 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt @@ -49,7 +49,7 @@ public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_nam return {{ config.into_custom.render("builtinValue") }} } - override fun allocationSize(value: {{ name }}): Int { + override fun allocationSize(value: {{ name }}): ULong { val builtinValue = {{ config.from_custom.render("value") }} return {{ builtin|allocation_size_fn }}(builtinValue) } 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 index 4237c6f9a8..62e02607f3 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt @@ -14,7 +14,7 @@ public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> { } // 8 bytes for seconds, 4 bytes for nanoseconds - override fun allocationSize(value: java.time.Duration) = 12 + override fun allocationSize(value: java.time.Duration) = 12UL override fun write(value: java.time.Duration, buf: ByteBuffer) { if (value.seconds < 0) { 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 index d4c4a1684a..8d1c2235ec 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt @@ -7,12 +7,25 @@ {%- if e.is_flat() %} +{%- call kt::docstring(e, 0) %} +{% match e.variant_discr_type() %} +{% when None %} enum class {{ type_name }} { {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} {{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %} {%- endfor %} companion object } +{% when Some with (variant_discr_type) %} +enum class {{ type_name }}(val value: {{ variant_discr_type|type_name(ci) }}) { + {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} + {{ variant|variant_name }}({{ e|variant_discr_literal(loop.index0) }}){% if loop.last %};{% else %},{% endif %} + {%- endfor %} + companion object +} +{% endmatch %} public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { override fun read(buf: ByteBuffer) = try { @@ -21,7 +34,7 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }} throw RuntimeException("invalid enum value, something is very wrong!!", e) } - override fun allocationSize(value: {{ type_name }}) = 4 + override fun allocationSize(value: {{ type_name }}) = 4UL override fun write(value: {{ type_name }}, buf: ByteBuffer) { buf.putInt(value.ordinal + 1) @@ -30,15 +43,18 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }} {% else %} +{%- call kt::docstring(e, 0) %} sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} { {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} {% 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 -%} + {%- for field in variant.fields() -%} + {%- call kt::docstring(field, 8) %} + val {% call kt::field_name(field, loop.index) %}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} + {%- endfor -%} ) : {{ type_name }}() { companion object } @@ -83,9 +99,9 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } 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 + 4UL {%- for field in variant.fields() %} - + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) + + {{ field|allocation_size_fn }}(value.{%- call kt::field_name(field, loop.index) -%}) {%- endfor %} ) } @@ -98,7 +114,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } 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) + {{ field|write_fn }}(value.{%- call kt::field_name(field, loop.index) -%}, buf) {%- endfor %} Unit } 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 index 986db5424d..4760c03fd6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt @@ -3,24 +3,26 @@ {%- let canonical_type_name = type_|canonical_name %} {% if e.is_flat() %} +{%- call kt::docstring(e, 0) %} 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() -%} + {%- call kt::docstring(variant, 4) %} class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message) {% endfor %} - companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> { override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf) } } {%- else %} +{%- call kt::docstring(e, 0) %} sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} { - // Each variant is a nested class {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} {%- let variant_name = variant|error_variant_name %} class {{ variant_name }}( {% for field in variant.fields() -%} + {%- call kt::docstring(field, 8) %} val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} {% endfor -%} ) : {{ type_name }}() { @@ -29,7 +31,7 @@ sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Di } {% endfor %} - companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> { override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf) } @@ -76,15 +78,15 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } {%- endif %} } - override fun allocationSize(value: {{ type_name }}): Int { + override fun allocationSize(value: {{ type_name }}): ULong { {%- if e.is_flat() %} - return 4 + return 4UL {%- 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 + 4UL {%- for field in variant.fields() %} + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) {%- endfor %} 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 index 0fade7a0bc..b7e77f0b2d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt @@ -1,5 +1,5 @@ {%- let package_name=self.external_type_package_name(module_path, namespace) %} -{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %} +{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name|class_name(ci)) %} {%- 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) %} 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 index 3b2c9d225a..0de90b9c4b 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt @@ -20,7 +20,7 @@ public interface FfiConverter<KotlinType, FfiType> { // 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 + fun allocationSize(value: KotlinType): ULong // Write a Kotlin type to a `ByteBuffer` fun write(value: KotlinType, buf: ByteBuffer) @@ -34,11 +34,11 @@ public interface FfiConverter<KotlinType, FfiType> { fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { val rbuf = RustBuffer.alloc(allocationSize(value)) try { - val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also { + val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity).also { it.order(ByteOrder.BIG_ENDIAN) } write(value, bbuf) - rbuf.writeField("len", bbuf.position()) + rbuf.writeField("len", bbuf.position().toLong()) return rbuf } catch (e: Throwable) { RustBuffer.free(rbuf) 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 index eafec5d122..be91ac8fcb 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterFloat: FfiConverter<Float, Float> { return value } - override fun allocationSize(value: Float) = 4 + override fun allocationSize(value: Float) = 4UL 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 index 9fc2892c95..5eb465f0df 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterDouble: FfiConverter<Double, Double> { return value } - override fun allocationSize(value: Double) = 8 + override fun allocationSize(value: Double) = 8UL 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 deleted file mode 100644 index 3544b2f9e6..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt +++ /dev/null @@ -1,83 +0,0 @@ -{{ 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/HandleMap.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt new file mode 100644 index 0000000000..3a56648190 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt @@ -0,0 +1,27 @@ +// Map handles to objects +// +// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. +internal class UniffiHandleMap<T: Any> { + private val map = ConcurrentHashMap<Long, T>() + private val counter = java.util.concurrent.atomic.AtomicLong(0) + + val size: Int + get() = map.size + + // Insert a new object into the handle map and get a handle for it + fun insert(obj: T): Long { + val handle = counter.getAndAdd(1) + map.put(handle, obj) + return handle + } + + // Get an object from the handle map + fun get(handle: Long): T { + return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") + } + + // Remove an entry from the handlemap and get the Kotlin object back + fun remove(handle: Long): T { + return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") + } +} 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 index 382a5f7413..1fdbd3ffc0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -1,30 +1,43 @@ // 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. + +internal const val UNIFFI_CALL_SUCCESS = 0.toByte() +internal const val UNIFFI_CALL_ERROR = 1.toByte() +internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() + @Structure.FieldOrder("code", "error_buf") -internal open class RustCallStatus : Structure() { +internal open class UniffiRustCallStatus : Structure() { @JvmField var code: Byte = 0 @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - class ByValue: RustCallStatus(), Structure.ByValue + class ByValue: UniffiRustCallStatus(), Structure.ByValue fun isSuccess(): Boolean { - return code == 0.toByte() + return code == UNIFFI_CALL_SUCCESS } fun isError(): Boolean { - return code == 1.toByte() + return code == UNIFFI_CALL_ERROR } fun isPanic(): Boolean { - return code == 2.toByte() + return code == UNIFFI_CALL_UNEXPECTED_ERROR + } + + companion object { + fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue { + val callStatus = UniffiRustCallStatus.ByValue() + callStatus.code = code + callStatus.error_buf = errorBuf + return callStatus + } } } 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> { +interface UniffiRustCallStatusErrorHandler<E> { fun lift(error_buf: RustBuffer.ByValue): E; } @@ -33,15 +46,15 @@ interface CallStatusErrorHandler<E> { // 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(); +private inline fun <U, E: Exception> uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler<E>, callback: (UniffiRustCallStatus) -> U): U { + var status = UniffiRustCallStatus(); val return_value = callback(status) - checkCallStatus(errorHandler, status) + uniffiCheckCallStatus(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) { +// Check UniffiRustCallStatus and throw an error if the call wasn't successful +private fun<E: Exception> uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler<E>, status: UniffiRustCallStatus) { if (status.isSuccess()) { return } else if (status.isError()) { @@ -60,8 +73,8 @@ private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E } } -// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR -object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> { +// UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler<InternalException> { override fun lift(error_buf: RustBuffer.ByValue): InternalException { RustBuffer.free(error_buf) return InternalException("Unexpected CALL_ERROR") @@ -69,93 +82,38 @@ object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> { } // Call a rust function that returns a plain value -private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); +private inline fun <U> uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U { + return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, 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) - } - } +internal inline fun<T> uniffiTraitInterfaceCall( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, +) { + try { + writeReturn(makeCall()) + } catch(e: Exception) { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString()) } } - -// 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) +internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallWithError( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, + lowerError: (E) -> RustBuffer.ByValue +) { + try { + writeReturn(makeCall()) + } catch(e: Exception) { + if (e is E) { + callStatus.code = UNIFFI_CALL_ERROR + callStatus.error_buf = lowerError(e) + } else { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString()) + } } } - -// 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 index 75564276be..de8296fff6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterShort: FfiConverter<Short, Short> { return value } - override fun allocationSize(value: Short) = 2 + override fun allocationSize(value: Short) = 2UL 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 index b7a8131c8b..171809a9c4 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterInt: FfiConverter<Int, Int> { return value } - override fun allocationSize(value: Int) = 4 + override fun allocationSize(value: Int) = 4UL 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 index 601cfc7c2c..35cf8f3169 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterLong: FfiConverter<Long, Long> { return value } - override fun allocationSize(value: Long) = 8 + override fun allocationSize(value: Long) = 8UL 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 index 9237768dbf..27c98a6659 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterByte: FfiConverter<Byte, Byte> { return value } - override fun allocationSize(value: Byte) = 1 + override fun allocationSize(value: Byte) = 1UL override fun write(value: Byte, buf: ByteBuffer) { buf.put(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt new file mode 100644 index 0000000000..0b4249fb11 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt @@ -0,0 +1,14 @@ +{%- call kt::docstring_value(interface_docstring, 0) %} +public interface {{ interface_name }} { + {% for meth in methods.iter() -%} + {%- call kt::docstring(meth, 4) %} + {% if meth.is_async() -%}suspend {% endif -%} + fun {{ meth.name()|fn_name }}({% call kt::arg_list(meth, true) %}) + {%- match meth.return_type() -%} + {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} + {%- else -%} + {%- endmatch %} + {% endfor %} + companion object +} + 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 index 776c402727..a80418eb00 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt @@ -12,8 +12,8 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_n } } - override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int { - val spaceForMapSize = 4 + override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): ULong { + val spaceForMapSize = 4UL val spaceForChildren = value.map { (k, v) -> {{ key_type|allocation_size_fn }}(k) + {{ value_type|allocation_size_fn }}(v) 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 index 6a3aeada35..1bac8a435c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt @@ -13,14 +13,57 @@ private inline fun <reified Lib : Library> loadIndirect( return Native.load<Lib>(findLibraryName(componentName), Lib::class.java) } +// Define FFI callback types +{%- for def in ci.ffi_definitions() %} +{%- match def %} +{%- when FfiDefinition::CallbackFunction(callback) %} +internal interface {{ callback.name()|ffi_callback_name }} : com.sun.jna.Callback { + fun callback( + {%- for arg in callback.arguments() -%} + {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }}, + {%- endfor -%} + {%- if callback.has_rust_call_status_arg() -%} + uniffiCallStatus: UniffiRustCallStatus, + {%- endif -%} + ) + {%- match callback.return_type() %} + {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }} + {%- when None %} + {%- endmatch %} +} +{%- when FfiDefinition::Struct(ffi_struct) %} +@Structure.FieldOrder({% for field in ffi_struct.fields() %}"{{ field.name()|var_name_raw }}"{% if !loop.last %}, {% endif %}{% endfor %}) +internal open class {{ ffi_struct.name()|ffi_struct_name }}( + {%- for field in ffi_struct.fields() %} + @JvmField internal var {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }}, + {%- endfor %} +) : Structure() { + class UniffiByValue( + {%- for field in ffi_struct.fields() %} + {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }}, + {%- endfor %} + ): {{ ffi_struct.name()|ffi_struct_name }}({%- for field in ffi_struct.fields() %}{{ field.name()|var_name }}, {%- endfor %}), Structure.ByValue + + internal fun uniffiSetValue(other: {{ ffi_struct.name()|ffi_struct_name }}) { + {%- for field in ffi_struct.fields() %} + {{ field.name()|var_name }} = other.{{ field.name()|var_name }} + {%- endfor %} + } + +} +{%- when FfiDefinition::Function(_) %} +{# functions are handled below #} +{%- endmatch %} +{%- endfor %} + // 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 { +internal interface UniffiLib : Library { companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}") - .also { lib: _UniFFILib -> + internal val INSTANCE: UniffiLib by lazy { + loadIndirect<UniffiLib>(componentName = "{{ ci.namespace() }}") + .also { lib: UniffiLib -> uniffiCheckContractApiVersion(lib) uniffiCheckApiChecksums(lib) {% for fn in self.initialization_fns() -%} @@ -28,6 +71,12 @@ internal interface _UniFFILib : Library { {% endfor -%} } } + {% if ci.contains_object_types() %} + // The Cleaner for the whole library + internal val CLEANER: UniffiCleaner by lazy { + UniffiCleaner.create() + } + {%- endif %} } {% for func in ci.iter_ffi_function_definitions() -%} @@ -37,7 +86,7 @@ internal interface _UniFFILib : Library { {% endfor %} } -private fun uniffiCheckContractApiVersion(lib: _UniFFILib) { +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 @@ -48,7 +97,7 @@ private fun uniffiCheckContractApiVersion(lib: _UniFFILib) { } @Suppress("UNUSED_PARAMETER") -private fun uniffiCheckApiChecksums(lib: _UniFFILib) { +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") diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt new file mode 100644 index 0000000000..e3e85544d7 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt @@ -0,0 +1,40 @@ + +// The cleaner interface for Object finalization code to run. +// This is the entry point to any implementation that we're using. +// +// The cleaner registers objects and returns cleanables, so now we are +// defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the +// different implmentations available at compile time. +interface UniffiCleaner { + interface Cleanable { + fun clean() + } + + fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable + + companion object +} + +// The fallback Jna cleaner, which is available for both Android, and the JVM. +private class UniffiJnaCleaner : UniffiCleaner { + private val cleaner = com.sun.jna.internal.Cleaner.getCleaner() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + UniffiJnaCleanable(cleaner.register(value, cleanUpTask)) +} + +private class UniffiJnaCleanable( + private val cleanable: com.sun.jna.internal.Cleaner.Cleanable, +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} + +// We decide at uniffi binding generation time whether we were +// using Android or not. +// There are further runtime checks to chose the correct implementation +// of the cleaner. +{% if config.android_cleaner() %} +{%- include "ObjectCleanerHelperAndroid.kt" %} +{%- else %} +{%- include "ObjectCleanerHelperJvm.kt" %} +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt new file mode 100644 index 0000000000..d025879848 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt @@ -0,0 +1,26 @@ +{{- self.add_import("android.os.Build") }} +{{- self.add_import("androidx.annotation.RequiresApi") }} + +private fun UniffiCleaner.Companion.create(): UniffiCleaner = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + AndroidSystemCleaner() + } else { + UniffiJnaCleaner() + } + +// The SystemCleaner, available from API Level 33. +// Some API Level 33 OSes do not support using it, so we require API Level 34. +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +private class AndroidSystemCleaner : UniffiCleaner { + val cleaner = android.system.SystemCleaner.cleaner() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + AndroidSystemCleanable(cleaner.register(value, cleanUpTask)) +} + +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +private class AndroidSystemCleanable( + private val cleanable: java.lang.ref.Cleaner.Cleanable, +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt new file mode 100644 index 0000000000..c43bc167fc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt @@ -0,0 +1,25 @@ +private fun UniffiCleaner.Companion.create(): UniffiCleaner = + try { + // For safety's sake: if the library hasn't been run in android_cleaner = true + // mode, but is being run on Android, then we still need to think about + // Android API versions. + // So we check if java.lang.ref.Cleaner is there, and use that… + java.lang.Class.forName("java.lang.ref.Cleaner") + JavaLangRefCleaner() + } catch (e: ClassNotFoundException) { + // … otherwise, fallback to the JNA cleaner. + UniffiJnaCleaner() + } + +private class JavaLangRefCleaner : UniffiCleaner { + val cleaner = java.lang.ref.Cleaner.create() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + JavaLangRefCleanable(cleaner.register(value, cleanUpTask)) +} + +private class JavaLangRefCleanable( + val cleanable: java.lang.ref.Cleaner.Cleanable +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} 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 deleted file mode 100644 index b9352c690f..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt +++ /dev/null @@ -1,161 +0,0 @@ -// 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 index 8ce27a5d04..62cac7a4d0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -1,125 +1,282 @@ +// This template implements a class for working with a Rust struct via a Pointer/Arc<T> +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc<T>` and the +// Kotlin Pointer to work with 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 +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each 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 instance 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 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 risks +// leaking 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. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// 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 instance 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. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + +{{ self.add_import("java.util.concurrent.atomic.AtomicBoolean") }} +{%- if self.include_once_check("interface-support") %} + {%- include "ObjectCleanerHelper.kt" %} +{%- endif %} + {%- 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") }} +{%- let (interface_name, impl_class_name) = obj|object_names(ci) %} +{%- let methods = obj.methods() %} +{%- let interface_docstring = obj.docstring() %} +{%- let is_error = ci.is_name_used_as_error(name) %} +{%- let ffi_converter_name = obj|ffi_converter_name %} -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 -%} +{%- include "Interface.kt" %} - {% endfor %} - companion object -} +{%- call kt::docstring(obj, 0) %} +{% if (is_error) %} +open class {{ impl_class_name }} : Exception, Disposable, AutoCloseable, {{ interface_name }} { +{% else -%} +open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }} { +{%- endif %} -class {{ type_name }}( - pointer: Pointer -) : FFIObject(pointer), {{ type_name }}Interface { + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } {%- match obj.primary_constructor() %} - {%- when Some with (cons) %} - constructor({% call kt::arg_list_decl(cons) -%}) : + {%- when Some(cons) %} + {%- if cons.is_async() %} + // Note no constructor generated for this object as it is async. + {%- else %} + {%- call kt::docstring(cons, 4) %} + constructor({% call kt::arg_list(cons, true) -%}) : this({% call kt::to_ffi_call(cons) %}) + {%- endif %} {%- 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) + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + 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) { + cleanable.clean() + } } } - {% 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) + @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.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } } + } - {%- when None -%} - override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) = - callWithPointer { - {%- call kt::to_ffi_call_with_prefix("it", meth) %} + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.{{ obj.ffi_object_free().name() }}(ptr, status) + } + } } - {% endmatch %} - {% endif %} + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.{{ obj.ffi_object_clone().name() }}(pointer!!, status) + } + } + + {% for meth in obj.methods() -%} + {%- call kt::func_decl("override", meth, 4) %} {% endfor %} + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {% when UniffiTrait::Display { fmt } %} + override fun toString(): String { + return {{ fmt.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(fmt) %}) + } + {% when UniffiTrait::Eq { eq, ne } %} + {# only equals used #} + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is {{ impl_class_name}}) return false + return {{ eq.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(eq) %}) + } + {% when UniffiTrait::Hash { hash } %} + override fun hashCode(): Int { + return {{ hash.return_type().unwrap()|lift_fn }}({%- call kt::to_ffi_call(hash) %}).toInt() + } + {%- else %} + {%- endmatch %} + {%- endfor %} + + {# XXX - "companion object" confusion? How to have alternate constructors *and* be an error? #} {% 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) %}) + {% call kt::func_decl("", cons, 4) %} {% endfor %} } + {% else if is_error %} + companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ impl_class_name }}> { + override fun lift(error_buf: RustBuffer.ByValue): {{ impl_class_name }} { + // Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer. + val bb = error_buf.asByteBuffer() + if (bb == null) { + throw InternalException("?") + } + return {{ ffi_converter_name }}.read(bb) + } + } {% else %} companion object {% endif %} } -public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { - override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it } +{%- if obj.has_callback_interface() %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} +{%- let ffi_init_callback = obj.ffi_init_callback() %} +{% include "CallbackInterfaceImpl.kt" %} +{%- endif %} + +public object {{ ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { + {%- if obj.has_callback_interface() %} + internal val handleMap = UniffiHandleMap<{{ type_name }}>() + {%- endif %} + + override fun lower(value: {{ type_name }}): Pointer { + {%- if obj.has_callback_interface() %} + return Pointer(handleMap.insert(value)) + {%- else %} + return value.uniffiClonePointer() + {%- endif %} + } override fun lift(value: Pointer): {{ type_name }} { - return {{ type_name }}(value) + return {{ impl_class_name }}(value) } override fun read(buf: ByteBuffer): {{ type_name }} { @@ -128,7 +285,7 @@ public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointe return lift(Pointer(buf.getLong())) } - override fun allocationSize(value: {{ type_name }}) = 8 + override fun allocationSize(value: {{ type_name }}) = 8UL override fun write(value: {{ type_name }}, buf: ByteBuffer) { // The Rust code always expects pointers written as 8 bytes, 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 index 56cb5f87a5..98451e1451 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt @@ -8,11 +8,11 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_nam return {{ inner_type|read_fn }}(buf) } - override fun allocationSize(value: {{ inner_type_name }}?): Int { + override fun allocationSize(value: {{ inner_type_name }}?): ULong { if (value == null) { - return 1 + return 1UL } else { - return 1 + {{ inner_type|allocation_size_fn }}(value) + return 1UL + {{ inner_type|allocation_size_fn }}(value) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md new file mode 100644 index 0000000000..0e770cb829 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md @@ -0,0 +1,13 @@ +# Rules for the Kotlin template code + +## Naming + +Private variables, classes, functions, etc. should be prefixed with `uniffi`, `Uniffi`, or `UNIFFI`. +This avoids naming collisions with user-defined items. +Users will not get name collisions as long as they don't use "uniffi", which is reserved for us. + +In particular, make sure to use the `uniffi` prefix for any variable names in generated functions. +If you name a variable something like `result` the code will probably work initially. +Then it will break later on when a user decides to define a function with a parameter named `result`. + +Note: this doesn't apply to items that we want to expose, for example users may want to catch `InternalException` so doesn't get the `Uniffi` prefix. 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 index b588ca1398..bc3028c736 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt @@ -1,8 +1,11 @@ {%- let rec = ci|get_record_definition(name) %} +{%- if rec.has_fields() %} +{%- call kt::docstring(rec, 0) %} data class {{ type_name }} ( {%- for field in rec.fields() %} - var {{ field.name()|var_name }}: {{ field|type_name(ci) -}} + {%- call kt::docstring(field, 4) %} + {% if config.generate_immutable_records() %}val{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name(ci) -}} {%- match field.default_value() %} {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }} {%- else %} @@ -18,21 +21,39 @@ data class {{ type_name }} ( {% endif %} companion object } +{%- else -%} +{%- call kt::docstring(rec, 0) %} +class {{ type_name }} { + override fun equals(other: Any?): Boolean { + return other is {{ type_name }} + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + companion object +} +{%- endif %} public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { override fun read(buf: ByteBuffer): {{ type_name }} { + {%- if rec.has_fields() %} return {{ type_name }}( {%- for field in rec.fields() %} {{ field|read_fn }}(buf), {%- endfor %} ) + {%- else %} + return {{ type_name }}() + {%- endif %} } - override fun allocationSize(value: {{ type_name }}) = ( + override fun allocationSize(value: {{ type_name }}) = {%- if rec.has_fields() %} ( {%- for field in rec.fields() %} - {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%} + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif %} {%- endfor %} - ) + ) {%- else %} 0UL {%- endif %} override fun write(value: {{ type_name }}, buf: ByteBuffer) { {%- for field in rec.fields() %} 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 index dfbea24074..b28f25bfc3 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt @@ -4,32 +4,41 @@ @Structure.FieldOrder("capacity", "len", "data") open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 + // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values. + // When dealing with these fields, make sure to call `toULong()`. + @JvmField var capacity: Long = 0 + @JvmField var len: Long = 0 @JvmField var data: Pointer? = null class ByValue: RustBuffer(), Structure.ByValue class ByReference: RustBuffer(), Structure.ByReference + internal fun setValue(other: RustBuffer) { + capacity = other.capacity + len = other.len + data = other.data + } + companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status) + internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status -> + // Note: need to convert the size to a `Long` value to make this work with JVM. + UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toLong(), 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 { + internal fun create(capacity: ULong, len: ULong, data: Pointer?): RustBuffer.ByValue { var buf = RustBuffer.ByValue() - buf.capacity = capacity - buf.len = len + buf.capacity = capacity.toLong() + buf.len = len.toLong() buf.data = data return buf } - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status) + internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status -> + UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status) } } @@ -53,9 +62,9 @@ class RustBufferByReference : ByReference(16) { 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) + pointer.setLong(0, value.capacity) + pointer.setLong(8, value.len) + pointer.setPointer(16, value.data) } /** @@ -64,9 +73,9 @@ class RustBufferByReference : ByReference(16) { 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)) + value.writeField("capacity", pointer.getLong(0)) + value.writeField("len", pointer.getLong(8)) + value.writeField("data", pointer.getLong(16)) return value } 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 index 876d1bc05e..61f911cb0c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt @@ -8,15 +8,15 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_typ } } - override fun allocationSize(value: List<{{ inner_type_name }}>): Int { - val sizeForLength = 4 + override fun allocationSize(value: List<{{ inner_type_name }}>): ULong { + val sizeForLength = 4UL 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 { + value.iterator().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 index 68324be4f9..b67435bd1a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt @@ -4,7 +4,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> { // store our length and avoid writing it out to the buffer. override fun lift(value: RustBuffer.ByValue): String { try { - val byteArr = ByteArray(value.len) + val byteArr = ByteArray(value.len.toInt()) value.asByteBuffer()!!.get(byteArr) return byteArr.toString(Charsets.UTF_8) } finally { @@ -31,7 +31,7 @@ public object FfiConverterString: FfiConverter<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()) + val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) rbuf.asByteBuffer()!!.put(byteBuf) return rbuf } @@ -39,9 +39,9 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> { // 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 + override fun allocationSize(value: String): ULong { + val sizeForLength = 4UL + val sizeForString = value.length.toULong() * 3UL return sizeForLength + sizeForString } 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 index 21069d7ce8..10a450a4bd 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt @@ -14,7 +14,7 @@ public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> { } // 8 bytes for seconds, 4 bytes for nanoseconds - override fun allocationSize(value: java.time.Instant) = 12 + override fun allocationSize(value: java.time.Instant) = 12UL override fun write(value: java.time.Instant, buf: ByteBuffer) { var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value) 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 index 6a841d3484..681c48093a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt @@ -1,51 +1 @@ -{%- 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 %} +{%- call kt::func_decl("", func, 8) %} 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 index 103d444ea3..c27121b701 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt @@ -1,5 +1,38 @@ {%- import "macros.kt" as kt %} +// 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 + } + } + +/** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */ +object NoPointer + {%- for type_ in ci.iter_types() %} {%- let type_name = type_|type_name(ci) %} {%- let ffi_converter_name = type_|ffi_converter_name %} @@ -82,9 +115,6 @@ {%- when Type::CallbackInterface { module_path, name } %} {% include "CallbackInterfaceTemplate.kt" %} -{%- when Type::ForeignExecutor %} -{% include "ForeignExecutorTemplate.kt" %} - {%- when Type::Timestamp %} {% include "TimestampHelper.kt" %} @@ -104,6 +134,10 @@ {%- if ci.has_async_fns() %} {# Import types needed for async support #} {{ self.add_import("kotlin.coroutines.resume") }} +{{ self.add_import("kotlinx.coroutines.launch") }} {{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }} {{ self.add_import("kotlinx.coroutines.CancellableContinuation") }} +{{ self.add_import("kotlinx.coroutines.DelicateCoroutinesApi") }} +{{ self.add_import("kotlinx.coroutines.Job") }} +{{ self.add_import("kotlinx.coroutines.GlobalScope") }} {%- 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 index 279a8fa91b..b179145b62 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterUShort: FfiConverter<UShort, Short> { return value.toShort() } - override fun allocationSize(value: UShort) = 2 + override fun allocationSize(value: UShort) = 2UL 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 index da7b5b28d6..202d5bcd5b 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterUInt: FfiConverter<UInt, Int> { return value.toInt() } - override fun allocationSize(value: UInt) = 4 + override fun allocationSize(value: UInt) = 4UL 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 index 44d27ad36b..9be2a5a69d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterULong: FfiConverter<ULong, Long> { return value.toLong() } - override fun allocationSize(value: ULong) = 8 + override fun allocationSize(value: ULong) = 8UL 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 index b6d176603e..ee360673e0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterUByte: FfiConverter<UByte, Byte> { return value.toByte() } - override fun allocationSize(value: UByte) = 1 + override fun allocationSize(value: UByte) = 1UL 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 index 6a95d6a66d..7acfdc8861 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt @@ -1,32 +1,91 @@ {# // Template to call into rust. Used in several places. -// Variable names in `arg_list_decl` should match up with arg lists +// Variable names in `arg_list` 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 -%} + {%- if func.takes_self() %} + callWithPointer { + {%- call to_raw_ffi_call(func) %} + } + {% else %} + {%- call to_raw_ffi_call(func) %} + {% endif %} +{%- endmacro %} -{%- macro to_ffi_call_with_prefix(prefix, func) %} +{%- macro to_raw_ffi_call(func) -%} {%- match func.throws_type() %} {%- when Some with (e) %} - rustCallWithError({{ e|type_name(ci) }}) + uniffiRustCallWithError({{ e|type_name(ci) }}) {%- else %} - rustCall() + uniffiRustCall() {%- endmatch %} { _status -> - _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}( - {{- prefix }}, - {% call arg_list_lowered(func) %} + UniffiLib.INSTANCE.{{ func.ffi_func().name() }}( + {% if func.takes_self() %}it, {% endif -%} + {% call arg_list_lowered(func) -%} _status) } +{%- endmacro -%} + +{%- macro func_decl(func_decl, callable, indent) %} + {%- call docstring(callable, indent) %} + {%- match callable.throws_type() -%} + {%- when Some(throwable) %} + @Throws({{ throwable|type_name(ci) }}::class) + {%- else -%} + {%- endmatch -%} + {%- if callable.is_async() %} + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + {{ func_decl }} suspend fun {{ callable.name()|fn_name }}( + {%- call arg_list(callable, !callable.takes_self()) -%} + ){% match callable.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { + return {% call call_async(callable) %} + } + {%- else -%} + {{ func_decl }} fun {{ callable.name()|fn_name }}( + {%- call arg_list(callable, !callable.takes_self()) -%} + ){%- match callable.return_type() -%} + {%- when Some with (return_type) -%} + : {{ return_type|type_name(ci) }} { + return {{ return_type|lift_fn }}({% call to_ffi_call(callable) %}) + } + {%- when None %} + = {% call to_ffi_call(callable) %} + {%- endmatch %} + {% endif %} +{% endmacro %} + +{%- macro call_async(callable) -%} + uniffiRustCallAsync( +{%- if callable.takes_self() %} + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}( + thisPtr, + {% call arg_list_lowered(callable) %} + ) + }, +{%- else %} + UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}({% call arg_list_lowered(callable) %}), +{%- endif %} + {{ callable|async_poll(ci) }}, + {{ callable|async_complete(ci) }}, + {{ callable|async_free(ci) }}, + // lift function + {%- match callable.return_type() %} + {%- when Some(return_type) %} + { {{ return_type|lift_fn }}(it) }, + {%- when None %} + { Unit }, + {% endmatch %} + // Error FFI converter + {%- match callable.throws_type() %} + {%- when Some(e) %} + {{ e|type_name(ci) }}.ErrorHandler, + {%- when None %} + UniffiNullRustCallStatusErrorHandler, + {%- endmatch %} + ) {%- endmacro %} {%- macro arg_list_lowered(func) %} @@ -37,37 +96,42 @@ {#- // Arglist as used in kotlin declarations of methods, functions and constructors. +// If is_decl, then default values be specified. // Note the var_name and type_name filters. -#} -{% macro arg_list_decl(func) %} - {%- for arg in func.arguments() -%} +{% macro arg_list(func, is_decl) %} +{%- 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 %} +{%- if is_decl %} +{%- match arg.default_value() %} +{%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }} +{%- else %} +{%- endmatch %} +{%- endif %} +{%- 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. +// 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 %} + {%- if func.has_rust_call_status_arg() %}uniffi_out_err: UniffiRustCallStatus, {% endif %} {%- endmacro -%} +{% macro field_name(field, field_num) %} +{%- if field.name().is_empty() -%} +v{{- field_num -}} +{%- else -%} +{{ field.name()|var_name }} +{%- endif -%} +{%- endmacro %} + // Macro for destroying fields {%- macro destroy_fields(member) %} Disposable.destroy( @@ -75,3 +139,15 @@ this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%} {% endfor -%}) {%- endmacro -%} + +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- 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 index 9ee4229018..2cdc72a5e2 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt @@ -1,6 +1,8 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! +{%- call kt::docstring_value(ci.namespace_docstring(), 0) %} + @file:Suppress("NAME_SHADOWING") package {{ config.package_name() }}; @@ -28,6 +30,7 @@ import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.CharBuffer import java.nio.charset.CodingErrorAction +import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.ConcurrentHashMap {%- for req in self.imports() %} @@ -37,6 +40,7 @@ import java.util.concurrent.ConcurrentHashMap {% include "RustBufferTemplate.kt" %} {% include "FfiConverterTemplate.kt" %} {% include "Helpers.kt" %} +{% include "HandleMap.kt" %} // Contains loading, initialization code, // and the FFI Function declarations in a com.sun.jna.Library. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs index 7b78540741..0824015751 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs @@ -2,10 +2,8 @@ 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 crate::bindings::TargetLanguage; +use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use std::env; @@ -33,14 +31,18 @@ pub fn run_script( args: Vec<String>, options: &RunScriptOptions, ) -> Result<()> { - let script_path = Utf8Path::new(".").join(script_file); + let script_path = Utf8Path::new(script_file); let test_helper = UniFFITestHelper::new(crate_name)?; - let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + 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], + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Kotlin], + try_format_code: false, + }, + None, &out_dir, false, )?; diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs index b91bcbe18f..16adeca9a5 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs @@ -3,7 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::CodeType; -use crate::backend::{Literal, Type}; +use crate::{ + backend::{Literal, Type}, + bindings::python::gen_python::AsCodeType, +}; #[derive(Debug)] pub struct OptionalCodeType { @@ -33,8 +36,9 @@ impl CodeType for OptionalCodeType { fn literal(&self, literal: &Literal) -> String { match literal { - Literal::Null => "None".into(), - _ => super::PythonCodeOracle.find(&self.inner).literal(literal), + Literal::None => "None".into(), + Literal::Some { inner } => super::PythonCodeOracle.find(&self.inner).literal(inner), + _ => panic!("Invalid literal for Optional type: {literal:?}"), } } } @@ -88,7 +92,11 @@ impl MapCodeType { impl CodeType for MapCodeType { fn type_label(&self) -> String { - "dict".to_string() + format!( + "dict[{}, {}]", + self.key.as_codetype().type_label(), + self.value.as_codetype().type_label() + ) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs deleted file mode 100644 index be3ba1d791..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* 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; - -#[derive(Debug)] -pub struct ForeignExecutorCodeType; - -impl CodeType for ForeignExecutorCodeType { - fn type_label(&self) -> String { - "asyncio.BaseEventLoop".into() - } - - fn canonical_name(&self) -> String { - "ForeignExecutor".into() - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs index 0d19c4bb3c..0a46251d6d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs @@ -17,7 +17,7 @@ impl ExternalCodeType { impl CodeType for ExternalCodeType { fn type_label(&self) -> String { - self.name.clone() + super::PythonCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 8178fcc102..6a10a38e7f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -2,8 +2,9 @@ * 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::{Context, Result}; +use anyhow::{bail, Context, Result}; use askama::Template; +use camino::Utf8Path; use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -13,20 +14,43 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; use crate::backend::TemplateExpression; +use crate::bindings::python; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; mod callback_interface; mod compounds; mod custom; mod enum_; -mod executor; mod external; mod miscellany; mod object; mod primitives; mod record; +pub struct PythonBindingGenerator; + +impl BindingGenerator for PythonBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + python::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + if cdylib_name.is_none() { + bail!("Generate bindings for Python requires a cdylib, but {library_path} was given"); + } + Ok(()) + } +} + /// A trait tor the implementation. trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in @@ -114,6 +138,8 @@ pub struct Config { cdylib_name: Option<String>, #[serde(default)] custom_types: HashMap<String, CustomTypeConfig>, + #[serde(default)] + external_packages: HashMap<String, String>, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -133,6 +159,16 @@ impl Config { "uniffi".into() } } + + /// Get the package name for a given external namespace. + pub fn module_for_namespace(&self, ns: &str) -> String { + let ns = ns.to_string().to_snake_case(); + match self.external_packages.get(&ns) { + None => format!(".{ns}"), + Some(value) if value.is_empty() => ns, + Some(value) => format!("{value}.{ns}"), + } + } } impl BindingsConfig for Config { @@ -326,7 +362,19 @@ impl PythonCodeOracle { fixup_keyword(nm.to_string().to_shouty_snake_case()) } - fn ffi_type_label(ffi_type: &FfiType) -> String { + /// Get the idiomatic Python rendering of an FFI callback function name + fn ffi_callback_name(&self, nm: &str) -> String { + format!("UNIFFI_{}", nm.to_shouty_snake_case()) + } + + /// Get the idiomatic Python rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + // The ctypes docs use both SHOUTY_SNAKE_CASE AND UpperCamelCase for structs. Let's use + // UpperCamelCase and reserve shouting for global variables + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "ctypes.c_int8".to_string(), FfiType::UInt8 => "ctypes.c_uint8".to_string(), @@ -338,19 +386,64 @@ impl PythonCodeOracle { FfiType::UInt64 => "ctypes.c_uint64".to_string(), FfiType::Float32 => "ctypes.c_float".to_string(), FfiType::Float64 => "ctypes.c_double".to_string(), + FfiType::Handle => "ctypes.c_uint64".to_string(), FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { Some(suffix) => format!("_UniffiRustBuffer{suffix}"), None => "_UniffiRustBuffer".to_string(), }, + FfiType::RustCallStatus => "_UniffiRustCallStatus".to_string(), FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(), - FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(), + FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_name(name), // Pointer to an `asyncio.EventLoop` instance - FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(), - FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(), - FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), - FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(), - FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), + FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)), + FfiType::VoidPointer => "ctypes.c_void_p".to_string(), + } + } + + /// Default values for FFI types + /// + /// Used to set a default return value when returning an error + fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String { + match return_type { + Some(t) => match t { + FfiType::UInt8 + | FfiType::Int8 + | FfiType::UInt16 + | FfiType::Int16 + | FfiType::UInt32 + | FfiType::Int32 + | FfiType::UInt64 + | FfiType::Int64 => "0".to_owned(), + FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(), + FfiType::RustArcPtr(_) => "ctypes.c_void_p()".to_owned(), + FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { + Some(suffix) => format!("_UniffiRustBuffer{suffix}.default()"), + None => "_UniffiRustBuffer.default()".to_owned(), + }, + _ => unimplemented!("FFI return type: {t:?}"), + }, + // When we need to use a value for void returns, we use a `u8` placeholder + None => "0".to_owned(), + } + } + + /// Get the name of the protocol and class name for an object. + /// + /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that. + /// Otherwise, the class name is the object name and the protocol name is derived from that. + /// + /// This split determines what types `FfiConverter.lower()` inputs. If we support callback + /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower + /// only lowers the concrete class. + fn object_names(&self, obj: &Object) -> (String, String) { + let class_name = self.class_name(obj.name()); + if obj.has_callback_interface() { + let impl_name = format!("{class_name}Impl"); + (class_name, impl_name) + } else { + (format!("{class_name}Protocol"), class_name) } } } @@ -392,7 +485,6 @@ impl<T: AsType> AsCodeType for T { 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)) } @@ -429,6 +521,10 @@ pub mod filters { Ok(format!("{}.lift", ffi_converter_name(as_ct)?)) } + pub(super) fn check_lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.check_lower", ffi_converter_name(as_ct)?)) + } + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { Ok(format!("{}.lower", ffi_converter_name(as_ct)?)) } @@ -448,8 +544,18 @@ pub mod filters { Ok(as_ct.as_codetype().literal(literal)) } + // Get the idiomatic Python rendering of an individual enum variant's discriminant + pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> { + let literal = e.variant_discr(*index).expect("invalid index"); + Ok(Type::UInt64.as_codetype().literal(&literal)) + } + pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> { - Ok(PythonCodeOracle::ffi_type_label(type_)) + Ok(PythonCodeOracle.ffi_type_label(type_)) + } + + pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_default_value(return_type.as_ref())) } /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). @@ -471,4 +577,51 @@ pub mod filters { pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> { Ok(PythonCodeOracle.enum_variant_name(nm)) } + + /// Get the idiomatic Python rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_callback_name(nm)) + } + + /// Get the idiomatic Python rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_struct_name(nm)) + } + + /// Get the idiomatic Python rendering of an individual enum variant. + pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { + Ok(PythonCodeOracle.object_names(obj)) + } + + /// Get the idiomatic Python rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> { + let docstring = textwrap::dedent(docstring); + // Escape triple quotes to avoid syntax error + let escaped = docstring.replace(r#"""""#, r#"\"\"\""#); + + let wrapped = format!("\"\"\"\n{escaped}\n\"\"\""); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_docstring_escape() { + let docstring = r#""""This is a docstring beginning with triple quotes. +Contains "quotes" in it. +It also has a triple quote: """ +And a even longer quote: """"""#; + + let expected = r#"""" +\"\"\"This is a docstring beginning with triple quotes. +Contains "quotes" in it. +It also has a triple quote: \"\"\" +And a even longer quote: \"\"\""" +""""#; + + assert_eq!(super::filters::docstring(docstring, &0).unwrap(), expected); + } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py index 82aa534b46..26daa9ba5c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py @@ -3,13 +3,37 @@ _UNIFFI_RUST_FUTURE_POLL_READY = 0 _UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1 # Stores futures for _uniffi_continuation_callback -_UniffiContinuationPointerManager = _UniffiPointerManager() +_UniffiContinuationHandleMap = _UniffiHandleMap() + +UNIFFI_GLOBAL_EVENT_LOOP = None + +""" +Set the event loop to use for async functions + +This is needed if some async functions run outside of the eventloop, for example: + - A non-eventloop thread is spawned, maybe from `EventLoop.run_in_executor` or maybe from the + Rust code spawning its own thread. + - The Rust code calls an async callback method from a sync callback function, using something + like `pollster` to block on the async call. + +In this case, we need an event loop to run the Python async function, but there's no eventloop set +for the thread. Use `uniffi_set_event_loop` to force an eventloop to be used in this case. +""" +def uniffi_set_event_loop(eventloop: asyncio.BaseEventLoop): + global UNIFFI_GLOBAL_EVENT_LOOP + UNIFFI_GLOBAL_EVENT_LOOP = eventloop + +def _uniffi_get_event_loop(): + if UNIFFI_GLOBAL_EVENT_LOOP is not None: + return UNIFFI_GLOBAL_EVENT_LOOP + else: + return asyncio.get_running_loop() # Continuation callback for async functions # lift the return value or error and resolve the future, causing the async function to resume. -@_UNIFFI_FUTURE_CONTINUATION_T +@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK def _uniffi_continuation_callback(future_ptr, poll_code): - (eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr) + (eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr) eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) def _uniffi_set_future_result(future, poll_code): @@ -18,14 +42,15 @@ def _uniffi_set_future_result(future, poll_code): async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter): try: - eventloop = asyncio.get_running_loop() + eventloop = _uniffi_get_event_loop() # Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value while True: future = eventloop.create_future() ffi_poll( rust_future, - _UniffiContinuationPointerManager.new_pointer((eventloop, future)), + _uniffi_continuation_callback, + _UniffiContinuationHandleMap.insert((eventloop, future)), ) poll_code = await future if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY: @@ -37,4 +62,53 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, finally: ffi_free(rust_future) -_UniffiLib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(_uniffi_continuation_callback) +{%- if ci.has_async_callback_interface_definition() %} +def uniffi_trait_interface_call_async(make_call, handle_success, handle_error): + async def make_call_and_call_callback(): + try: + handle_success(await make_call()) + except Exception as e: + print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr) + traceback.print_exc(file=sys.stderr) + handle_error( + _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(repr(e)), + ) + eventloop = _uniffi_get_event_loop() + task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop) + handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task)) + return UniffiForeignFuture(handle, uniffi_foreign_future_free) + +def uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, error_type, lower_error): + async def make_call_and_call_callback(): + try: + try: + handle_success(await make_call()) + except error_type as e: + handle_error( + _UniffiRustCallStatus.CALL_ERROR, + lower_error(e), + ) + except Exception as e: + print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr) + traceback.print_exc(file=sys.stderr) + handle_error( + _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(repr(e)), + ) + eventloop = _uniffi_get_event_loop() + task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop) + handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task)) + return UniffiForeignFuture(handle, uniffi_foreign_future_free) + +UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = _UniffiHandleMap() + +@UNIFFI_FOREIGN_FUTURE_FREE +def uniffi_foreign_future_free(handle): + (eventloop, task) = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle) + eventloop.call_soon(uniffi_foreign_future_do_free, task) + +def uniffi_foreign_future_do_free(task): + if not task.done(): + task.cancel() +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py index 6775e9e132..3f8c5d1d4d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py @@ -1,16 +1,20 @@ -class _UniffiConverterBool(_UniffiConverterPrimitive): +class _UniffiConverterBool: @classmethod - def check(cls, value): + def check_lower(cls, value): return not not value @classmethod + def lower(cls, value): + return 1 if value else 0 + + @staticmethod + def lift(value): + return value != 0 + + @classmethod def read(cls, buf): return cls.lift(buf.read_u8()) @classmethod - def write_unchecked(cls, value, buf): + def write(cls, value, buf): buf.write_u8(value) - - @staticmethod - def lift(value): - return value != 0 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py index 196b5b29fa..4d09531322 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py @@ -7,10 +7,13 @@ class _UniffiConverterBytes(_UniffiConverterRustBuffer): return buf.read(size) @staticmethod - def write(value, buf): + def check_lower(value): try: memoryview(value) except TypeError: raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__)) + + @staticmethod + def write(value, buf): buf.write_i32(len(value)) buf.write(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py new file mode 100644 index 0000000000..676f01177a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py @@ -0,0 +1,98 @@ +{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} +{%- let trait_impl=format!("UniffiTraitImpl{}", name) %} + +# Put all the bits inside a class to keep the top-level namespace clean +class {{ trait_impl }}: + # For each method, generate a callback function to pass to Rust + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + + @{{ ffi_callback.name()|ffi_callback_name }} + def {{ meth.name()|fn_name }}( + {%- for arg in ffi_callback.arguments() %} + {{ arg.name()|var_name }}, + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() %} + uniffi_call_status_ptr, + {%- endif %} + ): + uniffi_obj = {{ ffi_converter_name }}._handle_map.get(uniffi_handle) + def make_call(): + args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name()|var_name }}), {% endfor %}) + method = uniffi_obj.{{ meth.name()|fn_name }} + return method(*args) + + {% if !meth.is_async() %} + {%- match meth.return_type() %} + {%- when Some(return_type) %} + def write_return_value(v): + uniffi_out_return[0] = {{ return_type|lower_fn }}(v) + {%- when None %} + write_return_value = lambda v: None + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + _uniffi_trait_interface_call( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + ) + {%- when Some(error) %} + _uniffi_trait_interface_call_with_error( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + {{ error|type_name }}, + {{ error|lower_fn }}, + ) + {%- endmatch %} + {%- else %} + def handle_success(return_value): + uniffi_future_callback( + uniffi_callback_data, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ return_type|lower_fn }}(return_value), + {%- when None %} + {%- endmatch %} + _UniffiRustCallStatus.default() + ) + ) + + def handle_error(status_code, rust_buffer): + uniffi_future_callback( + uniffi_callback_data, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ meth.return_type().map(FfiType::from)|ffi_default_value }}, + {%- when None %} + {%- endmatch %} + _UniffiRustCallStatus(status_code, rust_buffer), + ) + ) + + {%- match meth.throws_type() %} + {%- when None %} + uniffi_out_return[0] = uniffi_trait_interface_call_async(make_call, handle_success, handle_error) + {%- when Some(error) %} + uniffi_out_return[0] = uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, {{ error|type_name }}, {{ error|lower_fn }}) + {%- endmatch %} + {%- endif %} + {%- endfor %} + + @{{ "CallbackInterfaceFree"|ffi_callback_name }} + def uniffi_free(uniffi_handle): + {{ ffi_converter_name }}._handle_map.remove(uniffi_handle) + + # Generate the FFI VTable. This has a field for each callback interface method. + uniffi_vtable = {{ vtable|ffi_type_name }}( + {%- for (_, meth) in vtable_methods.iter() %} + {{ meth.name()|fn_name }}, + {%- endfor %} + uniffi_free + ) + # Send Rust a pointer to the VTable. Note: this means we need to keep the struct alive forever, + # or else bad things will happen when Rust tries to access it. + _UniffiLib.{{ ffi_init_callback.name() }}(ctypes.byref(uniffi_vtable)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py index 0fe2ab8dc0..d802c90fde 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py @@ -1,42 +1,3 @@ -import threading - -class ConcurrentHandleMap: - """ - A map where inserting, getting and removing data is synchronized with a lock. - """ - - def __init__(self): - # type Handle = int - self._left_map = {} # type: Dict[Handle, Any] - self._right_map = {} # type: Dict[Any, Handle] - - self._lock = threading.Lock() - self._current_handle = 0 - self._stride = 1 - - - def insert(self, obj): - with self._lock: - if obj in self._right_map: - return self._right_map[obj] - else: - handle = self._current_handle - self._current_handle += self._stride - self._left_map[handle] = obj - self._right_map[obj] = handle - return handle - - def get(self, handle): - with self._lock: - return self._left_map.get(handle) - - def remove(self, handle): - with self._lock: - if handle in self._left_map: - obj = self._left_map.pop(handle) - del self._right_map[obj] - return obj - # 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. IDX_CALLBACK_FREE = 0 @@ -45,22 +6,12 @@ _UNIFFI_CALLBACK_SUCCESS = 0 _UNIFFI_CALLBACK_ERROR = 1 _UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 -class _UniffiConverterCallbackInterface: - _handle_map = ConcurrentHandleMap() - - def __init__(self, cb): - self._foreign_callback = cb - - def drop(self, handle): - self.__class__._handle_map.remove(handle) +class UniffiCallbackInterfaceFfiConverter: + _handle_map = _UniffiHandleMap() @classmethod def lift(cls, handle): - obj = cls._handle_map.get(handle) - if not obj: - raise InternalError("The object in the handle map has been dropped already") - - return obj + return cls._handle_map.get(handle) @classmethod def read(cls, buf): @@ -68,6 +19,10 @@ class _UniffiConverterCallbackInterface: cls.lift(handle) @classmethod + def check_lower(cls, cb): + pass + + @classmethod def lower(cls, cb): handle = cls._handle_map.insert(cb) return handle diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py index e0e926757a..a41e58e635 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py @@ -1,105 +1,13 @@ -{%- let cbi = ci|get_callback_interface_definition(id) %} -{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} +{%- let cbi = ci|get_callback_interface_definition(name) %} +{%- let ffi_init_callback = cbi.ffi_init_callback() %} +{%- let protocol_name = type_name.clone() %} +{%- let protocol_docstring = cbi.docstring() %} +{%- let vtable = cbi.vtable() %} +{%- let methods = cbi.methods() %} +{%- let vtable_methods = cbi.vtable_methods() %} -{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} - -# Declaration and _UniffiConverters for {{ type_name }} Callback Interface - -class {{ type_name }}: - {% for meth in cbi.methods() -%} - def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}): - raise NotImplementedError - - {% endfor %} - -def py_{{ foreign_callback }}(handle, method, args_data, args_len, buf_ptr): - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name %} - def {{ method_name }}(python_callback, args_stream, buf_ptr): - {#- Unpacking args from the _UniffiRustBuffer #} - def makeCall(): - {#- Calling the concrete callback object #} - {%- if meth.arguments().len() != 0 -%} - return python_callback.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {{ arg|read_fn }}(args_stream) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - {%- else %} - return python_callback.{{ meth.name()|fn_name }}() - {%- endif %} - - def makeCallAndHandleReturn(): - {%- match meth.return_type() %} - {%- when Some(return_type) %} - rval = makeCall() - with _UniffiRustBuffer.alloc_with_builder() as builder: - {{ return_type|write_fn }}(rval, builder) - buf_ptr[0] = builder.finalize() - {%- when None %} - makeCall() - {%- endmatch %} - return _UNIFFI_CALLBACK_SUCCESS - - {%- match meth.throws_type() %} - {%- when None %} - return makeCallAndHandleReturn() - {%- when Some(err) %} - try: - return makeCallAndHandleReturn() - except {{ err|type_name }} as e: - # Catch errors declared in the UDL file - with _UniffiRustBuffer.alloc_with_builder() as builder: - {{ err|write_fn }}(e, builder) - buf_ptr[0] = builder.finalize() - return _UNIFFI_CALLBACK_ERROR - {%- endmatch %} - - {% endfor %} - - cb = {{ ffi_converter_name }}.lift(handle) - if not cb: - raise InternalError("No callback in handlemap; this is a uniffi bug") - - if method == IDX_CALLBACK_FREE: - {{ ffi_converter_name }}.drop(handle) - # Successfull return - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return _UNIFFI_CALLBACK_SUCCESS - - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - if method == {{ loop.index }}: - # Call the method and handle any errors - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for details - try: - return {{ method_name }}(cb, _UniffiRustBufferStream(args_data, args_len), buf_ptr) - except BaseException as e: - # Catch unexpected errors - try: - # Try to serialize the exception into a String - buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e)) - except: - # If that fails, just give up - pass - return _UNIFFI_CALLBACK_UNEXPECTED_ERROR - {% endfor %} - - # This should never happen, because an out of bounds method index won't - # ever be used. Once we can catch errors, we should return an InternalException. - # https://github.com/mozilla/uniffi-rs/issues/351 - - # An unexpected error happened. - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return _UNIFFI_CALLBACK_UNEXPECTED_ERROR - -# We need to keep this function reference alive: -# if they get GC'd while in use then UniFFI internals could attempt to call a function -# that is in freed memory. -# That would be...uh...bad. Yeah, that's the word. Bad. -{{ foreign_callback }} = _UNIFFI_FOREIGN_CALLBACK_T(py_{{ foreign_callback }}) -_rust_call(lambda err: _UniffiLib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)) +{% include "Protocol.py" %} +{% include "CallbackInterfaceImpl.py" %} # The _UniffiConverter which transforms the Callbacks in to Handles to pass to Rust. -{{ ffi_converter_name }} = _UniffiConverterCallbackInterface({{ foreign_callback }}) +{{ ffi_converter_name }} = UniffiCallbackInterfaceFfiConverter() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py index 5be6155b84..f75a85dc27 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py @@ -18,6 +18,10 @@ class _UniffiConverterType{{ name }}: return {{ builtin|ffi_converter_name }}.lift(value) @staticmethod + def check_lower(value): + return {{ builtin|ffi_converter_name }}.check_lower(value) + + @staticmethod def lower(value): return {{ builtin|ffi_converter_name }}.lower(value) @@ -52,6 +56,11 @@ class _UniffiConverterType{{ name }}: return {{ config.into_custom.render("builtin_value") }} @staticmethod + def check_lower(value): + builtin_value = {{ config.from_custom.render("value") }} + return {{ builtin|check_lower_fn }}(builtin_value) + + @staticmethod def lower(value): builtin_value = {{ config.from_custom.render("value") }} return {{ builtin|lower_fn }}(builtin_value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py index 10974e009d..ecb035b7f4 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py @@ -12,10 +12,14 @@ class _UniffiConverterDuration(_UniffiConverterRustBuffer): return datetime.timedelta(seconds=seconds, microseconds=microseconds) @staticmethod - def write(value, buf): + def check_lower(value): seconds = value.seconds + value.days * 24 * 3600 - nanoseconds = value.microseconds * 1000 if seconds < 0: raise ValueError("Invalid duration, must be non-negative") + + @staticmethod + def write(value, buf): + seconds = value.seconds + value.days * 24 * 3600 + nanoseconds = value.microseconds * 1000 buf.write_i64(seconds) buf.write_u32(nanoseconds) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py index 84d089baf9..d07dd1c44a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py @@ -7,31 +7,59 @@ {% if e.is_flat() %} class {{ type_name }}(enum.Enum): - {% for variant in e.variants() -%} - {{ variant.name()|enum_variant_py }} = {{ loop.index }} + {%- call py::docstring(e, 4) %} + {%- for variant in e.variants() %} + {{ variant.name()|enum_variant_py }} = {{ e|variant_discr_literal(loop.index0) }} + {%- call py::docstring(variant, 4) %} {% endfor %} {% else %} class {{ type_name }}: + {%- call py::docstring(e, 4) %} def __init__(self): raise RuntimeError("{{ type_name }} cannot be instantiated directly") # Each enum variant is a nested class of the enum itself. {% for variant in e.variants() -%} class {{ variant.name()|enum_variant_py }}: - {% for field in variant.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- call py::docstring(variant, 8) %} + + {%- if variant.has_nameless_fields() %} + def __init__(self, *values): + if len(values) != {{ variant.fields().len() }}: + raise TypeError(f"Expected a tuple of len {{ variant.fields().len() }}, found len {len(values)}") + {%- for field in variant.fields() %} + if not isinstance(values[{{ loop.index0 }}], {{ field|type_name }}): + raise TypeError(f"unexpected type for tuple element {{ loop.index0 }} - expected '{{ field|type_name }}', got '{type(values[{{ loop.index0 }}])}'") + {%- endfor %} + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"{{ type_name }}.{{ variant.name()|enum_variant_py }}{self._values!r}" + + def __eq__(self, other): + if not other.is_{{ variant.name()|var_name }}(): + return False + return self._values == other._values + + {%- else -%} + {%- for field in variant.fields() %} + {{ field.name()|var_name }}: "{{ field|type_name }}" + {%- call py::docstring(field, 8) %} {%- endfor %} @typing.no_type_check def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}): - {% if variant.has_fields() %} + {%- if variant.has_fields() %} {%- for field in variant.fields() %} self.{{ field.name()|var_name }} = {{ field.name()|var_name }} {%- endfor %} - {% else %} + {%- else %} pass - {% endif %} + {%- endif %} def __str__(self): return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) @@ -44,6 +72,7 @@ class {{ type_name }}: return False {%- endfor %} return True + {% endif %} {% endfor %} # For each variant, we have an `is_NAME` method for easily checking @@ -81,6 +110,30 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): {%- endfor %} raise InternalError("Raw enum value doesn't match any cases") + @staticmethod + def check_lower(value): + {%- if e.variants().is_empty() %} + pass + {%- else %} + {%- for variant in e.variants() %} + {%- if e.is_flat() %} + if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}: + {%- else %} + if value.is_{{ variant.name()|var_name }}(): + {%- endif %} + {%- for field in variant.fields() %} + {%- if variant.has_nameless_fields() %} + {{ field|check_lower_fn }}(value._values[{{ loop.index0 }}]) + {%- else %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endif %} + {%- endfor %} + return + {%- endfor %} + raise ValueError(value) + {%- endif %} + + @staticmethod def write(value, buf): {%- for variant in e.variants() %} {%- if e.is_flat() %} @@ -90,7 +143,11 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): if value.is_{{ variant.name()|var_name }}(): buf.write_i32({{ loop.index }}) {%- for field in variant.fields() %} + {%- if variant.has_nameless_fields() %} + {{ field|write_fn }}(value._values[{{ loop.index0 }}], buf) + {%- else %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endif %} {%- endfor %} {%- endif %} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py index 26a1e6452a..0911ff559a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py @@ -5,6 +5,7 @@ # __dict__. All of this happens in dummy class to avoid polluting the module # namespace. class {{ type_name }}(Exception): + {%- call py::docstring(e, 4) %} pass _UniffiTemp{{ type_name }} = {{ type_name }} @@ -14,10 +15,14 @@ class {{ type_name }}: # type: ignore {%- let variant_type_name = variant.name()|class_name -%} {%- if e.is_flat() %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + {%- call py::docstring(variant, 8) %} + def __repr__(self): return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(str(self))) {%- else %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + {%- call py::docstring(variant, 8) %} + def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}): {%- if variant.has_fields() %} super().__init__(", ".join([ @@ -60,6 +65,20 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): raise InternalError("Raw enum value doesn't match any cases") @staticmethod + def check_lower(value): + {%- if e.variants().is_empty() %} + pass + {%- else %} + {%- for variant in e.variants() %} + if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): + {%- for field in variant.fields() %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + return + {%- endfor %} + {%- endif %} + + @staticmethod def write(value, buf): {%- for variant in e.variants() %} if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py index 71e05e8b06..6c0cee85ef 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py @@ -1,9 +1,9 @@ -{%- let ns = namespace|fn_name %} +{%- let module = python_config.module_for_namespace(namespace) -%} # External type {{name}} is in namespace "{{namespace}}", crate {{module_path}} {%- let ffi_converter_name = "_UniffiConverterType{}"|format(name) %} -{{ self.add_import_of(ns, ffi_converter_name) }} -{{ self.add_import_of(ns, name) }} {#- import the type alias itself -#} +{{ self.add_import_of(module, ffi_converter_name) }} +{{ self.add_import_of(module, name|class_name) }} {#- import the type alias itself -#} {%- let rustbuffer_local_name = "_UniffiRustBuffer{}"|format(name) %} -{{ self.add_import_of_as(ns, "_UniffiRustBuffer", rustbuffer_local_name) }} +{{ self.add_import_of_as(module, "_UniffiRustBuffer", rustbuffer_local_name) }} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py index a52107a638..49a1a7286e 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py @@ -4,5 +4,5 @@ class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat): return buf.read_float() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_float(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py index 772f5080e9..e2084c7b13 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py @@ -4,5 +4,5 @@ class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat): return buf.read_double() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_double(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py deleted file mode 100644 index 6a6932fed0..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py +++ /dev/null @@ -1,63 +0,0 @@ -# FFI code for the ForeignExecutor type - -{{ self.add_import("asyncio") }} - -_UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0 -_UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED = 1 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2 - -class {{ ffi_converter_name }}: - _pointer_manager = _UniffiPointerManager() - - @classmethod - def lower(cls, eventloop): - if not isinstance(eventloop, asyncio.BaseEventLoop): - raise TypeError("_uniffi_executor_callback: Expected EventLoop instance") - return cls._pointer_manager.new_pointer(eventloop) - - @classmethod - def write(cls, eventloop, buf): - buf.write_c_size_t(cls.lower(eventloop)) - - @classmethod - def read(cls, buf): - return cls.lift(buf.read_c_size_t()) - - @classmethod - def lift(cls, value): - return cls._pointer_manager.lookup(value) - -@_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T -def _uniffi_executor_callback(eventloop_address, delay, task_ptr, task_data): - if task_ptr is None: - {{ ffi_converter_name }}._pointer_manager.release_pointer(eventloop_address) - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - else: - eventloop = {{ ffi_converter_name }}._pointer_manager.lookup(eventloop_address) - if eventloop.is_closed(): - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED - - callback = _UNIFFI_RUST_TASK(task_ptr) - # FIXME: there's no easy way to get a callback when an eventloop is closed. This means that - # if eventloop is called before the `call_soon_threadsafe()` calls are invoked, the call - # will never happen and we will probably leak a resource. - if delay == 0: - # This can be called from any thread, so make sure to use `call_soon_threadsafe' - eventloop.call_soon_threadsafe(callback, task_data, - _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS) - else: - # For delayed tasks, we use `call_soon_threadsafe()` + `call_later()` to make the - # operation threadsafe - eventloop.call_soon_threadsafe(eventloop.call_later, delay / 1000.0, callback, - task_data, _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS) - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - -# Register the callback with the scaffolding -{%- match ci.ffi_foreign_executor_callback_set() %} -{%- when Some with (fn) %} -_UniffiLib.{{ fn.name() }}(_uniffi_executor_callback) -{%- when None %} -{#- No foreign executor, we don't set anything #} -{% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py new file mode 100644 index 0000000000..f7c13cf745 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py @@ -0,0 +1,33 @@ +class _UniffiHandleMap: + """ + A map where inserting, getting and removing data is synchronized with a lock. + """ + + def __init__(self): + # type Handle = int + self._map = {} # type: Dict[Handle, Any] + self._lock = threading.Lock() + self._counter = itertools.count() + + def insert(self, obj): + with self._lock: + handle = next(self._counter) + self._map[handle] = obj + return handle + + def get(self, handle): + try: + with self._lock: + return self._map[handle] + except KeyError: + raise InternalError("UniffiHandleMap.get: Invalid handle") + + def remove(self, handle): + try: + with self._lock: + return self._map.pop(handle) + except KeyError: + raise InternalError("UniffiHandleMap.remove: Invalid handle") + + def __len__(self): + return len(self._map) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py index dca962f176..5d4bcbba89 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py @@ -16,15 +16,19 @@ class _UniffiRustCallStatus(ctypes.Structure): # These match the values from the uniffi::rustcalls module CALL_SUCCESS = 0 CALL_ERROR = 1 - CALL_PANIC = 2 + CALL_UNEXPECTED_ERROR = 2 + + @staticmethod + def default(): + return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default()) def __str__(self): if self.code == _UniffiRustCallStatus.CALL_SUCCESS: return "_UniffiRustCallStatus(CALL_SUCCESS)" elif self.code == _UniffiRustCallStatus.CALL_ERROR: return "_UniffiRustCallStatus(CALL_ERROR)" - elif self.code == _UniffiRustCallStatus.CALL_PANIC: - return "_UniffiRustCallStatus(CALL_PANIC)" + elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)" else: return "_UniffiRustCallStatus(<invalid code>)" @@ -37,7 +41,7 @@ def _rust_call_with_error(error_ffi_converter, fn, *args): # # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code. # error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result. - call_status = _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer(0, 0, None)) + call_status = _UniffiRustCallStatus.default() args_with_error = args + (ctypes.byref(call_status),) result = fn(*args_with_error) @@ -53,7 +57,7 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): raise InternalError("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") else: raise error_ffi_converter.lift(call_status.error_buf) - elif call_status.code == _UniffiRustCallStatus.CALL_PANIC: + elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer # with the message. But if that code panics, then it just sends back # an empty buffer. @@ -66,10 +70,20 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): raise InternalError("Invalid _UniffiRustCallStatus code: {}".format( call_status.code)) -# A function pointer for a callback as defined by UniFFI. -# Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int` -_UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer)) - -# UniFFI future continuation -_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8) +def _uniffi_trait_interface_call(call_status, make_call, write_return_value): + try: + return write_return_value(make_call()) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e)) +def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error): + try: + try: + return write_return_value(make_call()) + except error_type as e: + call_status.code = _UniffiRustCallStatus.CALL_ERROR + call_status.error_buf = lower_error(e) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py index 99f19dc1c0..befa563384 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt16(_UniffiConverterPrimitiveInt): return buf.read_i16() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i16(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py index 3b142c8749..70644f6717 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt32(_UniffiConverterPrimitiveInt): return buf.read_i32() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i32(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py index 6e94379cbf..232f127bd6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt64(_UniffiConverterPrimitiveInt): return buf.read_i64() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i64(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py index 732530e3cb..c1de1625e7 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt8(_UniffiConverterPrimitiveInt): return buf.read_i8() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i8(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py index 387227ed09..a09ca28a30 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py @@ -3,6 +3,12 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, items): + for (key, value) in items.items(): + {{ key_ffi_converter }}.check_lower(key) + {{ value_ffi_converter }}.check_lower(value) + + @classmethod def write(cls, items, buf): buf.write_i32(len(items)) for (key, value) in items.items(): diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py index fac6cd5564..1929f9aad6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -1,22 +1,6 @@ # Define some ctypes FFI types that we use in the library """ -ctypes type for the foreign executor callback. This is a built-in interface for scheduling -tasks - -Args: - executor: opaque c_size_t value representing the eventloop - delay: delay in ms - task: function pointer to the task callback - task_data: void pointer to the task callback data - -Normally we should call task(task_data) after the detail. -However, when task is NULL this indicates that Rust has dropped the ForeignExecutor and we should -decrease the EventLoop refcount. -""" -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int8, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p) - -""" Function pointer for a Rust task, which a callback function that takes a opaque pointer """ _UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8) @@ -25,7 +9,7 @@ def _uniffi_future_callback_t(return_type): """ Factory function to create callback function types for async functions """ - return ctypes.CFUNCTYPE(None, ctypes.c_size_t, return_type, _UniffiRustCallStatus) + return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus) def _uniffi_load_indirect(): """ @@ -72,12 +56,37 @@ def _uniffi_check_api_checksums(lib): # This is an implementation detail which will be called internally by the public API. _UniffiLib = _uniffi_load_indirect() -{%- for func in ci.iter_ffi_function_definitions() %} + +{%- for def in ci.ffi_definitions() %} +{%- match def %} +{%- when FfiDefinition::CallbackFunction(callback) %} +{{ callback.name()|ffi_callback_name }} = ctypes.CFUNCTYPE( + {%- match callback.return_type() %} + {%- when Some(return_type) %}{{ return_type|ffi_type_name }}, + {%- when None %}None, + {%- endmatch %} + {%- for arg in callback.arguments() -%} + {{ arg.type_().borrow()|ffi_type_name }}, + {%- endfor -%} + {%- if callback.has_rust_call_status_arg() %} + ctypes.POINTER(_UniffiRustCallStatus), + {%- endif %} +) +{%- when FfiDefinition::Struct(ffi_struct) %} +class {{ ffi_struct.name()|ffi_struct_name }}(ctypes.Structure): + _fields_ = [ + {%- for field in ffi_struct.fields() %} + ("{{ field.name()|var_name }}", {{ field.type_().borrow()|ffi_type_name }}), + {%- endfor %} + ] +{%- when FfiDefinition::Function(func) %} _UniffiLib.{{ func.name() }}.argtypes = ( {%- call py::arg_list_ffi_decl(func) -%} ) _UniffiLib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %} +{%- endmatch %} {%- endfor %} + {# Ensure to call the contract verification only after we defined all functions. -#} _uniffi_check_contract_api_version(_UniffiLib) _uniffi_check_api_checksums(_UniffiLib) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 7e98f7c46f..18dca4743a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -1,14 +1,33 @@ {%- let obj = ci|get_object_definition(name) %} - -class {{ type_name }}: +{%- let (protocol_name, impl_name) = obj|object_names %} +{%- let methods = obj.methods() %} +{%- let protocol_docstring = obj.docstring() %} + +{% include "Protocol.py" %} + +{% if ci.is_name_used_as_error(name) %} +class {{ impl_name }}(Exception): +{%- else %} +class {{ impl_name }}: +{%- endif %} + {%- call py::docstring(obj, 4) %} _pointer: ctypes.c_void_p {%- match obj.primary_constructor() %} {%- when Some with (cons) %} +{%- if cons.is_async() %} + def __init__(self, *args, **kw): + raise ValueError("async constructors not supported.") +{%- else %} def __init__(self, {% call py::arg_list_decl(cons) -%}): + {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} self._pointer = {% call py::to_ffi_call(cons) %} +{%- endif %} {%- when None %} + {# no __init__ means simple construction without a pointer works, which can confuse #} + def __init__(self, *args, **kwargs): + raise ValueError("This class has no default constructor") {%- endmatch %} def __del__(self): @@ -17,6 +36,9 @@ class {{ type_name }}: if pointer is not None: _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer) + def _uniffi_clone_pointer(self): + return _rust_call(_UniffiLib.{{ obj.ffi_object_clone().name() }}, self._pointer) + # Used by alternative constructors or any methods which return this type. @classmethod def _make_instance_(cls, pointer): @@ -29,17 +51,32 @@ class {{ type_name }}: {%- for cons in obj.alternate_constructors() %} @classmethod +{%- if cons.is_async() %} + async def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + {%- call py::docstring(cons, 8) %} + {%- call py::setup_args_extra_indent(cons) %} + + return await _uniffi_rust_call_async( + _UniffiLib.{{ cons.ffi_func().name() }}({% call py::arg_list_lowered(cons) %}), + _UniffiLib.{{ cons.ffi_rust_future_poll(ci) }}, + _UniffiLib.{{ cons.ffi_rust_future_complete(ci) }}, + _UniffiLib.{{ cons.ffi_rust_future_free(ci) }}, + {{ ffi_converter_name }}.lift, + {% call py::error_ffi_converter(cons) %} + ) +{%- else %} def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} # Call the (fallible) function before creating any half-baked object instances. pointer = {% call py::to_ffi_call(cons) %} return cls._make_instance_(pointer) +{%- endif %} {% endfor %} {%- for meth in obj.methods() -%} {%- call py::method_decl(meth.name()|fn_name, meth) %} -{% endfor %} - +{%- endfor %} {%- for tm in obj.uniffi_traits() -%} {%- match tm %} {%- when UniffiTrait::Debug { fmt } %} @@ -51,37 +88,84 @@ class {{ type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %}) + return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", eq) %}) def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %}) + return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", ne) %}) {%- when UniffiTrait::Hash { hash } %} {%- call py::method_decl("__hash__", hash) %} -{% endmatch %} -{% endfor %} - - -class {{ ffi_converter_name }}: +{%- endmatch %} +{%- endfor %} + +{%- if obj.has_callback_interface() %} +{%- let ffi_init_callback = obj.ffi_init_callback() %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} +{% include "CallbackInterfaceImpl.py" %} +{%- endif %} + +{# Objects as error #} +{%- if ci.is_name_used_as_error(name) %} +{# Due to some mismatches in the ffi converter mechanisms, errors are forced to be a RustBuffer #} +class {{ ffi_converter_name }}__as_error(_UniffiConverterRustBuffer): @classmethod def read(cls, buf): - ptr = buf.read_u64() - if ptr == 0: - raise InternalError("Raw pointer value was null") - return cls.lift(ptr) + raise NotImplementedError() @classmethod def write(cls, value, buf): - if not isinstance(value, {{ type_name }}): - raise TypeError("Expected {{ type_name }} instance, {} found".format(type(value).__name__)) - buf.write_u64(cls.lower(value)) + raise NotImplementedError() @staticmethod def lift(value): - return {{ type_name }}._make_instance_(value) + # Errors are always a rust buffer holding a pointer - which is a "read" + with value.consume_with_stream() as stream: + return {{ ffi_converter_name }}.read(stream) @staticmethod def lower(value): - return value._pointer + raise NotImplementedError() + +{%- endif %} + +class {{ ffi_converter_name }}: + {%- if obj.has_callback_interface() %} + _handle_map = _UniffiHandleMap() + {%- endif %} + + @staticmethod + def lift(value: int): + return {{ impl_name }}._make_instance_(value) + + @staticmethod + def check_lower(value: {{ type_name }}): + {%- if obj.has_callback_interface() %} + pass + {%- else %} + if not isinstance(value, {{ impl_name }}): + raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) + {%- endif %} + + @staticmethod + def lower(value: {{ protocol_name }}): + {%- if obj.has_callback_interface() %} + return {{ ffi_converter_name }}._handle_map.insert(value) + {%- else %} + if not isinstance(value, {{ impl_name }}): + raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) + return value._uniffi_clone_pointer() + {%- endif %} + + @classmethod + def read(cls, buf: _UniffiRustBuffer): + ptr = buf.read_u64() + if ptr == 0: + raise InternalError("Raw pointer value was null") + return cls.lift(ptr) + + @classmethod + def write(cls, value: {{ protocol_name }}, buf: _UniffiRustBuffer): + buf.write_u64(cls.lower(value)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py index e406c51d49..4c07ae3e34 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py @@ -2,6 +2,11 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, value): + if value is not None: + {{ inner_ffi_converter }}.check_lower(value) + + @classmethod def write(cls, value, buf): if value is None: buf.write_u8(0) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py deleted file mode 100644 index 23aa28eab4..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py +++ /dev/null @@ -1,68 +0,0 @@ -class _UniffiPointerManagerCPython: - """ - Manage giving out pointers to Python objects on CPython - - This class is used to generate opaque pointers that reference Python objects to pass to Rust. - It assumes a CPython platform. See _UniffiPointerManagerGeneral for the alternative. - """ - - def new_pointer(self, obj): - """ - Get a pointer for an object as a ctypes.c_size_t instance - - Each call to new_pointer() must be balanced with exactly one call to release_pointer() - - This returns a ctypes.c_size_t. This is always the same size as a pointer and can be - interchanged with pointers for FFI function arguments and return values. - """ - # IncRef the object since we're going to pass a pointer to Rust - ctypes.pythonapi.Py_IncRef(ctypes.py_object(obj)) - # id() is the object address on CPython - # (https://docs.python.org/3/library/functions.html#id) - return id(obj) - - def release_pointer(self, address): - py_obj = ctypes.cast(address, ctypes.py_object) - obj = py_obj.value - ctypes.pythonapi.Py_DecRef(py_obj) - return obj - - def lookup(self, address): - return ctypes.cast(address, ctypes.py_object).value - -class _UniffiPointerManagerGeneral: - """ - Manage giving out pointers to Python objects on non-CPython platforms - - This has the same API as _UniffiPointerManagerCPython, but doesn't assume we're running on - CPython and is slightly slower. - - Instead of using real pointers, it maps integer values to objects and returns the keys as - c_size_t values. - """ - - def __init__(self): - self._map = {} - self._lock = threading.Lock() - self._current_handle = 0 - - def new_pointer(self, obj): - with self._lock: - handle = self._current_handle - self._current_handle += 1 - self._map[handle] = obj - return handle - - def release_pointer(self, handle): - with self._lock: - return self._map.pop(handle) - - def lookup(self, handle): - with self._lock: - return self._map[handle] - -# Pick an pointer manager implementation based on the platform -if platform.python_implementation() == 'CPython': - _UniffiPointerManager = _UniffiPointerManagerCPython # type: ignore -else: - _UniffiPointerManager = _UniffiPointerManagerGeneral # type: ignore diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py new file mode 100644 index 0000000000..3b7e93596a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py @@ -0,0 +1,9 @@ +class {{ protocol_name }}(typing.Protocol): + {%- call py::docstring_value(protocol_docstring, 4) %} + {%- for meth in methods.iter() %} + def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}): + {%- call py::docstring(meth, 8) %} + raise NotImplementedError + {%- else %} + pass + {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py index 99a30e120f..0b5634eb52 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py @@ -1,11 +1,14 @@ {%- let rec = ci|get_record_definition(name) %} class {{ type_name }}: - {% for field in rec.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- call py::docstring(rec, 4) %} + {%- for field in rec.fields() %} + {{ field.name()|var_name }}: "{{ field|type_name }}" + {%- call py::docstring(field, 4) %} {%- endfor %} + {%- if rec.has_fields() %} @typing.no_type_check - def __init__(self, {% for field in rec.fields() %} + def __init__(self, *, {% for field in rec.fields() %} {{- field.name()|var_name }}: "{{- field|type_name }}" {%- if field.default_value().is_some() %} = _DEFAULT{% endif %} {%- if !loop.last %}, {% endif %} @@ -22,6 +25,7 @@ class {{ type_name }}: self.{{ field_name }} = {{ field_name }} {%- endmatch %} {%- endfor %} + {%- endif %} def __str__(self): return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) @@ -43,7 +47,21 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): ) @staticmethod + def check_lower(value): + {%- if rec.fields().is_empty() %} + pass + {%- else %} + {%- for field in rec.fields() %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + {%- endif %} + + @staticmethod def write(value, buf): + {%- if rec.has_fields() %} {%- for field in rec.fields() %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) {%- endfor %} + {%- else %} + pass + {%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py index daabd5b4b9..4db74fb157 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py @@ -1,28 +1,16 @@ # Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI. class _UniffiConverterPrimitive: @classmethod - def check(cls, value): - return value - - @classmethod def lift(cls, value): return value @classmethod def lower(cls, value): - return cls.lowerUnchecked(cls.check(value)) - - @classmethod - def lowerUnchecked(cls, value): return value - @classmethod - def write(cls, value, buf): - cls.write_unchecked(cls.check(value), buf) - class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): @classmethod - def check(cls, value): + def check_lower(cls, value): try: value = value.__index__() except Exception: @@ -31,18 +19,16 @@ class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__)) if not cls.VALUE_MIN <= value < cls.VALUE_MAX: raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX)) - return super().check(value) class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive): @classmethod - def check(cls, value): + def check_lower(cls, value): try: value = value.__float__() except Exception: raise TypeError("must be real number, not {}".format(type(value).__name__)) if not isinstance(value, float): raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__)) - return super().check(value) # Helper class for wrapper types that will always go through a _UniffiRustBuffer. # Classes should inherit from this and implement the `read` and `write` static methods. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py index c317a632fc..44e0ba1001 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py @@ -1,12 +1,16 @@ class _UniffiRustBuffer(ctypes.Structure): _fields_ = [ - ("capacity", ctypes.c_int32), - ("len", ctypes.c_int32), + ("capacity", ctypes.c_uint64), + ("len", ctypes.c_uint64), ("data", ctypes.POINTER(ctypes.c_char)), ] @staticmethod + def default(): + return _UniffiRustBuffer(0, 0, None) + + @staticmethod def alloc(size): return _rust_call(_UniffiLib.{{ ci.ffi_rustbuffer_alloc().name() }}, size) @@ -137,9 +141,6 @@ class _UniffiRustBufferStream: def read_double(self): return self._unpack_from(8, ">d") - def read_c_size_t(self): - return self._unpack_from(ctypes.sizeof(ctypes.c_size_t) , "@N") - class _UniffiRustBufferBuilder: """ Helper for structured writing of bytes into a _UniffiRustBuffer. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py index 3c9f5a4596..3c30d9f9f5 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py @@ -2,6 +2,11 @@ class {{ ffi_converter_name}}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, value): + for item in value: + {{ inner_ffi_converter }}.check_lower(item) + + @classmethod def write(cls, value, buf): items = len(value) buf.write_i32(items) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py index 40890b6abc..d574edd09f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py @@ -1,6 +1,6 @@ class _UniffiConverterString: @staticmethod - def check(value): + def check_lower(value): if not isinstance(value, str): raise TypeError("argument must be str, not {}".format(type(value).__name__)) return value @@ -15,7 +15,6 @@ class _UniffiConverterString: @staticmethod def write(value, buf): - value = _UniffiConverterString.check(value) utf8_bytes = value.encode("utf-8") buf.write_i32(len(utf8_bytes)) buf.write(utf8_bytes) @@ -27,7 +26,6 @@ class _UniffiConverterString: @staticmethod def lower(value): - value = _UniffiConverterString.check(value) with _UniffiRustBuffer.alloc_with_builder() as builder: builder.write(value.encode("utf-8")) return builder.finalize() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py index 8402f6095d..76d7f8bcdb 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py @@ -18,6 +18,10 @@ class _UniffiConverterTimestamp(_UniffiConverterRustBuffer): return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds) @staticmethod + def check_lower(value): + pass + + @staticmethod def write(value, buf): if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc): sign = 1 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py index f258b60a1c..230b9e853f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py @@ -1,7 +1,15 @@ {%- if func.is_async() %} -def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): - return _uniffi_rust_call_async( +{%- match func.return_type() -%} +{%- when Some with (return_type) %} +async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": +{% when None %} +async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None: +{% endmatch %} + + {%- call py::docstring(func, 4) %} + {%- call py::setup_args(func) %} + return await _uniffi_rust_call_async( _UniffiLib.{{ func.ffi_func().name() }}({% call py::arg_list_lowered(func) %}), _UniffiLib.{{func.ffi_rust_future_poll(ci) }}, _UniffiLib.{{func.ffi_rust_future_complete(ci) }}, @@ -13,13 +21,7 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): {%- when None %} lambda val: None, {% endmatch %} - # Error FFI converter - {%- match func.throws_type() %} - {%- when Some(e) %} - {{ e|ffi_converter_name }}, - {%- when None %} - None, - {%- endmatch %} + {% call py::error_ffi_converter(func) %} ) {%- else %} @@ -27,11 +29,13 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): {%- when Some with (return_type) %} def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": + {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %}) {% when None %} -def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): +def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None: + {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} {% call py::to_ffi_call(func) %} {% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py index 5e05314c37..4aaed253e0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py @@ -85,7 +85,7 @@ {%- when Type::Map { key_type, value_type } %} {%- include "MapTemplate.py" %} -{%- when Type::CallbackInterface { name: id, module_path } %} +{%- when Type::CallbackInterface { name, module_path } %} {%- include "CallbackInterfaceTemplate.py" %} {%- when Type::Custom { name, module_path, builtin } %} @@ -94,9 +94,6 @@ {%- when Type::External { name, module_path, namespace, kind, tagged } %} {%- include "ExternalTemplate.py" %} -{%- when Type::ForeignExecutor %} -{%- include "ForeignExecutorTemplate.py" %} - {%- else %} {%- endmatch %} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py index 081c6731ce..039bf76162 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt16(_UniffiConverterPrimitiveInt): return buf.read_u16() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u16(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py index b80e75177d..1650bf9b60 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt32(_UniffiConverterPrimitiveInt): return buf.read_u32() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u32(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py index 4b87e58547..f354545e26 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt64(_UniffiConverterPrimitiveInt): return buf.read_u64() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u64(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py index 33026706f2..cee130b4d9 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt8(_UniffiConverterPrimitiveInt): return buf.read_u8() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u8(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py index ef3b1bb94d..6818a8c107 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -5,27 +5,29 @@ #} {%- macro to_ffi_call(func) -%} - {%- match func.throws_type() -%} - {%- when Some with (e) -%} -_rust_call_with_error({{ e|ffi_converter_name }}, - {%- else -%} -_rust_call( - {%- endmatch -%} - _UniffiLib.{{ func.ffi_func().name() }}, - {%- call arg_list_lowered(func) -%} -) +{%- call _to_ffi_call_with_prefix_arg("", func) %} {%- endmacro -%} {%- macro to_ffi_call_with_prefix(prefix, func) -%} - {%- match func.throws_type() -%} - {%- when Some with (e) -%} -_rust_call_with_error( - {{ e|ffi_converter_name }}, - {%- else -%} +{%- call _to_ffi_call_with_prefix_arg(format!("{},", prefix), func) %} +{%- endmacro -%} + +{%- macro _to_ffi_call_with_prefix_arg(prefix, func) -%} +{%- match func.throws_type() -%} +{%- when Some with (e) -%} +{%- match e -%} +{%- when Type::Enum { name, module_path } -%} +_rust_call_with_error({{ e|ffi_converter_name }}, +{%- when Type::Object { name, module_path, imp } -%} +_rust_call_with_error({{ e|ffi_converter_name }}__as_error, +{%- else %} +# unsupported error type! +{%- endmatch %} +{%- else -%} _rust_call( - {%- endmatch -%} +{%- endmatch -%} _UniffiLib.{{ func.ffi_func().name() }}, - {{- prefix }}, + {{- prefix }} {%- call arg_list_lowered(func) -%} ) {%- endmacro -%} @@ -37,6 +39,19 @@ _rust_call( {%- endfor %} {%- endmacro -%} +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{{ "" }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} + {#- // Arglist as used in Python declarations of methods, functions and constructors. // Note the var_name and type_name filters. @@ -76,6 +91,7 @@ _rust_call( if {{ arg.name()|var_name }} is _DEFAULT: {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} {%- endmatch %} + {{ arg|check_lower_fn }}({{ arg.name()|var_name }}) {% endfor -%} {%- endmacro -%} @@ -91,6 +107,7 @@ _rust_call( if {{ arg.name()|var_name }} is _DEFAULT: {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} {%- endmatch %} + {{ arg|check_lower_fn }}({{ arg.name()|var_name }}) {% endfor -%} {%- endmacro -%} @@ -100,11 +117,18 @@ _rust_call( {%- macro method_decl(py_method_name, meth) %} {% if meth.is_async() %} - def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): +{%- match meth.return_type() %} +{%- when Some with (return_type) %} + async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": +{%- when None %} + async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None: +{% endmatch %} + + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} - return _uniffi_rust_call_async( + return await _uniffi_rust_call_async( _UniffiLib.{{ meth.ffi_func().name() }}( - self._pointer, {% call arg_list_lowered(meth) %} + self._uniffi_clone_pointer(), {% call arg_list_lowered(meth) %} ), _UniffiLib.{{ meth.ffi_rust_future_poll(ci) }}, _UniffiLib.{{ meth.ffi_rust_future_complete(ci) }}, @@ -116,13 +140,7 @@ _rust_call( {%- when None %} lambda val: None, {% endmatch %} - # Error FFI converter - {%- match meth.throws_type() %} - {%- when Some(e) %} - {{ e|ffi_converter_name }}, - {%- when None %} - None, - {%- endmatch %} + {% call error_ffi_converter(meth) %} ) {%- else -%} @@ -131,17 +149,36 @@ _rust_call( {%- when Some with (return_type) %} def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} return {{ return_type|lift_fn }}( - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %} ) {%- when None %} - def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): + def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None: + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %} {% endmatch %} {% endif %} {% endmacro %} + +{%- macro error_ffi_converter(func) %} + # Error FFI converter +{% match func.throws_type() %} +{%- when Some(e) %} +{%- match e -%} +{%- when Type::Enum { name, module_path } -%} + {{ e|ffi_converter_name }}, +{%- when Type::Object { name, module_path, imp } -%} + {{ e|ffi_converter_name }}__as_error, +{%- else %} + # unsupported error type! +{%- endmatch %} +{%- when None %} + None, +{%- endmatch %} +{% endmacro %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py index 24c3290ff7..1ccd6821c0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py @@ -1,3 +1,5 @@ +{%- call py::docstring_value(ci.namespace_docstring(), 0) %} + # This file was autogenerated by some hot garbage in the `uniffi` crate. # Trust me, you don't want to mess with it! @@ -13,6 +15,7 @@ # compile the rust component. The easiest way to ensure this is to bundle the Python # helpers directly inline like we're doing here. +from __future__ import annotations import os import sys import ctypes @@ -20,6 +23,9 @@ import enum import struct import contextlib import datetime +import threading +import itertools +import traceback import typing {%- if ci.has_async_fns() %} import asyncio @@ -34,20 +40,20 @@ _DEFAULT = object() {% include "RustBufferTemplate.py" %} {% include "Helpers.py" %} -{% include "PointerManager.py" %} +{% include "HandleMap.py" %} {% include "RustBufferHelper.py" %} # Contains loading, initialization code, and the FFI Function declarations. {% include "NamespaceLibraryTemplate.py" %} +# Public interface members begin here. +{{ type_helper_code }} + # Async support {%- if ci.has_async_fns() %} {%- include "Async.py" %} {%- endif %} -# Public interface members begin here. -{{ type_helper_code }} - {%- for func in ci.function_definitions() %} {%- include "TopLevelFunctionTemplate.py" %} {%- endfor %} @@ -69,6 +75,9 @@ __all__ = [ {%- for c in ci.callback_interface_definitions() %} "{{ c.name()|class_name }}", {%- endfor %} + {%- if ci.has_async_fns() %} + "uniffi_set_event_loop", + {%- endif %} ] {% import "macros.py" as py %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs index 0fcf09996f..0c23140b33 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs @@ -2,10 +2,8 @@ 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 crate::bindings::TargetLanguage; +use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; use anyhow::{Context, Result}; use camino::Utf8Path; use std::env; @@ -34,14 +32,18 @@ pub fn run_script( args: Vec<String>, _options: &RunScriptOptions, ) -> Result<()> { - let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let script_path = Utf8Path::new(script_file).canonicalize_utf8()?; 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::Python], + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Python], + try_format_code: false, + }, + None, &out_dir, false, )?; diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index 1f1bf8e299..04841b459c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -2,15 +2,39 @@ * 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 anyhow::{bail, Result}; use askama::Template; +use camino::Utf8Path; use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use std::collections::HashMap; +use crate::bindings::ruby; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; + +pub struct RubyBindingGenerator; +impl BindingGenerator for RubyBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + ruby::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + if cdylib_name.is_none() { + bail!("Generate bindings for Ruby requires a cdylib, but {library_path} was given"); + } + Ok(()) + } +} const RESERVED_WORDS: &[&str] = &[ "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", @@ -57,7 +81,6 @@ pub fn canonical_name(t: &Type) -> String { Type::CallbackInterface { name, .. } => format!("CallbackInterface{name}"), Type::Timestamp => "Timestamp".into(), Type::Duration => "Duration".into(), - Type::ForeignExecutor => "ForeignExecutor".into(), // Recursive types. // These add a prefix to the name of the underlying type. // The component API definition cannot give names to recursive types, so as long as the @@ -150,20 +173,20 @@ mod filters { FfiType::UInt64 => ":uint64".to_string(), FfiType::Float32 => ":float".to_string(), FfiType::Float64 => ":double".to_string(), + FfiType::Handle => ":uint64".to_string(), FfiType::RustArcPtr(_) => ":pointer".to_string(), FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(), + FfiType::RustCallStatus => "RustCallStatus".to_string(), FfiType::ForeignBytes => "ForeignBytes".to_string(), - FfiType::ForeignCallback => unimplemented!("Callback interfaces are not implemented"), - FfiType::ForeignExecutorCallback => { - unimplemented!("Foreign executors are not implemented") - } - FfiType::ForeignExecutorHandle => { - unimplemented!("Foreign executors are not implemented") - } - FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback - | FfiType::RustFutureContinuationData => { - unimplemented!("Async functions are not implemented") + FfiType::Callback(_) => unimplemented!("FFI Callbacks not implemented"), + // Note: this can't just be `unimplemented!()` because some of the FFI function + // definitions use references. Those FFI functions aren't actually used, so we just + // pick something that runs and makes some sense. Revisit this once the references + // are actually implemented. + FfiType::Reference(_) => ":pointer".to_string(), + FfiType::VoidPointer => ":pointer".to_string(), + FfiType::Struct(_) => { + unimplemented!("Structs are not implemented") } }) } @@ -179,7 +202,8 @@ mod filters { } // use the double-quote form to match with the other languages, and quote escapes. Literal::String(s) => format!("\"{s}\""), - Literal::Null => "nil".into(), + Literal::None => "nil".into(), + Literal::Some { inner } => literal_rb(inner)?, Literal::EmptySequence => "[]".into(), Literal::EmptyMap => "{}".into(), Literal::Enum(v, type_) => match type_ { @@ -264,7 +288,24 @@ mod filters { } Type::External { .. } => panic!("No support for external types, yet"), Type::Custom { .. } => panic!("No support for custom types, yet"), - Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), + }) + } + + pub fn check_lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> { + Ok(match type_ { + Type::Object { name, .. } => { + format!("({}.uniffi_check_lower {nm})", class_name_rb(name)?) + } + Type::Enum { .. } + | Type::Record { .. } + | Type::Optional { .. } + | Type::Sequence { .. } + | Type::Map { .. } => format!( + "RustBuffer.check_lower_{}({})", + class_name_rb(&canonical_name(type_))?, + nm + ), + _ => "".to_owned(), }) } @@ -283,7 +324,7 @@ mod filters { Type::Boolean => format!("({nm} ? 1 : 0)"), Type::String => format!("RustBuffer.allocFromString({nm})"), Type::Bytes => format!("RustBuffer.allocFromBytes({nm})"), - Type::Object { name, .. } => format!("({}._uniffi_lower {nm})", class_name_rb(name)?), + Type::Object { name, .. } => format!("({}.uniffi_lower {nm})", class_name_rb(name)?), Type::CallbackInterface { .. } => { panic!("No support for lowering callback interfaces yet") } @@ -300,7 +341,6 @@ mod filters { ), Type::External { .. } => panic!("No support for lowering external types, yet"), Type::Custom { .. } => panic!("No support for lowering custom types, yet"), - Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), }) } @@ -318,7 +358,7 @@ mod filters { Type::Boolean => format!("1 == {nm}"), Type::String => format!("{nm}.consumeIntoString"), Type::Bytes => format!("{nm}.consumeIntoBytes"), - Type::Object { name, .. } => format!("{}._uniffi_allocate({nm})", class_name_rb(name)?), + Type::Object { name, .. } => format!("{}.uniffi_allocate({nm})", class_name_rb(name)?), Type::CallbackInterface { .. } => { panic!("No support for lifting callback interfaces, yet") } @@ -341,7 +381,6 @@ mod filters { ), Type::External { .. } => panic!("No support for lifting external types, yet"), Type::Custom { .. } => panic!("No support for lifting custom types, yet"), - Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), }) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb index 677c5c729b..ba2caf7380 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb @@ -2,18 +2,18 @@ class {{ obj.name()|class_name_rb }} # A private helper for initializing instances of the class from a raw pointer, # bypassing any initialization logic and ensuring they are GC'd properly. - def self._uniffi_allocate(pointer) + def self.uniffi_allocate(pointer) pointer.autorelease = false inst = allocate inst.instance_variable_set :@pointer, pointer - ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_pointer(pointer, inst.object_id)) + ObjectSpace.define_finalizer(inst, uniffi_define_finalizer_by_pointer(pointer, inst.object_id)) return inst end # A private helper for registering an object finalizer. # N.B. it's important that this does not capture a reference # to the actual instance, only its underlying pointer. - def self._uniffi_define_finalizer_by_pointer(pointer, object_id) + def self.uniffi_define_finalizer_by_pointer(pointer, object_id) Proc.new do |_id| {{ ci.namespace()|class_name_rb }}.rust_call( :{{ obj.ffi_object_free().name() }}, @@ -25,31 +25,41 @@ class {{ obj.name()|class_name_rb }} # A private helper for lowering instances into a raw pointer. # This does an explicit typecheck, because accidentally lowering a different type of # object in a place where this type is expected, could lead to memory unsafety. - def self._uniffi_lower(inst) + def self.uniffi_check_lower(inst) if not inst.is_a? self raise TypeError.new "Expected a {{ obj.name()|class_name_rb }} instance, got #{inst}" end - return inst.instance_variable_get :@pointer + end + + def uniffi_clone_pointer() + return {{ ci.namespace()|class_name_rb }}.rust_call( + :{{ obj.ffi_object_clone().name() }}, + @pointer + ) + end + + def self.uniffi_lower(inst) + return inst.uniffi_clone_pointer() end {%- match obj.primary_constructor() %} {%- when Some with (cons) %} def initialize({% call rb::arg_list_decl(cons) -%}) - {%- call rb::coerce_args_extra_indent(cons) %} + {%- call rb::setup_args_extra_indent(cons) %} pointer = {% call rb::to_ffi_call(cons) %} @pointer = pointer - ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_pointer(pointer, self.object_id)) + ObjectSpace.define_finalizer(self, self.class.uniffi_define_finalizer_by_pointer(pointer, self.object_id)) end {%- when None %} {%- endmatch %} {% for cons in obj.alternate_constructors() -%} def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %}) - {%- call rb::coerce_args_extra_indent(cons) %} + {%- call rb::setup_args_extra_indent(cons) %} # Call the (fallible) function before creating any half-baked object instances. # Lightly yucky way to bypass the usual "initialize" logic # and just create a new instance with the required pointer. - return _uniffi_allocate({% call rb::to_ffi_call(cons) %}) + return uniffi_allocate({% call rb::to_ffi_call(cons) %}) end {% endfor %} @@ -58,15 +68,15 @@ class {{ obj.name()|class_name_rb }} {%- when Some with (return_type) -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) - {%- call rb::coerce_args_extra_indent(meth) %} - result = {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + {%- call rb::setup_args_extra_indent(meth) %} + result = {% call rb::to_ffi_call_with_prefix("uniffi_clone_pointer()", meth) %} return {{ "result"|lift_rb(return_type) }} end {%- when None -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) - {%- call rb::coerce_args_extra_indent(meth) %} - {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + {%- call rb::setup_args_extra_indent(meth) %} + {% call rb::to_ffi_call_with_prefix("uniffi_clone_pointer()", meth) %} end {% endmatch %} {% endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb index c940b31060..b5a201b248 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb @@ -2,7 +2,12 @@ class {{ rec.name()|class_name_rb }} attr_reader {% for field in rec.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %} - def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) + def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb -}}: + {%- match field.default_value() %} + {%- when Some with(literal) %} {{ literal|literal_rb }} + {%- else %} + {%- endmatch %} + {%- if loop.last %}{% else %}, {% endif -%}{% endfor %}) {%- for field in rec.fields() %} @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb index 8749139116..d15c0bbe76 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb @@ -163,7 +163,7 @@ class RustBufferBuilder # The Object type {{ object_name }}. def write_{{ canonical_type_name }}(obj) - pointer = {{ object_name|class_name_rb}}._uniffi_lower obj + pointer = {{ object_name|class_name_rb}}.uniffi_lower obj pack_into(8, 'Q>', pointer.address) end diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb index b085dddf15..f9b0806abc 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb @@ -155,7 +155,7 @@ class RustBufferStream def read{{ canonical_type_name }} pointer = FFI::Pointer.new unpack_from 8, 'Q>' - return {{ object_name|class_name_rb }}._uniffi_allocate(pointer) + return {{ object_name|class_name_rb }}.uniffi_allocate(pointer) end {% when Type::Enum { name, module_path } -%} @@ -237,7 +237,7 @@ class RustBufferStream def read{{ canonical_type_name }} {{ rec.name()|class_name_rb }}.new( {%- for field in rec.fields() %} - read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %} + {{ field.name()|var_name_rb }}: read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %} {%- endfor %} ) end diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb index 0194c9666d..452d9831cd 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb @@ -1,6 +1,6 @@ class RustBuffer < FFI::Struct - layout :capacity, :int32, - :len, :int32, + layout :capacity, :uint64, + :len, :uint64, :data, :pointer def self.alloc(size) @@ -128,6 +128,12 @@ class RustBuffer < FFI::Struct {%- let rec = ci|get_record_definition(record_name) -%} # The Record type {{ record_name }}. + def self.check_lower_{{ canonical_type_name }}(v) + {%- for field in rec.fields() %} + {{ "v.{}"|format(field.name()|var_name_rb)|check_lower_rb(field.as_type().borrow()) }} + {%- endfor %} + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -146,6 +152,19 @@ class RustBuffer < FFI::Struct {%- let e = ci|get_enum_definition(enum_name) -%} # The Enum type {{ enum_name }}. + def self.check_lower_{{ canonical_type_name }}(v) + {%- if !e.is_flat() %} + {%- for variant in e.variants() %} + if v.{{ variant.name()|var_name_rb }}? + {%- for field in variant.fields() %} + {{ "v.{}"|format(field.name())|check_lower_rb(field.as_type().borrow()) }} + {%- endfor %} + return + end + {%- endfor %} + {%- endif %} + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -163,6 +182,12 @@ class RustBuffer < FFI::Struct {% when Type::Optional { inner_type } -%} # The Optional<T> type for {{ canonical_name(inner_type) }}. + def self.check_lower_{{ canonical_type_name }}(v) + if not v.nil? + {{ "v"|check_lower_rb(inner_type.borrow()) }} + end + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -179,6 +204,12 @@ class RustBuffer < FFI::Struct {% when Type::Sequence { inner_type } -%} # The Sequence<T> type for {{ canonical_name(inner_type) }}. + def self.check_lower_{{ canonical_type_name }}(v) + v.each do |item| + {{ "item"|check_lower_rb(inner_type.borrow()) }} + end + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -195,6 +226,13 @@ class RustBuffer < FFI::Struct {% when Type::Map { key_type: k, value_type: inner_type } -%} # The Map<T> type for {{ canonical_name(inner_type) }}. + def self.check_lower_{{ canonical_type_name }}(v) + v.each do |k, v| + {{ "k"|check_lower_rb(k.borrow()) }} + {{ "v"|check_lower_rb(inner_type.borrow()) }} + end + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb index 13214cf31b..b6dce0effa 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb @@ -2,7 +2,7 @@ {%- when Some with (return_type) %} def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) - {%- call rb::coerce_args(func) %} + {%- call rb::setup_args(func) %} result = {% call rb::to_ffi_call(func) %} return {{ "result"|lift_rb(return_type) }} end @@ -10,7 +10,7 @@ end {% when None %} def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) - {%- call rb::coerce_args(func) %} + {%- call rb::setup_args(func) %} {% call rb::to_ffi_call(func) %} end {% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb index 8dc3e5e613..59fa4ef4cc 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb @@ -60,14 +60,16 @@ [{%- for arg in func.arguments() -%}{{ arg.type_().borrow()|type_ffi }}, {% endfor -%} RustCallStatus.by_ref] {%- endmacro -%} -{%- macro coerce_args(func) %} +{%- macro setup_args(func) %} {%- for arg in func.arguments() %} - {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) -}} + {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }} + {{ arg.name()|check_lower_rb(arg.as_type().borrow()) }} {% endfor -%} {%- endmacro -%} -{%- macro coerce_args_extra_indent(func) %} - {%- for arg in func.arguments() %} +{%- macro setup_args_extra_indent(meth) %} + {%- for arg in meth.arguments() %} {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }} + {{ arg.name()|check_lower_rb(arg.as_type().borrow()) }} {%- endfor %} {%- endmacro -%} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs index 03da37d567..88f770b9dc 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs @@ -4,6 +4,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this use crate::bindings::TargetLanguage; use crate::library_mode::generate_bindings; +use crate::BindingGeneratorDefault; use anyhow::{bail, Context, Result}; use camino::Utf8Path; use std::env; @@ -30,11 +31,21 @@ pub fn test_script_command( fixture_name: &str, script_file: &str, ) -> Result<Command> { - let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let script_path = Utf8Path::new(script_file).canonicalize_utf8()?; let test_helper = UniFFITestHelper::new(fixture_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::Ruby], &out_dir, false)?; + generate_bindings( + &cdylib_path, + None, + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Ruby], + try_format_code: false, + }, + None, + &out_dir, + false, + )?; let rubypath = env::var_os("RUBYLIB").unwrap_or_else(|| OsString::from("")); let rubypath = env::join_paths( diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs index 5d8b37e0af..dab89e0259 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs @@ -6,21 +6,25 @@ use super::CodeType; #[derive(Debug)] pub struct CallbackInterfaceCodeType { - id: String, + name: String, } impl CallbackInterfaceCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(name: String) -> Self { + Self { name } } } impl CodeType for CallbackInterfaceCodeType { fn type_label(&self) -> String { - super::SwiftCodeOracle.class_name(&self.id) + super::SwiftCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { format!("CallbackInterface{}", self.type_label()) } + + fn initialization_fn(&self) -> Option<String> { + Some(format!("uniffiCallbackInit{}", self.name)) + } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs index 8e6dddf3f9..d89fdfd386 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs @@ -30,8 +30,9 @@ impl CodeType for OptionalCodeType { fn literal(&self, literal: &Literal) -> String { match literal { - Literal::Null => "nil".into(), - _ => super::SwiftCodeOracle.find(&self.inner).literal(literal), + Literal::None => "nil".into(), + Literal::Some { inner } => super::SwiftCodeOracle.find(&self.inner).literal(inner), + _ => panic!("Invalid literal for Optional type: {literal:?}"), } } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs deleted file mode 100644 index b488b004cf..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* 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; - -#[derive(Debug)] -pub struct ForeignExecutorCodeType; - -impl CodeType for ForeignExecutorCodeType { - fn type_label(&self) -> String { - // On Swift, we define a struct to represent a ForeignExecutor - "UniFfiForeignExecutor".into() - } - - fn canonical_name(&self) -> String { - "ForeignExecutor".into() - } - - fn initialization_fn(&self) -> Option<String> { - Some("uniffiInitForeignExecutor".into()) - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs index 0b6728ba84..3960b7aae1 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs @@ -17,7 +17,7 @@ impl ExternalCodeType { impl CodeType for ExternalCodeType { fn type_label(&self) -> String { - self.name.clone() + super::SwiftCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 12db4afc66..16c1625123 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -10,25 +10,49 @@ use std::fmt::Debug; use anyhow::{Context, Result}; use askama::Template; -use heck::{ToLowerCamelCase, ToUpperCamelCase}; +use camino::Utf8Path; +use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use super::Bindings; use crate::backend::TemplateExpression; +use crate::bindings::swift; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; mod callback_interface; mod compounds; mod custom; mod enum_; -mod executor; mod external; mod miscellany; mod object; mod primitives; mod record; +pub struct SwiftBindingGenerator; +impl BindingGenerator for SwiftBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + swift::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path( + &self, + _library_path: &Utf8Path, + _cdylib_name: Option<&str>, + ) -> Result<()> { + Ok(()) + } +} + /// A trait tor the implementation. trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in @@ -196,6 +220,8 @@ pub struct Config { ffi_module_filename: Option<String>, generate_module_map: Option<bool>, omit_argument_labels: Option<bool>, + generate_immutable_records: Option<bool>, + experimental_sendable_value_types: Option<bool>, #[serde(default)] custom_types: HashMap<String, CustomTypeConfig>, } @@ -261,6 +287,16 @@ impl Config { pub fn omit_argument_labels(&self) -> bool { self.omit_argument_labels.unwrap_or(false) } + + /// Whether to generate immutable records (`let` instead of `var`) + pub fn generate_immutable_records(&self) -> bool { + self.generate_immutable_records.unwrap_or(false) + } + + /// Whether to mark value types as 'Sendable' + pub fn experimental_sendable_value_types(&self) -> bool { + self.experimental_sendable_value_types.unwrap_or(false) + } } impl BindingsConfig for Config { @@ -400,7 +436,6 @@ pub struct SwiftWrapper<'a> { ci: &'a ComponentInterface, type_helper_code: String, type_imports: BTreeSet<String>, - has_async_fns: bool, } impl<'a> SwiftWrapper<'a> { pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { @@ -412,7 +447,6 @@ impl<'a> SwiftWrapper<'a> { ci, type_helper_code, type_imports, - has_async_fns: ci.has_async_fns(), } } @@ -425,10 +459,6 @@ impl<'a> SwiftWrapper<'a> { .iter_types() .map(|t| SwiftCodeOracle.find(t)) .filter_map(|ct| ct.initialization_fn()) - .chain( - self.has_async_fns - .then(|| "uniffiInitContinuationCallback".into()), - ) .collect() } } @@ -464,12 +494,11 @@ impl SwiftCodeOracle { 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::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)), Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } - Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) } @@ -509,7 +538,22 @@ impl SwiftCodeOracle { nm.to_string().to_lower_camel_case() } - fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String { + /// Get the idiomatic Swift rendering of an FFI callback function name + fn ffi_callback_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + /// Get the idiomatic Swift rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + /// Get the idiomatic Swift rendering of an if guard name + fn if_guard_name(&self, nm: &str) -> String { + format!("UNIFFI_FFIDEF_{}", nm.to_shouty_snake_case()) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "Int8".into(), FfiType::UInt8 => "UInt8".into(), @@ -521,40 +565,74 @@ impl SwiftCodeOracle { FfiType::UInt64 => "UInt64".into(), FfiType::Float32 => "Float".into(), FfiType::Float64 => "Double".into(), + FfiType::Handle => "UInt64".into(), FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::RustCallStatus => "RustCallStatus".into(), FfiType::ForeignBytes => "ForeignBytes".into(), - FfiType::ForeignCallback => "ForeignCallback".into(), - FfiType::ForeignExecutorHandle => "Int".into(), - FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(), - FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(), - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "UnsafeMutableRawPointer".into() + // Note: @escaping is required for Swift versions before 5.7 for callbacks passed into + // async functions. Swift 5.7 and later does not require it. We should probably remove + // it once we upgrade our minimum requirement to 5.7 or later. + FfiType::Callback(name) => format!("@escaping {}", self.ffi_callback_name(name)), + FfiType::Struct(name) => self.ffi_struct_name(name), + FfiType::Reference(inner) => { + format!("UnsafeMutablePointer<{}>", self.ffi_type_label(inner)) } + FfiType::VoidPointer => "UnsafeMutableRawPointer".into(), } } - fn ffi_type_label(&self, ffi_type: &FfiType) -> String { - match ffi_type { - FfiType::ForeignCallback - | FfiType::ForeignExecutorCallback - | FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback - | FfiType::RustFutureContinuationData => { - format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type)) - } - _ => self.ffi_type_label_raw(ffi_type), + /// Default values for FFI types + /// + /// Used to set a default return value when returning an error + fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String { + match return_type { + Some(t) => match t { + FfiType::UInt8 + | FfiType::Int8 + | FfiType::UInt16 + | FfiType::Int16 + | FfiType::UInt32 + | FfiType::Int32 + | FfiType::UInt64 + | FfiType::Int64 => "0".to_owned(), + FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(), + FfiType::RustArcPtr(_) => "nil".to_owned(), + FfiType::RustBuffer(_) => "RustBuffer.empty()".to_owned(), + _ => unimplemented!("FFI return type: {t:?}"), + }, + // When we need to use a value for void returns, we use a `u8` placeholder + None => "0".to_owned(), } } fn ffi_canonical_name(&self, ffi_type: &FfiType) -> String { - self.ffi_type_label_raw(ffi_type) + self.ffi_type_label(ffi_type) + } + + /// Get the name of the protocol and class name for an object. + /// + /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that. + /// Otherwise, the class name is the object name and the protocol name is derived from that. + /// + /// This split determines what types `FfiConverter.lower()` inputs. If we support callback + /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower + /// only lowers the concrete class. + fn object_names(&self, obj: &Object) -> (String, String) { + let class_name = self.class_name(obj.name()); + if obj.has_callback_interface() { + let impl_name = format!("{class_name}Impl"); + (class_name, impl_name) + } else { + (format!("{class_name}Protocol"), class_name) + } } } pub mod filters { use super::*; pub use crate::backend::filters::*; + use uniffi_meta::LiteralMetadata; fn oracle() -> &'static SwiftCodeOracle { &SwiftCodeOracle @@ -564,6 +642,13 @@ pub mod filters { Ok(oracle().find(&as_type.as_type()).type_label()) } + pub fn return_type_name(as_type: Option<&impl AsType>) -> Result<String, askama::Error> { + Ok(match as_type { + Some(as_type) => oracle().find(&as_type.as_type()).type_label(), + None => "()".to_owned(), + }) + } + pub fn canonical_name(as_type: &impl AsType) -> Result<String, askama::Error> { Ok(oracle().find(&as_type.as_type()).canonical_name()) } @@ -572,6 +657,15 @@ pub mod filters { Ok(oracle().find(&as_type.as_type()).ffi_converter_name()) } + pub fn ffi_error_converter_name(as_type: &impl AsType) -> Result<String, askama::Error> { + // special handling for types used as errors. + let mut name = oracle().find(&as_type.as_type()).ffi_converter_name(); + if matches!(&as_type.as_type(), Type::Object { .. }) { + name.push_str("__as_error") + } + Ok(name) + } + pub fn lower_fn(as_type: &impl AsType) -> Result<String, askama::Error> { Ok(oracle().find(&as_type.as_type()).lower()) } @@ -595,6 +689,16 @@ pub mod filters { Ok(oracle().find(&as_type.as_type()).literal(literal)) } + // Get the idiomatic Swift rendering of an individual enum variant's discriminant + pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> { + let literal = e.variant_discr(*index).expect("invalid index"); + match literal { + LiteralMetadata::UInt(v, _, _) => Ok(v.to_string()), + LiteralMetadata::Int(v, _, _) => Ok(v.to_string()), + _ => unreachable!("expected an UInt!"), + } + } + /// Get the Swift type for an FFIType pub fn ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> { Ok(oracle().ffi_type_label(ffi_type)) @@ -604,6 +708,10 @@ pub mod filters { Ok(oracle().ffi_canonical_name(ffi_type)) } + pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> { + Ok(oracle().ffi_default_value(return_type.as_ref())) + } + /// Like `ffi_type_name`, but used in `BridgingHeaderTemplate.h` which uses a slightly different /// names. pub fn header_ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> { @@ -618,18 +726,17 @@ pub mod filters { FfiType::UInt64 => "uint64_t".into(), FfiType::Float32 => "float".into(), FfiType::Float64 => "double".into(), + FfiType::Handle => "uint64_t".into(), FfiType::RustArcPtr(_) => "void*_Nonnull".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::RustCallStatus => "RustCallStatus".into(), FfiType::ForeignBytes => "ForeignBytes".into(), - FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(), - FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(), - FfiType::ForeignExecutorHandle => "size_t".into(), - FfiType::RustFutureContinuationCallback => { - "UniFfiRustFutureContinuation _Nonnull".into() - } - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "void* _Nonnull".into() + FfiType::Callback(name) => { + format!("{} _Nonnull", SwiftCodeOracle.ffi_callback_name(name)) } + FfiType::Struct(name) => SwiftCodeOracle.ffi_struct_name(name), + FfiType::Reference(inner) => format!("{}* _Nonnull", header_ffi_type_name(inner)?), + FfiType::VoidPointer => "void* _Nonnull".into(), }) } @@ -664,6 +771,30 @@ pub mod filters { Ok(oracle().enum_variant_name(nm)) } + /// Get the idiomatic Swift rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().ffi_callback_name(nm)) + } + + /// Get the idiomatic Swift rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().ffi_struct_name(nm)) + } + + /// Get the idiomatic Swift rendering of an if guard name + pub fn if_guard_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().if_guard_name(nm)) + } + + /// Get the idiomatic Swift rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> { + let middle = textwrap::indent(&textwrap::dedent(docstring), " * "); + let wrapped = format!("/**\n{middle}\n */"); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } + pub fn error_handler(result: &ResultType) -> Result<String, askama::Error> { Ok(match &result.throws_type { Some(t) => format!("{}.lift", ffi_converter_name(t)?), @@ -685,4 +816,8 @@ pub mod filters { } )) } + + pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { + Ok(SwiftCodeOracle.object_names(obj)) + } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs index ea140c998d..d4497a7b19 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs @@ -3,24 +3,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::CodeType; +use crate::interface::ObjectImpl; #[derive(Debug)] pub struct ObjectCodeType { - id: String, + name: String, + imp: ObjectImpl, } impl ObjectCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(name: String, imp: ObjectImpl) -> Self { + Self { name, imp } } } impl CodeType for ObjectCodeType { fn type_label(&self) -> String { - super::SwiftCodeOracle.class_name(&self.id) + super::SwiftCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { - format!("Type{}", self.id) + format!("Type{}", self.name) + } + + fn initialization_fn(&self) -> Option<String> { + self.imp + .has_callback_interface() + .then(|| format!("uniffiCallbackInit{}", self.name)) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs index 86424658a3..e0c670520e 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs @@ -9,7 +9,11 @@ use paste::paste; fn render_literal(literal: &Literal) -> String { fn typed_number(type_: &Type, num_str: String) -> String { - match type_ { + let unwrapped_type = match type_ { + Type::Optional { inner_type } => inner_type, + t => t, + }; + match unwrapped_type { // special case Int32. Type::Int32 => num_str, // otherwise use constructor e.g. UInt8(x) @@ -29,7 +33,7 @@ fn render_literal(literal: &Literal) -> String { super::SwiftCodeOracle.find(type_).type_label() ) } - _ => panic!("Unexpected literal: {num_str} is not a number"), + _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"), } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift index 695208861d..e16f3108e1 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -1,11 +1,13 @@ private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 +fileprivate let uniffiContinuationHandleMap = UniffiHandleMap<UnsafeContinuation<Int8, Never>>() + fileprivate func uniffiRustCallAsync<F, T>( - rustFutureFunc: () -> UnsafeMutableRawPointer, - pollFunc: (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> (), - completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer<RustCallStatus>) -> F, - freeFunc: (UnsafeMutableRawPointer) -> (), + rustFutureFunc: () -> UInt64, + pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (), + completeFunc: (UInt64, UnsafeMutablePointer<RustCallStatus>) -> F, + freeFunc: (UInt64) -> (), liftFunc: (F) throws -> T, errorHandler: ((RustBuffer) throws -> Error)? ) async throws -> T { @@ -19,7 +21,11 @@ fileprivate func uniffiRustCallAsync<F, T>( var pollResult: Int8; repeat { pollResult = await withUnsafeContinuation { - pollFunc(rustFuture, ContinuationHolder($0).toOpaque()) + pollFunc( + rustFuture, + uniffiFutureContinuationCallback, + uniffiContinuationHandleMap.insert(obj: $0) + ) } } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY @@ -31,32 +37,80 @@ fileprivate func uniffiRustCallAsync<F, T>( // Callback handlers for an async calls. These are invoked by Rust when the future is ready. They // lift the return value or error and resume the suspended function. -fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) { - ContinuationHolder.fromOpaque(ptr).resume(pollResult) +fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) { + if let continuation = try? uniffiContinuationHandleMap.remove(handle: handle) { + continuation.resume(returning: pollResult) + } else { + print("uniffiFutureContinuationCallback invalid handle") + } } -// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across -// the FFI -fileprivate class ContinuationHolder { - let continuation: UnsafeContinuation<Int8, Never> - - init(_ continuation: UnsafeContinuation<Int8, Never>) { - self.continuation = continuation +{%- if ci.has_async_callback_interface_definition() %} +private func uniffiTraitInterfaceCallAsync<T>( + makeCall: @escaping () async throws -> T, + handleSuccess: @escaping (T) -> (), + handleError: @escaping (Int8, RustBuffer) -> () +) -> UniffiForeignFuture { + let task = Task { + do { + handleSuccess(try await makeCall()) + } catch { + handleError(CALL_UNEXPECTED_ERROR, {{ Type::String.borrow()|lower_fn }}(String(describing: error))) + } } + let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task) + return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree) - func resume(_ pollResult: Int8) { - self.continuation.resume(returning: pollResult) - } +} - func toOpaque() -> UnsafeMutableRawPointer { - return Unmanaged<ContinuationHolder>.passRetained(self).toOpaque() +private func uniffiTraitInterfaceCallAsyncWithError<T, E>( + makeCall: @escaping () async throws -> T, + handleSuccess: @escaping (T) -> (), + handleError: @escaping (Int8, RustBuffer) -> (), + lowerError: @escaping (E) -> RustBuffer +) -> UniffiForeignFuture { + let task = Task { + do { + handleSuccess(try await makeCall()) + } catch let error as E { + handleError(CALL_ERROR, lowerError(error)) + } catch { + handleError(CALL_UNEXPECTED_ERROR, {{ Type::String.borrow()|lower_fn }}(String(describing: error))) + } } + let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task) + return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree) +} + +// Borrow the callback handle map implementation to store foreign future handles +// TODO: consolidate the handle-map code (https://github.com/mozilla/uniffi-rs/pull/1823) +fileprivate var UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = UniffiHandleMap<UniffiForeignFutureTask>() + +// Protocol for tasks that handle foreign futures. +// +// Defining a protocol allows all tasks to be stored in the same handle map. This can't be done +// with the task object itself, since has generic parameters. +protocol UniffiForeignFutureTask { + func cancel() +} + +extension Task: UniffiForeignFutureTask {} - static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder { - return Unmanaged<ContinuationHolder>.fromOpaque(ptr).takeRetainedValue() +private func uniffiForeignFutureFree(handle: UInt64) { + do { + let task = try UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle: handle) + // Set the cancellation flag on the task. If it's still running, the code can check the + // cancellation flag or call `Task.checkCancellation()`. If the task has completed, this is + // a no-op. + task.cancel() + } catch { + print("uniffiForeignFutureFree: handle missing from handlemap") } } -fileprivate func uniffiInitContinuationCallback() { - {{ ci.ffi_rust_future_continuation_callback_set().name() }}(uniffiFutureContinuationCallback) +// For testing +public func uniffiForeignFutureHandleCount{{ ci.namespace()|class_name }}() -> Int { + UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.count } + +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h index 87698e359f..89d98594d3 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h @@ -24,25 +24,11 @@ typedef struct RustBuffer { - int32_t capacity; - int32_t len; + uint64_t capacity; + uint64_t len; uint8_t *_Nullable data; } RustBuffer; -typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull); - -// Task defined in Rust that Swift executes -typedef void (*UniFfiRustTaskCallback)(const void * _Nullable, int8_t); - -// Callback to execute Rust tasks using a Swift Task -// -// Args: -// executor: ForeignExecutor lowered into a size_t value -// delay: Delay in MS -// task: UniFfiRustTaskCallback to call -// task_data: data to pass the task callback -typedef int8_t (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable); - typedef struct ForeignBytes { int32_t len; @@ -59,11 +45,29 @@ typedef struct RustCallStatus { // ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ #endif // def UNIFFI_SHARED_H -// Continuation callback for UniFFI Futures -typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t); - -// Scaffolding functions -{%- for func in ci.iter_ffi_function_definitions() %} +{%- for def in ci.ffi_definitions() %} +#ifndef {{ def.name()|if_guard_name }} +#define {{ def.name()|if_guard_name }} +{%- match def %} +{% when FfiDefinition::CallbackFunction(callback) %} +typedef + {%- match callback.return_type() %}{% when Some(return_type) %} {{ return_type|header_ffi_type_name }} {% when None %} void {% endmatch -%} + (*{{ callback.name()|ffi_callback_name }})( + {%- for arg in callback.arguments() -%} + {{ arg.type_().borrow()|header_ffi_type_name }} + {%- if !loop.last || callback.has_rust_call_status_arg() %}, {% endif %} + {%- endfor -%} + {%- if callback.has_rust_call_status_arg() %} + RustCallStatus *_Nonnull uniffiCallStatus + {%- endif %} + ); +{% when FfiDefinition::Struct(struct) %} +typedef struct {{ struct.name()|ffi_struct_name }} { + {%- for field in struct.fields() %} + {{ field.type_().borrow()|header_ffi_type_name }} {{ field.name()|var_name }}; + {%- endfor %} +} {{ struct.name()|ffi_struct_name }}; +{% when FfiDefinition::Function(func) %} {% match func.return_type() -%}{%- when Some with (type_) %}{{ type_|header_ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}( {%- if func.arguments().len() > 0 %} {%- for arg in func.arguments() %} @@ -74,6 +78,8 @@ typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t); {%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{%- else %}void{% endif %} {% endif %} ); +{%- endmatch %} +#endif {%- endfor %} {% import "macros.swift" as swift %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift new file mode 100644 index 0000000000..74ee372642 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift @@ -0,0 +1,113 @@ +{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %} +{%- let trait_impl=format!("UniffiCallbackInterface{}", name) %} + +// Put the implementation in a struct so we don't pollute the top-level namespace +fileprivate struct {{ trait_impl }} { + + // Create the VTable using a series of closures. + // Swift automatically converts these into C callback functions. + static var vtable: {{ vtable|ffi_type_name }} = {{ vtable|ffi_type_name }}( + {%- for (ffi_callback, meth) in vtable_methods %} + {{ meth.name()|fn_name }}: { ( + {%- for arg in ffi_callback.arguments() %} + {{ arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name }}{% if !loop.last || ffi_callback.has_rust_call_status_arg() %},{% endif %} + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() %} + uniffiCallStatus: UnsafeMutablePointer<RustCallStatus> + {%- endif %} + ) in + let makeCall = { + () {% if meth.is_async() %}async {% endif %}throws -> {% match meth.return_type() %}{% when Some(t) %}{{ t|type_name }}{% when None %}(){% endmatch %} in + guard let uniffiObj = try? {{ ffi_converter_name }}.handleMap.get(handle: uniffiHandle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return {% if meth.throws() %}try {% endif %}{% if meth.is_async() %}await {% endif %}uniffiObj.{{ meth.name()|fn_name }}( + {%- for arg in meth.arguments() %} + {% if !config.omit_argument_labels() %} {{ arg.name()|arg_name }}: {% endif %}try {{ arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} + {%- endfor %} + ) + } + {%- if !meth.is_async() %} + + {% match meth.return_type() %} + {%- when Some(t) %} + let writeReturn = { uniffiOutReturn.pointee = {{ t|lower_fn }}($0) } + {%- when None %} + let writeReturn = { () } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + uniffiTraitInterfaceCall( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn + ) + {%- when Some(error_type) %} + uniffiTraitInterfaceCallWithError( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn, + lowerError: {{ error_type|lower_fn }} + ) + {%- endmatch %} + {%- else %} + + let uniffiHandleSuccess = { (returnValue: {{ meth.return_type()|return_type_name }}) in + uniffiFutureCallback( + uniffiCallbackData, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + returnValue: {{ return_type|lower_fn }}(returnValue), + {%- when None %} + {%- endmatch %} + callStatus: RustCallStatus() + ) + ) + } + let uniffiHandleError = { (statusCode, errorBuf) in + uniffiFutureCallback( + uniffiCallbackData, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + returnValue: {{ meth.return_type().map(FfiType::from)|ffi_default_value }}, + {%- when None %} + {%- endmatch %} + callStatus: RustCallStatus(code: statusCode, errorBuf: errorBuf) + ) + ) + } + + {%- match meth.throws_type() %} + {%- when None %} + let uniffiForeignFuture = uniffiTraitInterfaceCallAsync( + makeCall: makeCall, + handleSuccess: uniffiHandleSuccess, + handleError: uniffiHandleError + ) + {%- when Some(error_type) %} + let uniffiForeignFuture = uniffiTraitInterfaceCallAsyncWithError( + makeCall: makeCall, + handleSuccess: uniffiHandleSuccess, + handleError: uniffiHandleError, + lowerError: {{ error_type|lower_fn }} + ) + {%- endmatch %} + uniffiOutReturn.pointee = uniffiForeignFuture + {%- endif %} + }, + {%- endfor %} + uniffiFree: { (uniffiHandle: UInt64) -> () in + let result = try? {{ ffi_converter_name }}.handleMap.remove(handle: uniffiHandle) + if result == nil { + print("Uniffi callback interface {{ name }}: handle missing in uniffiFree") + } + } + ) +} + +private func {{ callback_init }}() { + {{ ffi_init_callback.name() }}(&{{ trait_impl }}.vtable) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift index 9ae62d1667..5863c2ad41 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift @@ -1,60 +1,3 @@ -fileprivate extension NSLock { - func withLock<T>(f: () throws -> T) rethrows -> T { - self.lock() - defer { self.unlock() } - return try f() - } -} - -fileprivate typealias UniFFICallbackHandle = UInt64 -fileprivate class UniFFICallbackHandleMap<T> { - private var leftMap: [UniFFICallbackHandle: T] = [:] - private var counter: [UniFFICallbackHandle: UInt64] = [:] - private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:] - - private let lock = NSLock() - private var currentHandle: UniFFICallbackHandle = 0 - private let stride: UniFFICallbackHandle = 1 - - func insert(obj: T) -> UniFFICallbackHandle { - lock.withLock { - let id = ObjectIdentifier(obj as AnyObject) - let handle = rightMap[id] ?? { - currentHandle += stride - let handle = currentHandle - leftMap[handle] = obj - rightMap[id] = handle - return handle - }() - counter[handle] = (counter[handle] ?? 0) + 1 - return handle - } - } - - func get(handle: UniFFICallbackHandle) -> T? { - lock.withLock { - leftMap[handle] - } - } - - func delete(handle: UniFFICallbackHandle) { - remove(handle: handle) - } - - @discardableResult - func remove(handle: UniFFICallbackHandle) -> T? { - lock.withLock { - defer { counter[handle] = (counter[handle] ?? 1) - 1 } - guard counter[handle] == 1 else { return leftMap[handle] } - let obj = leftMap.removeValue(forKey: handle) - if let obj = obj { - rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject)) - } - return obj - } - } -} - // 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. private let IDX_CALLBACK_FREE: Int32 = 0 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index aec8ded930..7aa1cca9b2 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -1,150 +1,39 @@ {%- let cbi = ci|get_callback_interface_definition(name) %} -{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} -{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %} - -// Declaration and FfiConverters for {{ type_name }} Callback Interface - -public protocol {{ type_name }} : AnyObject { - {% for meth in cbi.methods() -%} - func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%} - {%- match meth.return_type() -%} - {%- when Some with (return_type) %} -> {{ return_type|type_name -}} - {%- else -%} - {%- endmatch %} - {% endfor %} -} - -// The ForeignCallback that is passed to Rust. -fileprivate let {{ foreign_callback }} : ForeignCallback = - { (handle: UniFFICallbackHandle, method: Int32, argsData: UnsafePointer<UInt8>, argsLen: Int32, out_buf: UnsafeMutablePointer<RustBuffer>) -> Int32 in - {% for meth in cbi.methods() -%} - {%- let method_name = format!("invoke_{}", meth.name())|fn_name %} - - func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer<UInt8>, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer<RustBuffer>) throws -> Int32 { - {%- if meth.arguments().len() > 0 %} - var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen))) - {%- endif %} - - {%- match meth.return_type() %} - {%- when Some(return_type) %} - func makeCall() throws -> Int32 { - let result = {% if meth.throws() %} try{% endif %} swiftCallbackInterface.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - var writer = [UInt8]() - {{ return_type|write_fn }}(result, into: &writer) - out_buf.pointee = RustBuffer(bytes: writer) - return UNIFFI_CALLBACK_SUCCESS - } - {%- when None %} - func makeCall() throws -> Int32 { - try swiftCallbackInterface.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - return UNIFFI_CALLBACK_SUCCESS - } - {%- endmatch %} - - {%- match meth.throws_type() %} - {%- when None %} - return try makeCall() - {%- when Some(error_type) %} - do { - return try makeCall() - } catch let error as {{ error_type|type_name }} { - out_buf.pointee = {{ error_type|lower_fn }}(error) - return UNIFFI_CALLBACK_ERROR - } - {%- endmatch %} - } - {%- endfor %} - - - switch method { - case IDX_CALLBACK_FREE: - {{ ffi_converter_name }}.drop(handle: handle) - // Sucessful return - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return UNIFFI_CALLBACK_SUCCESS - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - case {{ loop.index }}: - let cb: {{ cbi|type_name }} - do { - cb = try {{ ffi_converter_name }}.lift(handle) - } catch { - out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("{{ cbi.name() }}: Invalid handle") - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } - do { - return try {{ method_name }}(cb, argsData, argsLen, out_buf) - } catch let error { - out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } - {% endfor %} - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalError. - // https://github.com/mozilla/uniffi-rs/issues/351 - default: - // An unexpected error happened. - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } -} +{%- let callback_handler = format!("uniffiCallbackHandler{}", name) %} +{%- let callback_init = format!("uniffiCallbackInit{}", name) %} +{%- let methods = cbi.methods() %} +{%- let protocol_name = type_name.clone() %} +{%- let protocol_docstring = cbi.docstring() %} +{%- let vtable = cbi.vtable() %} +{%- let vtable_methods = cbi.vtable_methods() %} +{%- let ffi_init_callback = cbi.ffi_init_callback() %} + +{% include "Protocol.swift" %} +{% include "CallbackInterfaceImpl.swift" %} // FfiConverter protocol for callback interfaces fileprivate struct {{ ffi_converter_name }} { - private static let initCallbackOnce: () = { - // Swift ensures this initializer code will once run once, even when accessed by multiple threads. - try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in - {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err) - } - }() - - private static func ensureCallbackinitialized() { - _ = initCallbackOnce - } - - static func drop(handle: UniFFICallbackHandle) { - handleMap.remove(handle: handle) - } - - private static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>() + fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>() } extension {{ ffi_converter_name }} : FfiConverter { typealias SwiftType = {{ type_name }} - // We can use Handle as the FfiType because it's a typealias to UInt64 - typealias FfiType = UniFFICallbackHandle + typealias FfiType = UInt64 - public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType { - ensureCallbackinitialized(); - guard let callback = handleMap.get(handle: handle) else { - throw UniffiInternalError.unexpectedStaleHandle - } - return callback + public static func lift(_ handle: UInt64) throws -> SwiftType { + try handleMap.get(handle: handle) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { - ensureCallbackinitialized(); - let handle: UniFFICallbackHandle = try readInt(&buf) + let handle: UInt64 = try readInt(&buf) return try lift(handle) } - public static func lower(_ v: SwiftType) -> UniFFICallbackHandle { - ensureCallbackinitialized(); + public static func lower(_ v: SwiftType) -> UInt64 { return handleMap.insert(obj: v) } public static func write(_ v: SwiftType, into buf: inout [UInt8]) { - ensureCallbackinitialized(); writeInt(&buf, lower(v)) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift index 99f45290cc..1d8b3cf500 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift @@ -1,10 +1,26 @@ // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +{%- call swift::docstring(e, 0) %} +{% match e.variant_discr_type() %} +{% when None %} public enum {{ type_name }} { {% for variant in e.variants() %} - case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} + {%- call swift::docstring(variant, 4) %} + case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}( + {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %} + ){% endif -%} {% endfor %} } +{% when Some with (variant_discr_type) %} +public enum {{ type_name }} : {{ variant_discr_type|type_name }} { + {% for variant in e.variants() %} + {%- call swift::docstring(variant, 4) %} + case {{ variant.name()|enum_variant_swift_quoted }} = {{ e|variant_discr_literal(loop.index0) }}{% if variant.fields().len() > 0 %}( + {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %} + ){% endif -%} + {% endfor %} +} +{% endmatch %} public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} @@ -15,7 +31,11 @@ public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { {% for variant in e.variants() %} case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift_quoted }}{% if variant.has_fields() %}( {%- for field in variant.fields() %} + {%- if variant.has_nameless_fields() -%} + try {{ field|read_fn }}(from: &buf) + {%- else -%} {{ field.name()|arg_name }}: try {{ field|read_fn }}(from: &buf) + {%- endif -%} {%- if !loop.last %}, {% endif %} {%- endfor %} ){%- endif %} @@ -28,10 +48,10 @@ public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { switch value { {% for variant in e.variants() %} {% if variant.has_fields() %} - case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): + case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{%- call swift::field_name(field, loop.index) -%}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): writeInt(&buf, Int32({{ loop.index }})) {% for field in variant.fields() -%} - {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf) + {{ field|write_fn }}({% call swift::field_name(field, loop.index) %}, into: &buf) {% endfor -%} {% else %} case .{{ variant.name()|enum_variant_swift_quoted }}: @@ -55,5 +75,6 @@ public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuff } {% if !contains_object_references %} +{% if config.experimental_sendable_value_types() %}extension {{ type_name }}: Sendable {} {% endif %} extension {{ type_name }}: Equatable, Hashable {} {% endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift index 786091395b..0702c477e9 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -1,21 +1,21 @@ +{%- call swift::docstring(e, 0) %} public enum {{ type_name }} { {% if e.is_flat() %} {% for variant in e.variants() %} - // Simple error enums only carry a message + {%- call swift::docstring(variant, 4) %} case {{ variant.name()|class_name }}(message: String) {% endfor %} {%- else %} {% for variant in e.variants() %} - case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} + {%- call swift::docstring(variant, 4) %} + case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}( + {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %} + ){% endif -%} {% endfor %} {%- endif %} - - fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { - return try {{ ffi_converter_name }}.lift(error) - } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift deleted file mode 100644 index 167e4c7546..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift +++ /dev/null @@ -1,69 +0,0 @@ -private let UNIFFI_RUST_TASK_CALLBACK_SUCCESS: Int8 = 0 -private let UNIFFI_RUST_TASK_CALLBACK_CANCELLED: Int8 = 1 -private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS: Int8 = 0 -private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED: Int8 = 1 -private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR: Int8 = 2 - -// Encapsulates an executor that can run Rust tasks -// -// On Swift, `Task.detached` can handle this we just need to know what priority to send it. -public struct UniFfiForeignExecutor { - var priority: TaskPriority - - public init(priority: TaskPriority) { - self.priority = priority - } - - public init() { - self.priority = Task.currentPriority - } -} - -fileprivate struct FfiConverterForeignExecutor: FfiConverter { - typealias SwiftType = UniFfiForeignExecutor - // Rust uses a pointer to represent the FfiConverterForeignExecutor, but we only need a u8. - // let's use `Int`, which is equivalent to `size_t` - typealias FfiType = Int - - public static func lift(_ value: FfiType) throws -> SwiftType { - UniFfiForeignExecutor(priority: TaskPriority(rawValue: numericCast(value))) - } - public static func lower(_ value: SwiftType) -> FfiType { - numericCast(value.priority.rawValue) - } - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { - fatalError("FfiConverterForeignExecutor.read not implemented yet") - } - public static func write(_ value: SwiftType, into buf: inout [UInt8]) { - fatalError("FfiConverterForeignExecutor.read not implemented yet") - } -} - - -fileprivate func uniffiForeignExecutorCallback(executorHandle: Int, delayMs: UInt32, rustTask: UniFfiRustTaskCallback?, taskData: UnsafeRawPointer?) -> Int8 { - if let rustTask = rustTask { - let executor = try! FfiConverterForeignExecutor.lift(executorHandle) - Task.detached(priority: executor.priority) { - if delayMs != 0 { - let nanoseconds: UInt64 = numericCast(delayMs * 1000000) - try! await Task.sleep(nanoseconds: nanoseconds) - } - rustTask(taskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS) - } - return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - } else { - // When rustTask is null, we should drop the foreign executor. - // However, since its just a value type, we don't need to do anything here. - return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - } -} - -fileprivate func uniffiInitForeignExecutor() { - {%- match ci.ffi_foreign_executor_callback_set() %} - {%- when Some with (fn) %} - {{ fn.name() }}(uniffiForeignExecutorCallback) - {%- when None %} - {#- No foreign executor, we don't set anything #} - {% endmatch %} -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift new file mode 100644 index 0000000000..6de9f085d6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift @@ -0,0 +1,40 @@ +fileprivate class UniffiHandleMap<T> { + private var map: [UInt64: T] = [:] + private let lock = NSLock() + private var currentHandle: UInt64 = 1 + + func insert(obj: T) -> UInt64 { + lock.withLock { + let handle = currentHandle + currentHandle += 1 + map[handle] = obj + return handle + } + } + + func get(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map[handle] else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + @discardableResult + func remove(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map.removeValue(forKey: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + var count: Int { + get { + map.count + } + } +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift index a34b128e23..cfddf7b313 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift @@ -26,9 +26,17 @@ fileprivate enum UniffiInternalError: LocalizedError { } } +fileprivate extension NSLock { + func withLock<T>(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + fileprivate let CALL_SUCCESS: Int8 = 0 fileprivate let CALL_ERROR: Int8 = 1 -fileprivate let CALL_PANIC: Int8 = 2 +fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2 fileprivate let CALL_CANCELLED: Int8 = 3 fileprivate extension RustCallStatus { @@ -81,7 +89,7 @@ private func uniffiCheckCallStatus( throw UniffiInternalError.unexpectedRustCallError } - case CALL_PANIC: + case CALL_UNEXPECTED_ERROR: // 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. @@ -93,9 +101,39 @@ private func uniffiCheckCallStatus( } case CALL_CANCELLED: - throw CancellationError() + fatalError("Cancellation not supported yet") default: throw UniffiInternalError.unexpectedRustCallStatusCode } } + +private func uniffiTraitInterfaceCall<T>( + callStatus: UnsafeMutablePointer<RustCallStatus>, + makeCall: () throws -> T, + writeReturn: (T) -> () +) { + do { + try writeReturn(makeCall()) + } catch let error { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + } +} + +private func uniffiTraitInterfaceCallWithError<T, E>( + callStatus: UnsafeMutablePointer<RustCallStatus>, + makeCall: () throws -> T, + writeReturn: (T) -> (), + lowerError: (E) -> RustBuffer +) { + do { + try writeReturn(makeCall()) + } catch let error as E { + callStatus.pointee.code = CALL_ERROR + callStatus.pointee.errorBuf = lowerError(error) + } catch { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index 57a77ca6df..0c28bc4c09 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -1,104 +1,146 @@ {%- let obj = ci|get_object_definition(name) %} -public protocol {{ obj.name() }}Protocol { - {% for meth in obj.methods() -%} - func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) %} {% call swift::throws(meth) -%} - {%- match meth.return_type() -%} - {%- when Some with (return_type) %} -> {{ return_type|type_name -}} - {%- else -%} - {%- endmatch %} - {% endfor %} -} - -public class {{ type_name }}: {{ obj.name() }}Protocol { - fileprivate let pointer: UnsafeMutableRawPointer +{%- let (protocol_name, impl_class_name) = obj|object_names %} +{%- let methods = obj.methods() %} +{%- let protocol_docstring = obj.docstring() %} + +{%- let is_error = ci.is_name_used_as_error(name) %} + +{% include "Protocol.swift" %} + +{%- call swift::docstring(obj, 0) %} +open class {{ impl_class_name }}: + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {%- when UniffiTrait::Display { fmt } %} + CustomStringConvertible, + {%- when UniffiTrait::Debug { fmt } %} + CustomDebugStringConvertible, + {%- when UniffiTrait::Eq { eq, ne } %} + Equatable, + {%- when UniffiTrait::Hash { hash } %} + Hashable, + {%- else %} + {%- endmatch %} + {%- endfor %} + {%- if is_error %} + Error, + {% endif %} + {{ protocol_name }} { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + public struct NoPointer { + public init() {} + } // TODO: We'd like this to be `private` but for Swifty reasons, // we can't implement `FfiConverter` without making this `required` and we can't // make it `required` without making it `public`. - required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { self.pointer = pointer } + /// This constructor can be used to instantiate a fake object. + /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + /// + /// - Warning: + /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. + public init(noPointer: NoPointer) { + self.pointer = nil + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { {{ obj.ffi_object_clone().name() }}(self.pointer, $0) } + } + {%- match obj.primary_constructor() %} {%- when Some with (cons) %} - public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} { - self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) - } + {%- call swift::ctor_decl(cons, 4) %} {%- when None %} + // No primary constructor declared for this class. {%- endmatch %} deinit { + guard let pointer = pointer else { + return + } + try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) } } {% for cons in obj.alternate_constructors() %} - - public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ type_name }} { - return {{ type_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) - } - + {%- call swift::func_decl("public static func", cons, 4) %} {% endfor %} - {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #} {% for meth in obj.methods() -%} - {%- if meth.is_async() %} - - public func {{ meth.name()|fn_name }}({%- call swift::arg_list_decl(meth) -%}) async {% call swift::throws(meth) %}{% match meth.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { - return {% call swift::try(meth) %} await uniffiRustCallAsync( - rustFutureFunc: { - {{ meth.ffi_func().name() }}( - self.pointer - {%- for arg in meth.arguments() -%} - , - {{ arg|lower_fn }}({{ arg.name()|var_name }}) - {%- endfor %} - ) - }, - pollFunc: {{ meth.ffi_rust_future_poll(ci) }}, - completeFunc: {{ meth.ffi_rust_future_complete(ci) }}, - freeFunc: {{ meth.ffi_rust_future_free(ci) }}, - {%- match meth.return_type() %} - {%- when Some(return_type) %} - liftFunc: {{ return_type|lift_fn }}, - {%- when None %} - liftFunc: { $0 }, - {%- endmatch %} - {%- match meth.throws_type() %} - {%- when Some with (e) %} - errorHandler: {{ e|ffi_converter_name }}.lift - {%- else %} - errorHandler: nil - {% endmatch %} + {%- call swift::func_decl("open func", meth, 4) %} + {% endfor %} + + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {%- when UniffiTrait::Display { fmt } %} + open var description: String { + return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call(fmt) %} ) } - - {% else -%} - - {%- match meth.return_type() -%} - - {%- when Some with (return_type) %} - - public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} { - return {% call swift::try(meth) %} {{ return_type|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + {%- when UniffiTrait::Debug { fmt } %} + open var debugDescription: String { + return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call(fmt) %} ) } - - {%- when None %} - - public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} { - {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + {%- when UniffiTrait::Eq { eq, ne } %} + public static func == (self: {{ impl_class_name }}, other: {{ impl_class_name }}) -> Bool { + return {% call swift::try(eq) %} {{ eq.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call(eq) %} + ) + } + {%- when UniffiTrait::Hash { hash } %} + open func hash(into hasher: inout Hasher) { + let val = {% call swift::try(hash) %} {{ hash.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call(hash) %} + ) + hasher.combine(val) } + {%- else %} + {%- endmatch %} + {%- endfor %} - {%- endmatch -%} - {%- endif -%} - {% endfor %} } +{%- if obj.has_callback_interface() %} +{%- let callback_handler = format!("uniffiCallbackInterface{}", name) %} +{%- let callback_init = format!("uniffiCallbackInit{}", name) %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} +{%- let ffi_init_callback = obj.ffi_init_callback() %} +{% include "CallbackInterfaceImpl.swift" %} +{%- endif %} + public struct {{ ffi_converter_name }}: FfiConverter { + {%- if obj.has_callback_interface() %} + fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>() + {%- endif %} + typealias FfiType = UnsafeMutableRawPointer typealias SwiftType = {{ type_name }} + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { + return {{ impl_class_name }}(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + {%- if obj.has_callback_interface() %} + guard let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: handleMap.insert(obj: value))) else { + fatalError("Cast to UnsafeMutableRawPointer failed") + } + return ptr + {%- else %} + return value.uniffiClonePointer() + {%- endif %} + } + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { let v: UInt64 = try readInt(&buf) // The Rust code won't compile if a pointer won't fit in a UInt64. @@ -115,15 +157,30 @@ public struct {{ ffi_converter_name }}: FfiConverter { // The Rust code won't compile if a pointer won't fit in a `UInt64`. writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) } +} - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { - return {{ type_name}}(unsafeFromRawPointer: pointer) +{# Objects as error #} +{%- if is_error %} +{# Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer holding a pointer #} +public struct {{ ffi_converter_name }}__as_error: FfiConverterRustBuffer { + public static func lift(_ buf: RustBuffer) throws -> {{ type_name }} { + var reader = createReader(data: Data(rustBuffer: buf)) + return try {{ ffi_converter_name }}.read(from: &reader) } - public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { - return value.pointer + public static func lower(_ value: {{ type_name }}) -> RustBuffer { + fatalError("not implemented") + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + fatalError("not implemented") + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + fatalError("not implemented") } } +{%- endif %} {# We always write these public functions just in case the enum is used as diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift new file mode 100644 index 0000000000..7df953558a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift @@ -0,0 +1,12 @@ +{%- call swift::docstring_value(protocol_docstring, 0) %} +public protocol {{ protocol_name }} : AnyObject { + {% for meth in methods.iter() -%} + {%- call swift::docstring(meth, 4) %} + func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) -%}{% call swift::throws(meth) -%} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %} -> {{ return_type|type_name -}} + {%- else -%} + {%- endmatch %} + {% endfor %} +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift index 44de9dd358..c262a7a216 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift @@ -1,12 +1,14 @@ {%- let rec = ci|get_record_definition(name) %} +{%- call swift::docstring(rec, 0) %} public struct {{ type_name }} { {%- for field in rec.fields() %} - public var {{ field.name()|var_name }}: {{ field|type_name }} + {%- call swift::docstring(field, 4) %} + public {% if config.generate_immutable_records() %}let{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name }} {%- endfor %} // Default memberwise initializers are never public by default, so we // declare one manually. - public init({% call swift::field_list_decl(rec) %}) { + public init({% call swift::field_list_decl(rec, false) %}) { {%- for field in rec.fields() %} self.{{ field.name()|var_name }} = {{ field.name()|var_name }} {%- endfor %} @@ -14,6 +16,7 @@ public struct {{ type_name }} { } {% if !contains_object_references %} +{% if config.experimental_sendable_value_types() %}extension {{ type_name }}: Sendable {} {% endif %} extension {{ type_name }}: Equatable, Hashable { public static func ==(lhs: {{ type_name }}, rhs: {{ type_name }}) -> Bool { {%- for field in rec.fields() %} @@ -34,12 +37,16 @@ extension {{ type_name }}: Equatable, Hashable { public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { - return try {{ type_name }}( + return {%- if rec.has_fields() %} + try {{ type_name }}( {%- for field in rec.fields() %} - {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf) - {%- if !loop.last %}, {% endif %} + {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf) + {%- if !loop.last %}, {% endif %} {%- endfor %} ) + {%- else %} + {{ type_name }}() + {%- endif %} } public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift index 2f737b6635..a053334a30 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift @@ -7,6 +7,10 @@ fileprivate extension RustBuffer { self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) } + static func empty() -> RustBuffer { + RustBuffer(capacity: 0, len:0, data: nil) + } + static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer { try! rustCall { {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), $0) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift index a2c6311931..ce946076f7 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift @@ -1,48 +1 @@ -{%- if func.is_async() %} - -public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) async {% call swift::throws(func) %}{% match func.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { - return {% call swift::try(func) %} await uniffiRustCallAsync( - rustFutureFunc: { - {{ func.ffi_func().name() }}( - {%- for arg in func.arguments() %} - {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} - {%- endfor %} - ) - }, - pollFunc: {{ func.ffi_rust_future_poll(ci) }}, - completeFunc: {{ func.ffi_rust_future_complete(ci) }}, - freeFunc: {{ func.ffi_rust_future_free(ci) }}, - {%- match func.return_type() %} - {%- when Some(return_type) %} - liftFunc: {{ return_type|lift_fn }}, - {%- when None %} - liftFunc: { $0 }, - {%- endmatch %} - {%- match func.throws_type() %} - {%- when Some with (e) %} - errorHandler: {{ e|ffi_converter_name }}.lift - {%- else %} - errorHandler: nil - {% endmatch %} - ) -} - -{% else %} - -{%- match func.return_type() -%} -{%- when Some with (return_type) %} - -public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) {% call swift::throws(func) %} -> {{ return_type|type_name }} { - return {% call swift::try(func) %} {{ return_type|lift_fn }}( - {% call swift::to_ffi_call(func) %} - ) -} - -{%- when None %} - -public func {{ func.name()|fn_name }}({% call swift::arg_list_decl(func) %}) {% call swift::throws(func) %} { - {% call swift::to_ffi_call(func) %} -} - -{% endmatch %} -{%- endif %} +{%- call swift::func_decl("public func", func, 0) %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift index aba34f4b0b..5e26758f3c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift @@ -64,9 +64,6 @@ {%- when Type::CallbackInterface { name, module_path } %} {%- include "CallbackInterfaceTemplate.swift" %} -{%- when Type::ForeignExecutor %} -{%- include "ForeignExecutorTemplate.swift" %} - {%- when Type::Custom { name, module_path, builtin } %} {%- include "CustomType.swift" %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift index 0a125e6f61..8692cd6ff0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift @@ -8,28 +8,97 @@ {%- call try(func) -%} {%- match func.throws_type() -%} {%- when Some with (e) -%} - rustCallWithError({{ e|ffi_converter_name }}.lift) { + rustCallWithError({{ e|ffi_error_converter_name }}.lift) { {%- else -%} rustCall() { {%- endmatch %} - {{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} $0) + {{ func.ffi_func().name() }}( + {%- if func.takes_self() %}self.uniffiClonePointer(),{% endif %} + {%- call arg_list_lowered(func) -%} $0 + ) } {%- endmacro -%} -{%- macro to_ffi_call_with_prefix(prefix, func) -%} -{% call try(func) %} - {%- match func.throws_type() %} - {%- when Some with (e) %} - rustCallWithError({{ e|ffi_converter_name }}.lift) { +// eg, `public func foo_bar() { body }` +{%- macro func_decl(func_decl, callable, indent) %} +{%- call docstring(callable, indent) %} +{{ func_decl }} {{ callable.name()|fn_name }}( + {%- call arg_list_decl(callable) -%}) + {%- call async(callable) %} + {%- call throws(callable) %} + {%- match callable.return_type() %} + {%- when Some with (return_type) %} -> {{ return_type|type_name }} + {%- when None %} + {%- endmatch %} { + {%- call call_body(callable) %} +} +{%- endmacro %} + +// primary ctor - no name, no return-type. +{%- macro ctor_decl(callable, indent) %} +{%- call docstring(callable, indent) %} +public convenience init( + {%- call arg_list_decl(callable) -%}) {%- call async(callable) %} {%- call throws(callable) %} { + {%- if callable.is_async() %} + let pointer = + {%- call call_async(callable) %} + {# The async mechanism returns an already constructed self. + We work around that by cloning the pointer from that object, then + assune the old object dies as there are no other references possible. + #} + .uniffiClonePointer() {%- else %} - rustCall() { - {% endmatch %} - {{ func.ffi_func().name() }}( - {{- prefix }}, {% call arg_list_lowered(func) -%} $0 - ) + let pointer = + {% call to_ffi_call(callable) %} + {%- endif %} + self.init(unsafeFromRawPointer: pointer) } {%- endmacro %} +{%- macro call_body(callable) %} +{%- if callable.is_async() %} + return {%- call call_async(callable) %} +{%- else %} +{%- match callable.return_type() -%} +{%- when Some with (return_type) %} + return {% call try(callable) %} {{ return_type|lift_fn }}({% call to_ffi_call(callable) %}) +{%- when None %} +{%- call to_ffi_call(callable) %} +{%- endmatch %} +{%- endif %} + +{%- endmacro %} + +{%- macro call_async(callable) %} + {% call try(callable) %} await uniffiRustCallAsync( + rustFutureFunc: { + {{ callable.ffi_func().name() }}( + {%- if callable.takes_self() %} + self.uniffiClonePointer(){% if !callable.arguments().is_empty() %},{% endif %} + {% endif %} + {%- for arg in callable.arguments() -%} + {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} + {%- endfor %} + ) + }, + pollFunc: {{ callable.ffi_rust_future_poll(ci) }}, + completeFunc: {{ callable.ffi_rust_future_complete(ci) }}, + freeFunc: {{ callable.ffi_rust_future_free(ci) }}, + {%- match callable.return_type() %} + {%- when Some(return_type) %} + liftFunc: {{ return_type|lift_fn }}, + {%- when None %} + liftFunc: { $0 }, + {%- endmatch %} + {%- match callable.throws_type() %} + {%- when Some with (e) %} + errorHandler: {{ e|ffi_error_converter_name }}.lift + {%- else %} + errorHandler: nil + {% endmatch %} + ) +{%- endmacro %} + {%- macro arg_list_lowered(func) %} {%- for arg in func.arguments() %} {{ arg|lower_fn }}({{ arg.name()|var_name }}), @@ -56,17 +125,30 @@ // Field lists as used in Swift declarations of Records and Enums. // Note the var_name and type_name filters. -#} -{% macro field_list_decl(item) %} +{% macro field_list_decl(item, has_nameless_fields) %} {%- for field in item.fields() -%} + {%- call docstring(field, 8) %} + {%- if has_nameless_fields %} + {{- field|type_name -}} + {%- if !loop.last -%}, {%- endif -%} + {%- else -%} {{ field.name()|var_name }}: {{ field|type_name -}} {%- match field.default_value() %} {%- when Some with(literal) %} = {{ literal|literal_swift(field) }} {%- else %} {%- endmatch -%} {% if !loop.last %}, {% endif %} + {%- endif -%} {%- endfor %} {%- endmacro %} +{% macro field_name(field, field_num) %} +{%- if field.name().is_empty() -%} +v{{- field_num -}} +{%- else -%} +{{ field.name()|var_name }} +{%- endif -%} +{%- endmacro %} {% macro arg_list_protocol(func) %} {%- for arg in func.arguments() -%} @@ -75,15 +157,26 @@ {%- endfor %} {%- endmacro %} - {%- macro async(func) %} -{%- if func.is_async() %}async{% endif %} +{%- if func.is_async() %}async {% endif %} {%- endmacro -%} {%- macro throws(func) %} -{%- if func.throws() %}throws{% endif %} +{%- if func.throws() %}throws {% endif %} {%- endmacro -%} {%- macro try(func) %} {%- if func.throws() %}try {% else %}try! {% endif %} {%- endmacro -%} + +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift index c34d348efb..17fdde74e0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift @@ -1,5 +1,10 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! + +// swiftlint:disable all + +{%- call swift::docstring_value(ci.namespace_docstring(), 0) %} + {%- import "macros.swift" as swift %} import Foundation {%- for imported_class in self.imports() %} @@ -15,6 +20,7 @@ import {{ config.ffi_module_name() }} {% include "RustBufferTemplate.swift" %} {% include "Helpers.swift" %} +{% include "HandleMap.swift" %} // Public interface members begin here. {{ type_helper_code }} @@ -66,3 +72,5 @@ private func uniffiEnsureInitialized() { fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } } + +// swiftlint:enable all diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs index c3b2f15277..195a77696b 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs @@ -2,10 +2,7 @@ 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 crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; @@ -15,6 +12,8 @@ use std::io::Write; use std::process::{Command, Stdio}; use uniffi_testing::UniFFITestHelper; +use crate::bindings::TargetLanguage; + /// Run Swift tests for a UniFFI test fixture pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { run_script( @@ -36,7 +35,7 @@ pub fn run_script( args: Vec<String>, options: &RunScriptOptions, ) -> Result<()> { - let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let script_path = Utf8Path::new(script_file).canonicalize_utf8()?; 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)?; @@ -126,8 +125,17 @@ struct GeneratedSources { impl GeneratedSources { fn new(crate_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result<Self> { - let sources = - generate_bindings(cdylib_path, None, &[TargetLanguage::Swift], out_dir, false)?; + let sources = generate_bindings( + cdylib_path, + None, + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Swift], + try_format_code: false, + }, + None, + out_dir, + false, + )?; let main_source = sources .iter() .find(|s| s.package.name == crate_name) @@ -169,7 +177,7 @@ fn create_command(program: &str, options: &RunScriptOptions) -> Command { if !options.show_compiler_messages { // This prevents most compiler messages, but not remarks command.arg("-suppress-warnings"); - // This gets the remarks. Note: swift will eventually get a `-supress-remarks` argument, + // This gets the remarks. Note: swift will eventually get a `-suppress-remarks` argument, // maybe we can eventually move to that command.stderr(Stdio::null()); } |