diff --git a/CHANGELOG.md b/CHANGELOG.md index 095245f9bf..a38464fb34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,15 +34,13 @@ - The `include_scaffolding!()` macro must now either be called from your crate root or you must have `use the_mod_that_calls_include_scaffolding::*` in your crate root. This was always the expectation, but wasn't required before. This will now start failing with errors that say `crate::UniFfiTag` does not exist. - proc-macros now work with many more types including type aliases, type paths, etc. - The `uniffi_types` module is no longer needed when using proc-macros. - - Traits can be exposed as a UniFFI `interface` by using a `[Trait]` attribute in the UDL. See [the documentation](https://mozilla.github.io/uniffi-rs/udl/interfaces.html#exposing-traits-as-interfaces). - - The `bytes` primitive type was added, it represents an array of bytes. It maps to `ByteArray` in Kotlin, `bytes` in Python, `String` with `Encoding::BINARY` in Ruby and `Data` in Swift. - Shortened `str()` representations of errors in Python to align with other exceptions in Python. Use `repr()` or the `{!r}` format to get the old representation back. - - Methods implemented by standard Rust traits, such as `Debug`, `Display`, `Eq` and `Hash` can now be exposed over the FFI and bindings may implement special methods for them. See [the documentation](https://mozilla.github.io/uniffi-rs/udl/interfaces.html#exposing-methods-from-standard-rust-traits). +- Implemented proc-macro callback interface support ### Guidance for external bindings diff --git a/docs/manual/src/proc_macro/index.md b/docs/manual/src/proc_macro/index.md index 24d3250c43..a62dc9a85a 100644 --- a/docs/manual/src/proc_macro/index.md +++ b/docs/manual/src/proc_macro/index.md @@ -228,6 +228,55 @@ fn do_http_request() -> Result<(), MyApiError> { } ``` +## The `#[uniffi::export(callback_interface)]` attribute + +`#[uniffi::export(callback_interface)]` can be used to export a [callback interface](../udl/callback_interfaces.html) definition. +This allows the foreign bindings to implement the interface and pass an instance to the Rust code. + +```rust +#[uniffi::export(callback_interface)] +pub trait Person { + fn name() -> String; + fn age() -> u32; +} + +// Corresponding UDL: +// callback interface Person { +// string name(); +// u32 age(); +// } +``` + +### Exception handling in callback interfaces + +Most languages allow arbitrary exceptions to be thrown, which presents issues for callback +interfaces. If a callback interface function returns a non-Result type, then any exception will +result in a panic on the Rust side. + +To avoid panics, callback interfaces can use `Result` types for all return values. If the callback +interface implementation throws the exception that corresponds to the `E` parameter, `Err(E)` will +be returned to the Rust code. However, in most languages it's still possible for the implementation +to throw other exceptions. To avoid panics in those cases, the error type must be wrapped +with the `#[uniffi(handle_unknown_callback_error)]` attribute and +`From` must be implemented: + +```rust +#[derive(uniffi::Error)] +#[uniffi(handle_unknown_callback_error)] +pub enum MyApiError { + IOError, + ValueError, + UnexpectedError { reason: String }, +} + +impl From for MyApiError { + fn from(e: UnexpectedUniFFICallbackError) -> Self { + Self::UnexpectedError { reason: e.reason } + } +} +``` + + ## Other limitations In addition to the per-item limitations of the macros presented above, there is also currently a diff --git a/fixtures/metadata/src/tests.rs b/fixtures/metadata/src/tests.rs index 29519fc54c..411dfc2abb 100644 --- a/fixtures/metadata/src/tests.rs +++ b/fixtures/metadata/src/tests.rs @@ -85,6 +85,11 @@ mod calc { pub struct Calculator {} } +#[uniffi::export(callback_interface)] +pub trait Logger { + fn log(&self, message: String); +} + pub use calc::Calculator; pub use error::{ComplexError, FlatError}; pub use person::Person; @@ -476,7 +481,6 @@ mod test_function_metadata { MethodMetadata { module_path: "uniffi_fixture_metadata".into(), self_name: "Calculator".into(), - self_is_trait: false, name: "add".into(), is_async: false, inputs: vec![ @@ -555,7 +559,6 @@ mod test_function_metadata { MethodMetadata { module_path: "uniffi_fixture_metadata".into(), self_name: "Calculator".into(), - self_is_trait: false, name: "async_sub".into(), is_async: true, inputs: vec![ @@ -583,7 +586,6 @@ mod test_function_metadata { MethodMetadata { module_path: "uniffi_fixture_metadata".into(), self_name: "Calculator".into(), - self_is_trait: false, name: "get_display".into(), is_async: false, inputs: vec![], @@ -602,10 +604,10 @@ mod test_function_metadata { fn test_trait_method() { check_metadata( &UNIFFI_META_UNIFFI_FIXTURE_METADATA_METHOD_CALCULATORDISPLAY_DISPLAY_RESULT, - MethodMetadata { + TraitMethodMetadata { module_path: "uniffi_fixture_metadata".into(), - self_name: "CalculatorDisplay".into(), - self_is_trait: true, + trait_name: "CalculatorDisplay".into(), + index: 0, name: "display_result".into(), is_async: false, inputs: vec![ @@ -621,4 +623,32 @@ mod test_function_metadata { }, ); } + + #[test] + fn test_callback_interface() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_CALLBACK_INTERFACE_LOGGER, + CallbackInterfaceMetadata { + module_path: "uniffi_fixture_metadata".into(), + name: "Logger".into(), + }, + ); + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_METHOD_LOGGER_LOG, + TraitMethodMetadata { + module_path: "uniffi_fixture_metadata".into(), + trait_name: "Logger".into(), + index: 0, + name: "log".into(), + is_async: false, + inputs: vec![FnParamMetadata { + name: "message".into(), + ty: Type::String, + }], + return_type: None, + throws: None, + checksum: UNIFFI_META_CONST_UNIFFI_FIXTURE_METADATA_METHOD_LOGGER_LOG.checksum(), + }, + ); + } } diff --git a/fixtures/proc-macro/src/callback_interface.rs b/fixtures/proc-macro/src/callback_interface.rs new file mode 100644 index 0000000000..8574994966 --- /dev/null +++ b/fixtures/proc-macro/src/callback_interface.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::BasicError; + +#[uniffi::export(callback_interface)] +pub trait TestCallbackInterface { + fn do_nothing(&self); + fn add(&self, a: u32, b: u32) -> u32; + fn try_parse_int(&self, value: String) -> Result; +} diff --git a/fixtures/proc-macro/src/lib.rs b/fixtures/proc-macro/src/lib.rs index f3b4b1f79b..f09b5b9ab6 100644 --- a/fixtures/proc-macro/src/lib.rs +++ b/fixtures/proc-macro/src/lib.rs @@ -4,6 +4,10 @@ use std::sync::Arc; +mod callback_interface; + +use callback_interface::TestCallbackInterface; + #[derive(uniffi::Record)] pub struct One { inner: i32, @@ -87,6 +91,21 @@ fn take_two(two: Two) -> String { two.a } +#[uniffi::export] +fn test_callback_interface(cb: Box) { + cb.do_nothing(); + assert_eq!(cb.add(1, 1), 2); + assert_eq!(Ok(10), cb.try_parse_int("10".to_string())); + assert_eq!( + Err(BasicError::InvalidInput), + cb.try_parse_int("ten".to_string()) + ); + assert!(matches!( + cb.try_parse_int("force-unexpected-error".to_string()), + Err(BasicError::UnexpectedError { .. }), + )); +} + // Type that's defined in the UDL and not wrapped with #[uniffi::export] pub struct Zero { inner: String, @@ -111,10 +130,18 @@ fn enum_identity(value: MaybeBool) -> MaybeBool { value } -#[derive(uniffi::Error)] +#[derive(uniffi::Error, Debug, PartialEq, Eq)] +#[uniffi(handle_unknown_callback_error)] pub enum BasicError { InvalidInput, OsError, + UnexpectedError { reason: String }, +} + +impl From for BasicError { + fn from(e: uniffi::UnexpectedUniFFICallbackError) -> Self { + Self::UnexpectedError { reason: e.reason } + } } #[uniffi::export] diff --git a/fixtures/proc-macro/tests/bindings/test_proc_macro.kts b/fixtures/proc-macro/tests/bindings/test_proc_macro.kts index 9bf540fc29..2057b04fe9 100644 --- a/fixtures/proc-macro/tests/bindings/test_proc_macro.kts +++ b/fixtures/proc-macro/tests/bindings/test_proc_macro.kts @@ -34,3 +34,24 @@ try { throw RuntimeException("doStuff should throw if its argument is 0") } catch (e: FlatException) { } + + +class KtTestCallbackInterface : TestCallbackInterface { + override fun doNothing() { } + + override fun add(a: UInt, b: UInt) = a + b + + override fun tryParseInt(value: String): UInt { + if (value == "force-unexpected-error") { + // raise an error that's not expected + throw RuntimeException(value) + } + try { + return value.toUInt() + } catch(e: NumberFormatException) { + throw BasicException.InvalidInput() + } + } +} + +testCallbackInterface(KtTestCallbackInterface()) diff --git a/fixtures/proc-macro/tests/bindings/test_proc_macro.py b/fixtures/proc-macro/tests/bindings/test_proc_macro.py index 955f4e8c2f..234f7d8474 100644 --- a/fixtures/proc-macro/tests/bindings/test_proc_macro.py +++ b/fixtures/proc-macro/tests/bindings/test_proc_macro.py @@ -40,3 +40,21 @@ pass else: raise Exception("do_stuff should throw if its argument is 0") + +class PyTestCallbackInterface(TestCallbackInterface): + def do_nothing(self): + pass + + def add(self, a, b): + return a + b + + def try_parse_int(self, value): + if value == "force-unexpected-error": + # raise an error that's not expected + raise KeyError(value) + try: + return int(value) + except BaseException: + raise BasicError.InvalidInput() + +test_callback_interface(PyTestCallbackInterface()) diff --git a/fixtures/proc-macro/tests/bindings/test_proc_macro.swift b/fixtures/proc-macro/tests/bindings/test_proc_macro.swift index ed423ed9d4..e7e51374b3 100644 --- a/fixtures/proc-macro/tests/bindings/test_proc_macro.swift +++ b/fixtures/proc-macro/tests/bindings/test_proc_macro.swift @@ -34,3 +34,28 @@ do { fatalError("doStuff should throw if its argument is 0") } catch FlatError.InvalidInput { } + +struct SomeOtherError: Error { } + +class SwiftTestCallbackInterface : TestCallbackInterface { + func doNothing() { } + + func add(a: UInt32, b: UInt32) -> UInt32 { + return a + b; + } + + func tryParseInt(value: String) throws -> UInt32 { + if (value == "force-unexpected-error") { + // raise an error that's not expected + throw SomeOtherError() + } + let parsed = UInt32(value) + if parsed != nil { + return parsed! + } else { + throw BasicError.InvalidInput + } + } +} + +testCallbackInterface(cb: SwiftTestCallbackInterface()) diff --git a/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.stderr b/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.stderr index c95fd2cf49..606409f293 100644 --- a/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.stderr +++ b/fixtures/uitests/tests/ui/fieldless_errors_used_in_callbacks_cant_have_fields.stderr @@ -5,6 +5,7 @@ error[E0533]: expected value, found struct variant `Self::DivisionByZero` | | tag = crate::UniFfiTag, | | flat_error, | | with_try_read, + | | handle_unknown_callback_error, | | )] | |__^ not a value | diff --git a/uniffi_bindgen/src/backend/filters.rs b/uniffi_bindgen/src/backend/filters.rs new file mode 100644 index 0000000000..4434d71122 --- /dev/null +++ b/uniffi_bindgen/src/backend/filters.rs @@ -0,0 +1,69 @@ +/* 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/. */ + +//! Backend-agnostic askama filters + +use crate::interface::{CallbackInterface, ComponentInterface, Enum, Function, Object, Record}; +use askama::Result; +use std::fmt; + +// Need to define an error that implements std::error::Error, which neither String nor +// anyhow::Error do. +#[derive(Debug)] +struct UniFFIError { + message: String, +} + +impl UniFFIError { + fn new(message: String) -> Self { + Self { message } + } +} + +impl fmt::Display for UniFFIError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for UniFFIError {} + +macro_rules! lookup_error { + ($($args:tt)*) => { + askama::Error::Custom(Box::new(UniFFIError::new(format!($($args)*)))) + } +} + +/// Get an Enum definition by name +pub fn get_enum_definition<'a>(ci: &'a ComponentInterface, name: &str) -> Result<&'a Enum> { + ci.get_enum_definition(name) + .ok_or_else(|| lookup_error!("enum {name} not found")) +} + +/// Get a Record definition by name +pub fn get_record_definition<'a>(ci: &'a ComponentInterface, name: &str) -> Result<&'a Record> { + ci.get_record_definition(name) + .ok_or_else(|| lookup_error!("record {name} not found")) +} + +/// Get a Function definition by name +pub fn get_function_definition<'a>(ci: &'a ComponentInterface, name: &str) -> Result<&'a Function> { + ci.get_function_definition(name) + .ok_or_else(|| lookup_error!("function {name} not found")) +} + +/// Get an Object definition by name +pub fn get_object_definition<'a>(ci: &'a ComponentInterface, name: &str) -> Result<&'a Object> { + ci.get_object_definition(name) + .ok_or_else(|| lookup_error!("object {name} not found")) +} + +/// Get an Callback Interface definition by name +pub fn get_callback_interface_definition<'a>( + ci: &'a ComponentInterface, + name: &str, +) -> Result<&'a CallbackInterface> { + ci.get_callback_interface_definition(name) + .ok_or_else(|| lookup_error!("callback interface {name} not found")) +} diff --git a/uniffi_bindgen/src/backend/mod.rs b/uniffi_bindgen/src/backend/mod.rs index 73bba483cb..6abcfc10d7 100644 --- a/uniffi_bindgen/src/backend/mod.rs +++ b/uniffi_bindgen/src/backend/mod.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ mod config; +pub mod filters; mod types; pub use crate::interface::{Literal, Type}; diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index 040bb2c8a7..d17e375493 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -356,6 +356,7 @@ impl AsCodeType for T { pub mod filters { use super::*; + pub use crate::backend::filters::*; pub fn type_name(as_ct: &impl AsCodeType) -> Result { Ok(as_ct.as_codetype().type_label()) diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt index f3cc097239..ada9898961 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -1,4 +1,4 @@ -{%- let cbi = ci.get_callback_interface_definition(name).unwrap() %} +{%- let cbi = ci|get_callback_interface_definition(name) %} {%- let type_name = cbi|type_name %} {%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt index 6dc16e989a..56f685225e 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt @@ -1,6 +1,7 @@ {%- let type_name = type_|error_type_name %} {%- let ffi_converter_name = type_|error_ffi_converter_name %} {%- let canonical_type_name = type_|error_canonical_name %} + {% if e.is_flat() %} sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} { // Each variant is a nested class diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt index 95621f850e..044ca99f2a 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -1,4 +1,4 @@ -{%- let obj = ci.get_object_definition(name).unwrap() %} +{%- 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") }} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt index a17f4d42eb..9b5fa3362d 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt @@ -1,4 +1,4 @@ -{%- let rec = ci.get_record_definition(name).unwrap() %} +{%- let rec = ci|get_record_definition(name) %} data class {{ type_name }} ( {%- for field in rec.fields() %} diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index c3831a35c1..7dc4855754 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -371,6 +371,7 @@ impl AsCodeType for T { pub mod filters { use super::*; + pub use crate::backend::filters::*; pub fn type_name(as_ct: &impl AsCodeType) -> Result { Ok(as_ct.as_codetype().type_label()) diff --git a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py index b847f04971..3086add300 100644 --- a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py @@ -1,4 +1,4 @@ -{%- let cbi = ci.get_callback_interface_definition(id).unwrap() %} +{%- let cbi = ci|get_callback_interface_definition(id) %} {%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} {% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} diff --git a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 80e5c96ff7..dd7b58e9e6 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -1,4 +1,4 @@ -{%- let obj = ci.get_object_definition(name).unwrap() %} +{%- let obj = ci|get_object_definition(name) %} class {{ type_name }}: {%- match obj.primary_constructor() %} diff --git a/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py index 2009d59e8a..ed8afb26d3 100644 --- a/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py @@ -1,4 +1,4 @@ -{%- let rec = ci.get_record_definition(name).unwrap() %} +{%- let rec = ci|get_record_definition(name) %} class {{ type_name }}: def __init__(self, {% for field in rec.fields() %} diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index 1bc3f1e5eb..490422e529 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -132,6 +132,7 @@ impl<'a> RubyWrapper<'a> { mod filters { use super::*; + pub use crate::backend::filters::*; pub fn type_ffi(type_: &FfiType) -> Result { Ok(match type_ { diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb index 9035563ade..8cdd88dff3 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb @@ -169,7 +169,7 @@ def write_{{ canonical_type_name }}(obj) {% when Type::Enum with (enum_name) -%} {% if !ci.is_name_used_as_error(enum_name) %} - {%- let e = ci.get_enum_definition(enum_name).unwrap() -%} + {%- let e = ci|get_enum_definition(enum_name) -%} # The Enum type {{ enum_name }}. def write_{{ canonical_type_name }}(v) @@ -189,7 +189,7 @@ def write_{{ canonical_type_name }}(v) {% endif %} {% when Type::Record with (record_name) -%} - {%- let rec = ci.get_record_definition(record_name).unwrap() -%} + {%- let rec = ci|get_record_definition(record_name) -%} # The Record type {{ record_name }}. def write_{{ canonical_type_name }}(v) diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb index 04cf09f7d0..efdc59f5f9 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb @@ -159,7 +159,7 @@ def read{{ canonical_type_name }} end {% when Type::Enum with (name) -%} - {%- let e = ci.get_enum_definition(name).unwrap() -%} + {%- let e = ci|get_enum_definition(name) -%} {% if !ci.is_name_used_as_error(name) %} {% let enum_name = name %} # The Enum type {{ enum_name }}. @@ -231,7 +231,7 @@ def read{{ canonical_type_name }} {% endif %} {% when Type::Record with (record_name) -%} - {%- let rec = ci.get_record_definition(record_name).unwrap() -%} + {%- let rec = ci|get_record_definition(record_name) -%} # The Record type {{ record_name }}. def read{{ canonical_type_name }} diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb index b7ea8a0300..6bc5d7348f 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb @@ -125,7 +125,7 @@ def consumeInto{{ canonical_type_name }} end {% when Type::Record with (record_name) -%} - {%- let rec = ci.get_record_definition(record_name).unwrap() -%} + {%- let rec = ci|get_record_definition(record_name) -%} # The Record type {{ record_name }}. def self.alloc_from_{{ canonical_type_name }}(v) @@ -143,7 +143,7 @@ def consumeInto{{ canonical_type_name }} {% when Type::Enum with (enum_name) -%} {% if !ci.is_name_used_as_error(enum_name) %} - {%- let e = ci.get_enum_definition(enum_name).unwrap() -%} + {%- let e = ci|get_enum_definition(enum_name) -%} # The Enum type {{ enum_name }}. def self.alloc_from_{{ canonical_type_name }}(v) diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 741bf8729b..d557adab88 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -384,6 +384,7 @@ impl SwiftCodeOracle { pub mod filters { use super::*; + pub use crate::backend::filters::*; fn oracle() -> &'static SwiftCodeOracle { &SwiftCodeOracle diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index f4e45cd4af..a915b27d55 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -1,4 +1,4 @@ -{%- let cbi = ci.get_callback_interface_definition(name).unwrap() %} +{%- 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 %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index ebb78c224c..0de3118707 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -1,4 +1,4 @@ -{%- let obj = ci.get_object_definition(name).unwrap() %} +{%- 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) -%} diff --git a/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift index 7f2de44052..41938be03e 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift @@ -1,4 +1,4 @@ -{%- let rec = ci.get_record_definition(name).unwrap() %} +{%- let rec = ci|get_record_definition(name) %} public struct {{ type_name }} { {%- for field in rec.fields() %} public var {{ field.name()|var_name }}: {{ field|type_name }} diff --git a/uniffi_bindgen/src/interface/callbacks.rs b/uniffi_bindgen/src/interface/callbacks.rs index 8313f87256..a535fc676b 100644 --- a/uniffi_bindgen/src/interface/callbacks.rs +++ b/uniffi_bindgen/src/interface/callbacks.rs @@ -56,7 +56,7 @@ pub struct CallbackInterface { } impl CallbackInterface { - fn new(name: String) -> CallbackInterface { + pub fn new(name: String) -> CallbackInterface { CallbackInterface { name, methods: Default::default(), diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index 60bacde09a..8195fe97f5 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -50,7 +50,7 @@ use std::{ iter, }; -use anyhow::{bail, ensure, Result}; +use anyhow::{anyhow, bail, ensure, Result}; pub mod types; pub use types::{AsType, ExternalKind, ObjectImpl, Type}; @@ -74,7 +74,7 @@ pub use record::{Field, Record}; pub mod ffi; pub use ffi::{FfiArgument, FfiFunction, FfiType}; -use uniffi_meta::{ConstructorMetadata, MethodMetadata, ObjectMetadata}; +use uniffi_meta::{ConstructorMetadata, ObjectMetadata, TraitMethodMetadata}; // This needs to match the minor version of the `uniffi` crate. See // `docs/uniffi-versioning.md` for details. @@ -100,6 +100,8 @@ pub struct ComponentInterface { callback_interfaces: Vec, // Type names which were seen used as an error. errors: HashSet, + // Types which were seen used as callback interface error. + callback_interface_throws_types: BTreeSet, } impl ComponentInterface { @@ -119,7 +121,7 @@ impl ComponentInterface { bail!("parse error"); } // Unconditionally add the String type, which is used by the panic handling - ci.types.add_known_type(&Type::String)?; + ci.types.add_known_type(&Type::String); // We process the WebIDL definitions in two passes. // First, go through and look for all the named types. ci.types.add_type_definitions_from(defns.as_slice())?; @@ -270,6 +272,10 @@ impl ComponentInterface { self.types.get_type_definition(name) } + pub fn is_callback_interface_throws_type(&self, type_: Type) -> bool { + self.callback_interface_throws_types.contains(&type_) + } + /// Iterate over all types contained in the given item. /// /// This method uses `iter_types` to iterate over the types contained within the given type, @@ -518,9 +524,20 @@ impl ComponentInterface { .into_iter() .map(|c| (c.checksum_fn_name(), c.checksum())) }); + let callback_method_checksums = self.callback_interfaces.iter().flat_map(|cbi| { + cbi.methods().into_iter().filter_map(|m| { + if m.checksum_fn_name().is_empty() { + // UDL-based callbacks don't have checksum functions, skip these + None + } else { + Some((m.checksum_fn_name(), m.checksum())) + } + }) + }); func_checksums .chain(method_checksums) .chain(constructor_checksums) + .chain(callback_method_checksums) .map(|(fn_name, checksum)| (fn_name.to_string(), checksum)) } @@ -586,7 +603,7 @@ impl ComponentInterface { Entry::Vacant(v) => { for variant in defn.variants() { for field in variant.fields() { - self.types.add_known_type(&field.as_type())?; + self.types.add_known_type(&field.as_type()); } } v.insert(defn); @@ -612,7 +629,7 @@ impl ComponentInterface { match self.records.entry(defn.name().to_owned()) { Entry::Vacant(v) => { for field in defn.fields() { - self.types.add_known_type(&field.as_type())?; + self.types.add_known_type(&field.as_type()); } v.insert(defn); } @@ -635,10 +652,10 @@ impl ComponentInterface { /// Called by `APIBuilder` impls to add a newly-parsed function definition to the `ComponentInterface`. pub(super) fn add_function_definition(&mut self, defn: Function) -> Result<()> { for arg in &defn.arguments { - self.types.add_known_type(&arg.type_)?; + self.types.add_known_type(&arg.type_); } if let Some(ty) = &defn.return_type { - self.types.add_known_type(ty)?; + self.types.add_known_type(ty); } // Since functions are not a first-class type, we have to check for duplicates here @@ -651,7 +668,7 @@ impl ComponentInterface { } if defn.is_async() { // Async functions depend on the foreign executor - self.types.add_known_type(&Type::ForeignExecutor)?; + self.types.add_known_type(&Type::ForeignExecutor); } self.functions.push(defn); @@ -659,46 +676,44 @@ impl ComponentInterface { } pub(super) fn add_constructor_meta(&mut self, meta: ConstructorMetadata) -> Result<()> { - let object = get_or_insert_object(&mut self.objects, &meta.self_name, ObjectImpl::Struct); + let object = get_object(&mut self.objects, &meta.self_name) + .ok_or_else(|| anyhow!("add_constructor_meta: object {} not found", &meta.self_name))?; let defn: Constructor = meta.into(); for arg in &defn.arguments { - self.types.add_known_type(&arg.type_)?; + self.types.add_known_type(&arg.type_); } object.constructors.push(defn); Ok(()) } - pub(super) fn add_method_meta(&mut self, meta: MethodMetadata) -> Result<()> { - let imp = ObjectImpl::from_is_trait(meta.self_is_trait); - let object = get_or_insert_object(&mut self.objects, &meta.self_name, imp); + pub(super) fn add_method_meta(&mut self, meta: impl Into) -> Result<()> { let defn: Method = meta.into(); + let object = get_object(&mut self.objects, &defn.object_name) + .ok_or_else(|| anyhow!("add_method_meta: object {} not found", &defn.object_name))?; for arg in &defn.arguments { - self.types.add_known_type(&arg.type_)?; + self.types.add_known_type(&arg.type_); } if let Some(ty) = &defn.return_type { - self.types.add_known_type(ty)?; + self.types.add_known_type(ty); } if defn.is_async() { // Async functions depend on the foreign executor - self.types.add_known_type(&Type::ForeignExecutor)?; + self.types.add_known_type(&Type::ForeignExecutor); } object.methods.push(defn); Ok(()) } - pub(super) fn add_object_free_fn(&mut self, meta: ObjectMetadata) -> Result<()> { - let imp = ObjectImpl::from_is_trait(meta.is_trait); - self.types.add_known_type(&Type::Object { - name: meta.name.clone(), - imp, - })?; - let object = get_or_insert_object(&mut self.objects, &meta.name, imp); - object.ffi_func_free.name = meta.free_ffi_symbol_name(); - Ok(()) + pub(super) fn add_object_meta(&mut self, meta: ObjectMetadata) { + let free_name = meta.free_ffi_symbol_name(); + let mut obj = Object::new(meta.name, ObjectImpl::from_is_trait(meta.is_trait)); + obj.ffi_func_free.name = free_name; + self.types.add_known_type(&obj.as_type()); + self.add_object_definition(obj); } /// Called by `APIBuilder` impls to add a newly-parsed object definition to the `ComponentInterface`. @@ -716,11 +731,36 @@ impl ComponentInterface { } /// Called by `APIBuilder` impls to add a newly-parsed callback interface definition to the `ComponentInterface`. - fn add_callback_interface_definition(&mut self, defn: CallbackInterface) { + pub(super) fn add_callback_interface_definition(&mut self, defn: CallbackInterface) { // Note that there will be no duplicates thanks to the previous type-finding pass. + for method in defn.methods() { + if let Some(error) = method.throws_type() { + self.callback_interface_throws_types.insert(error.clone()); + } + } self.callback_interfaces.push(defn); } + pub(super) fn add_trait_method_meta(&mut self, meta: TraitMethodMetadata) -> Result<()> { + if let Some(cbi) = get_callback_interface(&mut self.callback_interfaces, &meta.trait_name) { + // uniffi_meta should ensure that we process callback interface methods in order, double + // check that here + if cbi.methods.len() != meta.index as usize { + bail!( + "UniFFI internal error: callback interface method index mismatch for {}::{} (expected {}, saw {})", + meta.trait_name, + meta.name, + cbi.methods.len(), + meta.index, + ); + } + cbi.methods.push(meta.into()); + } else { + self.add_method_meta(meta)?; + } + Ok(()) + } + /// Perform global consistency checks on the declared interface. /// /// This method checks for consistency problems in the declared interface @@ -793,24 +833,15 @@ impl ComponentInterface { } } -fn get_or_insert_object<'a>( - objects: &'a mut Vec, +fn get_object<'a>(objects: &'a mut [Object], name: &str) -> Option<&'a mut Object> { + objects.iter_mut().find(|o| o.name == name) +} + +fn get_callback_interface<'a>( + callback_interfaces: &'a mut [CallbackInterface], name: &str, - imp: ObjectImpl, -) -> &'a mut Object { - // The find-based way of writing this currently runs into a borrow checker - // error, so we use position - match objects.iter_mut().position(|o| o.name == name) { - Some(idx) => { - // what we created before must match the implementation. - assert_eq!(objects[idx].imp, imp); - &mut objects[idx] - } - None => { - objects.push(Object::new(name.to_owned(), imp)); - objects.last_mut().unwrap() - } - } +) -> Option<&'a mut CallbackInterface> { + callback_interfaces.iter_mut().find(|o| o.name == name) } /// Stateful iterator for yielding all types contained in a given type. diff --git a/uniffi_bindgen/src/interface/object.rs b/uniffi_bindgen/src/interface/object.rs index aaf1261339..78a294f0a4 100644 --- a/uniffi_bindgen/src/interface/object.rs +++ b/uniffi_bindgen/src/interface/object.rs @@ -272,10 +272,10 @@ impl APIConverter for weedle::InterfaceDefinition<'_> { // need to add known types as they aren't explicitly referenced in // the UDL if let Some(ref return_type) = return_type { - ci.types.add_known_type(return_type)?; + ci.types.add_known_type(return_type); } for arg in &arguments { - ci.types.add_known_type(&arg.type_)?; + ci.types.add_known_type(&arg.type_); } Ok(Method { // The name is used to create the ffi function for the method. @@ -653,6 +653,29 @@ impl From for Method { } } +impl From for Method { + fn from(meta: uniffi_meta::TraitMethodMetadata) -> Self { + let checksum_fn_name = meta.checksum_symbol_name(); + let return_type = meta.return_type.map(Into::into); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + Self { + name: meta.name, + object_name: meta.trait_name, + is_async: false, + arguments, + return_type, + throws: meta.throws.map(Into::into), + takes_self_by_arc: false, // not yet supported by procmacros? + checksum_fn_name, + checksum_override: Some(meta.checksum), + // These are placeholder values that don't affect any behavior since we don't create + // scaffolding functions for callback interface methods + ffi_func: FfiFunction::default(), + object_impl: ObjectImpl::Struct, + } + } +} + impl APIConverter for weedle::interface::OperationInterfaceMember<'_> { fn convert(&self, ci: &mut ComponentInterface) -> Result { if self.special.is_some() { diff --git a/uniffi_bindgen/src/interface/types/mod.rs b/uniffi_bindgen/src/interface/types/mod.rs index c544079261..cdefb68db9 100644 --- a/uniffi_bindgen/src/interface/types/mod.rs +++ b/uniffi_bindgen/src/interface/types/mod.rs @@ -307,7 +307,7 @@ impl TypeUniverse { if resolve_builtin_type(name).is_some() { bail!("please don't shadow builtin types ({name}, {:?})", type_,); } - self.add_known_type(&type_)?; + self.add_known_type(&type_); match self.type_definitions.entry(name.to_string()) { Entry::Occupied(o) => { let existing_def = o.get(); @@ -345,7 +345,7 @@ impl TypeUniverse { } /// Add a [Type] to the set of all types seen in the component interface. - pub fn add_known_type(&mut self, type_: &Type) -> Result<()> { + pub fn add_known_type(&mut self, type_: &Type) { // Types are more likely to already be known than not, so avoid unnecessary cloning. if !self.all_known_types.contains(type_) { self.all_known_types.insert(type_.to_owned()); @@ -355,17 +355,15 @@ impl TypeUniverse { // this is important if the inner type isn't ever mentioned outside one of these // generic builtin types. match type_ { - Type::Optional(t) => self.add_known_type(t)?, - Type::Sequence(t) => self.add_known_type(t)?, + Type::Optional(t) => self.add_known_type(t), + Type::Sequence(t) => self.add_known_type(t), Type::Map(k, v) => { - self.add_known_type(k)?; - self.add_known_type(v)?; + self.add_known_type(k); + self.add_known_type(v); } _ => {} } } - - Ok(()) } /// Check if a [Type] is present diff --git a/uniffi_bindgen/src/interface/types/resolver.rs b/uniffi_bindgen/src/interface/types/resolver.rs index 262369e125..f1316f2ad9 100644 --- a/uniffi_bindgen/src/interface/types/resolver.rs +++ b/uniffi_bindgen/src/interface/types/resolver.rs @@ -91,7 +91,7 @@ impl TypeResolver for weedle::types::MayBeNull { None => Ok(type_), Some(_) => { let ty = Type::Optional(Box::new(type_)); - types.add_known_type(&ty)?; + types.add_known_type(&ty); Ok(ty) } } @@ -120,7 +120,7 @@ impl TypeResolver for weedle::types::SequenceType<'_> { fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result { let t = self.generics.body.as_ref().resolve_type_expression(types)?; let ty = Type::Sequence(Box::new(t)); - types.add_known_type(&ty)?; + types.add_known_type(&ty); Ok(ty) } } @@ -134,7 +134,7 @@ impl TypeResolver for weedle::types::RecordKeyType<'_> { consider using DOMString or string", ), DOM(_) => { - types.add_known_type(&Type::String)?; + types.add_known_type(&Type::String); Ok(Type::String) } NonAny(t) => t.resolve_type_expression(types), @@ -147,7 +147,7 @@ impl TypeResolver for weedle::types::RecordType<'_> { let key_type = self.generics.body.0.resolve_type_expression(types)?; let value_type = self.generics.body.2.resolve_type_expression(types)?; let map = Type::Map(Box::new(key_type), Box::new(value_type)); - types.add_known_type(&map)?; + types.add_known_type(&map); Ok(map) } } @@ -156,12 +156,12 @@ impl TypeResolver for weedle::common::Identifier<'_> { fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result { match resolve_builtin_type(self.0) { Some(type_) => { - types.add_known_type(&type_)?; + types.add_known_type(&type_); Ok(type_) } None => match types.get_type_definition(self.0) { Some(type_) => { - types.add_known_type(&type_)?; + types.add_known_type(&type_); Ok(type_) } None => bail!("unknown type reference: {}", self.0), @@ -172,7 +172,7 @@ impl TypeResolver for weedle::common::Identifier<'_> { impl TypeResolver for weedle::term::Boolean { fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result { - types.add_known_type(&Type::Boolean)?; + types.add_known_type(&Type::Boolean); Ok(Type::Boolean) } } @@ -182,7 +182,7 @@ impl TypeResolver for weedle::types::FloatType { if self.unrestricted.is_some() { bail!("we don't support `unrestricted float`"); } - types.add_known_type(&Type::Float32)?; + types.add_known_type(&Type::Float32); Ok(Type::Float32) } } @@ -192,7 +192,7 @@ impl TypeResolver for weedle::types::DoubleType { if self.unrestricted.is_some() { bail!("we don't support `unrestricted double`"); } - types.add_known_type(&Type::Float64)?; + types.add_known_type(&Type::Float64); Ok(Type::Float64) } } diff --git a/uniffi_bindgen/src/library_mode.rs b/uniffi_bindgen/src/library_mode.rs index dfa39ec89c..7978a24390 100644 --- a/uniffi_bindgen/src/library_mode.rs +++ b/uniffi_bindgen/src/library_mode.rs @@ -23,7 +23,7 @@ use anyhow::{bail, Context}; use camino::Utf8Path; use cargo_metadata::{MetadataCommand, Package}; use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeSet, HashMap, HashSet}, fs, }; use uniffi_meta::group_metadata; @@ -170,7 +170,7 @@ fn find_package_by_crate_name( fn load_component_interface( crate_name: &str, crate_root: &Utf8Path, - metadata: &[uniffi_meta::Metadata], + metadata: &BTreeSet, ) -> Result { let udl_items = metadata .iter() diff --git a/uniffi_bindgen/src/macro_metadata/ci.rs b/uniffi_bindgen/src/macro_metadata/ci.rs index 83d0a0ce36..beea60cbf5 100644 --- a/uniffi_bindgen/src/macro_metadata/ci.rs +++ b/uniffi_bindgen/src/macro_metadata/ci.rs @@ -2,7 +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::interface::{ComponentInterface, Enum, Record, Type}; +use crate::interface::{CallbackInterface, ComponentInterface, Enum, Record, Type}; use anyhow::{bail, Context}; use uniffi_meta::{group_metadata, EnumMetadata, ErrorMetadata, Metadata, MetadataGroup}; @@ -61,7 +61,7 @@ fn add_enum_to_ci( is_flat: bool, ) -> anyhow::Result<()> { let ty = Type::Enum(meta.name.clone()); - iface.types.add_known_type(&ty)?; + iface.types.add_known_type(&ty); iface.types.add_type_definition(&meta.name, ty)?; let enum_ = Enum::try_from_meta(meta, is_flat)?; @@ -84,7 +84,7 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res } Metadata::Record(meta) => { let ty = Type::Record(meta.name.clone()); - iface.types.add_known_type(&ty)?; + iface.types.add_known_type(&ty); iface.types.add_type_definition(&meta.name, ty)?; let record: Record = meta.try_into()?; @@ -95,7 +95,13 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res add_enum_to_ci(iface, meta, flat)?; } Metadata::Object(meta) => { - iface.add_object_free_fn(meta)?; + iface.add_object_meta(meta); + } + Metadata::CallbackInterface(meta) => { + iface.add_callback_interface_definition(CallbackInterface::new(meta.name)); + } + Metadata::TraitMethod(meta) => { + iface.add_trait_method_meta(meta)?; } Metadata::Error(meta) => { iface.note_name_used_as_error(meta.name()); diff --git a/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs index ec5405c893..a4cbe05073 100644 --- a/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs @@ -32,11 +32,17 @@ struct {{ trait_impl }} { handle: u64 } +impl {{ trait_impl }} { + fn new(handle: u64) -> Self { + Self { handle } + } +} + impl Drop for {{ trait_impl }} { fn drop(&mut self) { {{ foreign_callback_internals }}.invoke_callback::<(), crate::UniFfiTag>( self.handle, uniffi::IDX_CALLBACK_FREE, Default::default() - ).expect("Unexpected error dropping {{ trait_impl }}") + ) } } @@ -69,53 +75,8 @@ impl r#{{ trait_name }} for {{ trait_impl }} { {#- Calling into foreign code. #} {{ foreign_callback_internals }}.invoke_callback::<{{ meth|return_type }}, crate::UniFfiTag>(self.handle, {{ loop.index }}, args_rbuf) - {%- match meth.throws_type() %} - {%- when Some(error_type) %} - // `invoke_callback()` returns an Err value for unexpected errors. Convert that into - // the error type returned by the method. Note: we require all error types used in - // CallbackInterfaces to implement From - .unwrap_or_else(|e| Err(e.into())) - {%- when None %} - // No error type, the only option is panicking - .unwrap_or_else(|e| panic!("callback failed. Reason: {}", e.reason)) - {%- endmatch %} - } {%- endfor %} } -unsafe impl ::uniffi::FfiConverter for Box { - type FfiType = u64; - - // Lower and write are tricky to implement because we have a dyn trait as our type. There's - // probably a way to, but this carries lots of thread safety risks, down to impedance - // mismatches between Rust and foreign languages, and our uncertainty around implementations of - // concurrent handlemaps. - // - // The use case for them is also quite exotic: it's passing a foreign callback back to the foreign - // language. - // - // Until we have some certainty, and use cases, we shouldn't use them. - fn lower(_obj: Box) -> Self::FfiType { - panic!("Lowering CallbackInterface not supported") - } - - fn write(_obj: Box, _buf: &mut std::vec::Vec) { - panic!("Writing CallbackInterface not supported") - } - - fn try_lift(v: Self::FfiType) -> uniffi::deps::anyhow::Result> { - Ok(Box::new({{ trait_impl }} { handle: v })) - } - - fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result> { - use uniffi::deps::bytes::Buf; - uniffi::check_remaining(buf, 8)?; - Self::try_lift(buf.get_u64()) - } - - ::uniffi::ffi_converter_default_return!(crate::UniFfiTag); - - const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_CALLBACK_INTERFACE) - .concat_str("{{ cbi.name() }}"); -} +::uniffi::ffi_converter_callback_interface!(r#{{ trait_name }}, {{ trait_impl }}, "{{ cbi.name() }}", crate::UniFfiTag); diff --git a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs index f0cf1e61b7..bfe2a4c607 100644 --- a/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -15,6 +15,9 @@ with_try_read, {%- endif %} {%- endif %} + {%- if ci.is_callback_interface_throws_type(e.as_type()) %} + handle_unknown_callback_error, + {%- endif %} )] enum r#{{ e.name() }} { {%- for variant in e.variants() %} diff --git a/uniffi_core/src/ffi/foreigncallbacks.rs b/uniffi_core/src/ffi/foreigncallbacks.rs index a3e0d9cd1b..734db6fe9c 100644 --- a/uniffi_core/src/ffi/foreigncallbacks.rs +++ b/uniffi_core/src/ffi/foreigncallbacks.rs @@ -224,20 +224,15 @@ impl ForeignCallbackInternals { } /// Invoke a callback interface method on the foreign side and return the result - pub fn invoke_callback( - &self, - handle: u64, - method: u32, - args: RustBuffer, - ) -> Result + pub fn invoke_callback(&self, handle: u64, method: u32, args: RustBuffer) -> R where R: FfiConverter, { let mut ret_rbuf = RustBuffer::new(); let callback_result = self.call_callback(handle, method, args, &mut ret_rbuf); match callback_result { - CALLBACK_SUCCESS => Ok(R::lift_callback_return(ret_rbuf)), - CALLBACK_ERROR => Ok(R::lift_callback_error(ret_rbuf)), + CALLBACK_SUCCESS => R::lift_callback_return(ret_rbuf), + CALLBACK_ERROR => R::lift_callback_error(ret_rbuf), CALLBACK_UNEXPECTED_ERROR => { let reason = if !ret_rbuf.is_empty() { match >::try_lift(ret_rbuf) { @@ -251,7 +246,7 @@ impl ForeignCallbackInternals { RustBuffer::destroy(ret_rbuf); String::from("[Unknown Reason]") }; - Err(UnexpectedUniFFICallbackError { reason }) + R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason }) } // Other values should never be returned _ => panic!("Callback failed with unexpected return code"), diff --git a/uniffi_core/src/ffi_converter_impls.rs b/uniffi_core/src/ffi_converter_impls.rs index 22cfdec115..003facdf21 100644 --- a/uniffi_core/src/ffi_converter_impls.rs +++ b/uniffi_core/src/ffi_converter_impls.rs @@ -24,7 +24,7 @@ use crate::{ check_remaining, ffi_converter_default_return, ffi_converter_rust_buffer_lift_and_lower, lower_into_rust_buffer, metadata, try_lift_from_rust_buffer, FfiConverter, FutureCallback, - Interface, MetadataBuffer, Result, RustBuffer, RustCallStatus, + Interface, MetadataBuffer, Result, RustBuffer, RustCallStatus, UnexpectedUniFFICallbackError, }; use anyhow::bail; use bytes::buf::{Buf, BufMut}; @@ -582,6 +582,10 @@ where .expect("Error reading callback interface Err result")) } + fn handle_callback_unexpected_error(e: UnexpectedUniFFICallbackError) -> Self { + Err(E::handle_callback_unexpected_error(e)) + } + fn invoke_future_callback( callback: Self::FutureCallback, callback_data: *const (), diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index 048a1bd716..ae7e4d381a 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -219,12 +219,12 @@ pub unsafe trait FfiConverter: Sized { /// values of type Self::FfiType, this method is fallible. fn try_lift(v: Self::FfiType) -> Result; - /// Lift a Rust value based on a callback interface method result + /// Lift a Rust value for a callback interface method result fn lift_callback_return(buf: RustBuffer) -> Self { try_lift_from_rust_buffer(buf).expect("Error reading callback interface result") } - /// Lift a Rust value based on a callback interface method error result + /// Lift a Rust value for a callback interface method error result /// /// This is called for "expected errors" -- the callback method returns a Result<> type and the /// foreign code throws an exception that corresponds to the error type. @@ -232,6 +232,19 @@ pub unsafe trait FfiConverter: Sized { panic!("Callback interface method returned unexpected error") } + /// Lift a Rust value for an unexpected callback interface error + /// + /// The main reason this is called is when the callback interface throws an error type that + /// doesn't match the Rust trait definition. It's also called for corner cases, like when the + /// foreign code doesn't follow the FFI contract. + /// + /// The default implementation panics unconditionally. Errors used in callback interfaces + /// handle this using the `From` impl that the library author + /// must provide. + fn handle_callback_unexpected_error(_e: UnexpectedUniFFICallbackError) -> Self { + panic!("Callback interface method returned unexpected error") + } + /// Write a rust value into a buffer, to send over the FFI in serialized form. /// /// This trait method can be used for sending data from rust to the foreign language code, @@ -458,6 +471,50 @@ macro_rules! ffi_converter_trait_decl { } } +/// Macro to implement `FfiConverter` for a callback interface +#[macro_export] +macro_rules! ffi_converter_callback_interface { + ($trait:ident, $T:ty, $name:expr, $uniffi_tag:ty) => { + unsafe impl ::uniffi::FfiConverter<$uniffi_tag> for Box { + type FfiType = u64; + + // Lower and write are tricky to implement because we have a dyn trait as our type. There's + // probably a way to, but this carries lots of thread safety risks, down to impedance + // mismatches between Rust and foreign languages, and our uncertainty around implementations of + // concurrent handlemaps. + // + // The use case for them is also quite exotic: it's passing a foreign callback back to the foreign + // language. + // + // Until we have some certainty, and use cases, we shouldn't use them. + fn lower(_obj: Box) -> Self::FfiType { + panic!("Lowering CallbackInterface not supported") + } + + fn write(_obj: Box, _buf: &mut std::vec::Vec) { + panic!("Writing CallbackInterface not supported") + } + + fn try_lift(v: Self::FfiType) -> uniffi::deps::anyhow::Result> { + Ok(Box::new(<$T>::new(v))) + } + + fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result> { + use uniffi::deps::bytes::Buf; + uniffi::check_remaining(buf, 8)?; + Self::try_lift(buf.get_u64()) + } + + ::uniffi::ffi_converter_default_return!($uniffi_tag); + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code( + ::uniffi::metadata::codes::TYPE_CALLBACK_INTERFACE, + ) + .concat_str($name); + } + }; +} + #[cfg(test)] mod test { use super::{FfiConverter, UniFfiTag}; diff --git a/uniffi_core/src/metadata.rs b/uniffi_core/src/metadata.rs index 9fc906e658..08221b5293 100644 --- a/uniffi_core/src/metadata.rs +++ b/uniffi_core/src/metadata.rs @@ -36,6 +36,8 @@ pub mod codes { pub const NAMESPACE: u8 = 6; pub const CONSTRUCTOR: u8 = 7; pub const UDL_FILE: u8 = 8; + pub const CALLBACK_INTERFACE: u8 = 9; + pub const TRAIT_METHOD: u8 = 10; pub const UNKNOWN: u8 = 255; // Type codes @@ -140,6 +142,21 @@ impl MetadataBuffer { self } + // Concatenate a `u32` value to this buffer + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_u32(mut self, value: u32) -> Self { + assert!(self.size + 4 <= BUF_SIZE); + // store the value as little-endian + self.bytes[self.size] = value as u8; + self.bytes[self.size + 1] = (value >> 8) as u8; + self.bytes[self.size + 2] = (value >> 16) as u8; + self.bytes[self.size + 3] = (value >> 24) as u8; + self.size += 4; + self + } + // Concatenate a `bool` value to this buffer // // This consumes self, which is convenient for the proc-macro code and also allows us to avoid diff --git a/uniffi_macros/src/enum_.rs b/uniffi_macros/src/enum_.rs index 62b0dc74cd..9dc28dbf55 100644 --- a/uniffi_macros/src/enum_.rs +++ b/uniffi_macros/src/enum_.rs @@ -55,6 +55,7 @@ pub(crate) fn enum_ffi_converter_impl( ident, enum_, tag, + false, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) } @@ -63,11 +64,13 @@ pub(crate) fn rich_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, tag: Option<&Path>, + handle_unknown_callback_error: bool, ) -> TokenStream { enum_or_error_ffi_converter_impl( ident, enum_, tag, + handle_unknown_callback_error, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) } @@ -76,6 +79,7 @@ fn enum_or_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, tag: Option<&Path>, + handle_unknown_callback_error: bool, metadata_type_code: TokenStream, ) -> TokenStream { let name = ident_to_string(ident); @@ -116,6 +120,9 @@ fn enum_or_error_ffi_converter_impl( }) }; + let handle_callback_unexpected_error = + handle_callback_unexpected_error_fn(handle_unknown_callback_error); + quote! { #[automatically_derived] unsafe #impl_spec { @@ -130,6 +137,8 @@ fn enum_or_error_ffi_converter_impl( #try_read_impl } + #handle_callback_unexpected_error + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_type_code) .concat_str(#name); } @@ -202,3 +211,20 @@ pub fn variant_metadata(enum_: &DataEnum) -> syn::Result> { ) .collect() } + +/// Generate the `handle_callback_unexpected_error()` implementation +/// +/// If handle_unknown_callback_error is true, this will use the `From` +/// implementation that the library author must provide. +/// +/// If handle_unknown_callback_error is false, then we won't generate any code, falling back to the default +/// implementation which panics. +pub(crate) fn handle_callback_unexpected_error_fn( + handle_unknown_callback_error: bool, +) -> Option { + handle_unknown_callback_error.then(|| quote! { + fn handle_callback_unexpected_error(e: ::uniffi::UnexpectedUniFFICallbackError) -> Self { + >::from(e) + } + }) +} diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index 2b8cb1c1f7..cf090db312 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -6,7 +6,7 @@ use syn::{ }; use crate::{ - enum_::{rich_error_ffi_converter_impl, variant_metadata}, + enum_::{handle_callback_unexpected_error_fn, rich_error_ffi_converter_impl, variant_metadata}, util::{ chain, create_metadata_items, either_attribute_arg, ident_to_string, mod_path, parse_comma_separated, tagged_impl_header, try_metadata_value_from_usize, @@ -71,9 +71,15 @@ fn error_ffi_converter_impl(ident: &Ident, enum_: &DataEnum, attr: &ErrorAttr) - enum_, attr.tag.as_ref(), attr.with_try_read.is_some(), + attr.handle_unknown_callback_error.is_some(), ) } else { - rich_error_ffi_converter_impl(ident, enum_, attr.tag.as_ref()) + rich_error_ffi_converter_impl( + ident, + enum_, + attr.tag.as_ref(), + attr.handle_unknown_callback_error.is_some(), + ) } } @@ -86,6 +92,7 @@ fn flat_error_ffi_converter_impl( enum_: &DataEnum, tag: Option<&Path>, implement_try_read: bool, + handle_unknown_callback_error: bool, ) -> TokenStream { let name = ident_to_string(ident); let impl_spec = tagged_impl_header("FfiConverter", ident, tag); @@ -128,6 +135,9 @@ fn flat_error_ffi_converter_impl( quote! { ::std::panic!("try_read not supported for flat errors") } }; + let handle_callback_unexpected_error = + handle_callback_unexpected_error_fn(handle_unknown_callback_error); + quote! { #[automatically_derived] unsafe #impl_spec { @@ -142,6 +152,8 @@ fn flat_error_ffi_converter_impl( #try_read_impl } + #handle_callback_unexpected_error + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_ENUM) .concat_str(#name); } @@ -186,6 +198,7 @@ mod kw { syn::custom_keyword!(tag); syn::custom_keyword!(flat_error); syn::custom_keyword!(with_try_read); + syn::custom_keyword!(handle_unknown_callback_error); } #[derive(Default)] @@ -193,6 +206,8 @@ pub(crate) struct ErrorAttr { tag: Option, flat: Option, with_try_read: Option, + /// Can this error be used in a callback interface? + handle_unknown_callback_error: Option, } impl UniffiAttributeArgs for ErrorAttr { @@ -215,6 +230,11 @@ impl UniffiAttributeArgs for ErrorAttr { with_try_read: input.parse()?, ..Self::default() }) + } else if lookahead.peek(kw::handle_unknown_callback_error) { + Ok(Self { + handle_unknown_callback_error: input.parse()?, + ..Self::default() + }) } else { Err(lookahead.error()) } @@ -225,6 +245,10 @@ impl UniffiAttributeArgs for ErrorAttr { tag: either_attribute_arg(self.tag, other.tag)?, flat: either_attribute_arg(self.flat, other.flat)?, with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?, + handle_unknown_callback_error: either_attribute_arg( + self.handle_unknown_callback_error, + other.handle_unknown_callback_error, + )?, }) } } diff --git a/uniffi_macros/src/export.rs b/uniffi_macros/src/export.rs index 4e6dc523c8..3813b83c30 100644 --- a/uniffi_macros/src/export.rs +++ b/uniffi_macros/src/export.rs @@ -7,6 +7,7 @@ use quote::{quote, quote_spanned}; use syn::{visit_mut::VisitMut, Item, Type}; mod attributes; +mod callback_interface; mod item; mod scaffolding; @@ -32,25 +33,25 @@ pub(crate) fn expand_export( // new functions outside of the `impl`). rewrite_self_type(&mut item); - let metadata = ExportItem::new(item)?; + let metadata = ExportItem::new(item, &args)?; match metadata { - ExportItem::Function { sig } => gen_fn_scaffolding(sig, &mod_path, &args), + ExportItem::Function { sig } => gen_fn_scaffolding(sig, &args), ExportItem::Impl { items, self_ident } => { let item_tokens: TokenStream = items .into_iter() .map(|item| match item? { - ImplItem::Constructor(sig) => { - gen_constructor_scaffolding(sig, &mod_path, &self_ident, &args) - } - ImplItem::Method(sig) => { - gen_method_scaffolding(sig, &mod_path, &self_ident, &args, false) - } + ImplItem::Constructor(sig) => gen_constructor_scaffolding(sig, &args), + ImplItem::Method(sig) => gen_method_scaffolding(sig, &args), }) .collect::>()?; Ok(quote_spanned! { self_ident.span() => #item_tokens }) } - ExportItem::Trait { items, self_ident } => { + ExportItem::Trait { + items, + self_ident, + callback_interface: false, + } => { let name = ident_to_string(&self_ident); let free_fn_ident = Ident::new(&free_fn_symbol_name(&mod_path, &name), Span::call_site()); @@ -73,9 +74,7 @@ pub(crate) fn expand_export( let impl_tokens: TokenStream = items .into_iter() .map(|item| match item? { - ImplItem::Method(sig) => { - gen_method_scaffolding(sig, &mod_path, &self_ident, &args, true) - } + ImplItem::Method(sig) => gen_method_scaffolding(sig, &args), _ => unreachable!("traits have no constructors"), }) .collect::>()?; @@ -93,6 +92,65 @@ pub(crate) fn expand_export( #impl_tokens }) } + ExportItem::Trait { + items, + self_ident, + callback_interface: true, + } => { + let trait_name = ident_to_string(&self_ident); + let trait_impl_ident = Ident::new( + &format!("UniFFICallbackHandler{trait_name}"), + Span::call_site(), + ); + let internals_ident = Ident::new( + &format!( + "UNIFFI_FOREIGN_CALLBACK_INTERNALS_{}", + trait_name.to_ascii_uppercase() + ), + Span::call_site(), + ); + + let items = items.into_iter().collect::>>(); + let trait_impl_and_metadata_tokens = match items { + Ok(items) => { + let trait_impl = callback_interface::trait_impl( + &trait_impl_ident, + &self_ident, + &internals_ident, + &items, + ) + .unwrap_or_else(|e| e.into_compile_error()); + let metadata_items = + callback_interface::metadata_items(&self_ident, &items, &mod_path) + .unwrap_or_else(|e| vec![e.into_compile_error()]); + + quote! { + #trait_impl + + #(#metadata_items)* + } + } + Err(e) => e.into_compile_error(), + }; + + let init_ident = Ident::new( + &uniffi_meta::init_callback_fn_symbol_name(&mod_path, &trait_name), + Span::call_site(), + ); + + Ok(quote! { + #[doc(hidden)] + static #internals_ident: ::uniffi::ForeignCallbackInternals = ::uniffi::ForeignCallbackInternals::new(); + + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn #init_ident(callback: ::uniffi::ForeignCallback, _: &mut ::uniffi::RustCallStatus) { + #internals_ident.set_callback(callback); + } + + #trait_impl_and_metadata_tokens + }) + } } } diff --git a/uniffi_macros/src/export/attributes.rs b/uniffi_macros/src/export/attributes.rs index 99e6a38703..fbb69a18f7 100644 --- a/uniffi_macros/src/export/attributes.rs +++ b/uniffi_macros/src/export/attributes.rs @@ -9,11 +9,13 @@ use syn::{ pub(crate) mod kw { syn::custom_keyword!(async_runtime); + syn::custom_keyword!(callback_interface); } #[derive(Default)] pub struct ExportAttributeArguments { pub(crate) async_runtime: Option, + pub(crate) callback_interface: Option, } impl Parse for ExportAttributeArguments { @@ -24,17 +26,31 @@ impl Parse for ExportAttributeArguments { impl UniffiAttributeArgs for ExportAttributeArguments { fn parse_one(input: ParseStream<'_>) -> syn::Result { - let _: kw::async_runtime = input.parse()?; - let _: Token![=] = input.parse()?; - let async_runtime = input.parse()?; - Ok(Self { - async_runtime: Some(async_runtime), - }) + let lookahead = input.lookahead1(); + if lookahead.peek(kw::async_runtime) { + let _: kw::async_runtime = input.parse()?; + let _: Token![=] = input.parse()?; + Ok(Self { + async_runtime: Some(input.parse()?), + ..Self::default() + }) + } else if lookahead.peek(kw::callback_interface) { + Ok(Self { + callback_interface: input.parse()?, + ..Self::default() + }) + } else { + Ok(Self::default()) + } } fn merge(self, other: Self) -> syn::Result { Ok(Self { async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, + callback_interface: either_attribute_arg( + self.callback_interface, + other.callback_interface, + )?, }) } } diff --git a/uniffi_macros/src/export/callback_interface.rs b/uniffi_macros/src/export/callback_interface.rs new file mode 100644 index 0000000000..232231719e --- /dev/null +++ b/uniffi_macros/src/export/callback_interface.rs @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::{ + export::ImplItem, + fnsig::{FnKind, FnSignature}, + util::{create_metadata_items, ident_to_string}, +}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use std::iter; +use syn::Ident; + +pub(super) fn trait_impl( + ident: &Ident, + trait_ident: &Ident, + internals_ident: &Ident, + items: &[ImplItem], +) -> syn::Result { + let trait_name = ident_to_string(trait_ident); + let trait_impl_methods = items + .iter() + .map(|item| match item { + ImplItem::Method(sig) => gen_method_impl(sig, internals_ident), + _ => unreachable!("traits have no constructors"), + }) + .collect::>()?; + + Ok(quote! { + #[doc(hidden)] + #[derive(Debug)] + struct #ident { + handle: u64, + } + + impl #ident { + fn new(handle: u64) -> Self { + Self { handle } + } + } + + impl ::std::ops::Drop for #ident { + fn drop(&mut self) { + #internals_ident.invoke_callback::<(), crate::UniFfiTag>( + self.handle, uniffi::IDX_CALLBACK_FREE, Default::default() + ) + } + } + + ::uniffi::deps::static_assertions::assert_impl_all!(#ident: Send); + + impl #trait_ident for #ident { + #trait_impl_methods + } + + ::uniffi::ffi_converter_callback_interface!(#trait_ident, #ident, #trait_name, crate::UniFfiTag); + }) +} + +fn gen_method_impl(sig: &FnSignature, internals_ident: &Ident) -> syn::Result { + let FnSignature { + ident, + return_ty, + kind, + receiver, + .. + } = sig; + let index = match kind { + // Note: the callback index is 1-based, since 0 is reserved for the free function + FnKind::TraitMethod { index, .. } => index + 1, + k => { + return Err(syn::Error::new( + sig.span, + format!( + "Internal UniFFI error: Unexpected function kind for callback interface {k:?}" + ), + )); + } + }; + + if receiver.is_none() { + return Err(syn::Error::new( + sig.span, + "callback interface methods must take &self as their first argument", + )); + } + let params = sig.params(); + let buf_ident = Ident::new("uniffi_args_buf", Span::call_site()); + let write_exprs = sig.write_exprs(&buf_ident); + + Ok(quote! { + fn #ident(&self, #(#params),*) -> #return_ty { + #[allow(unused_mut)] + let mut #buf_ident = ::std::vec::Vec::new(); + #(#write_exprs;)* + let uniffi_args_rbuf = uniffi::RustBuffer::from_vec(#buf_ident); + + #internals_ident.invoke_callback::<#return_ty, crate::UniFfiTag>(self.handle, #index, uniffi_args_rbuf) + } + }) +} + +pub(super) fn metadata_items( + self_ident: &Ident, + items: &[ImplItem], + module_path: &str, +) -> syn::Result> { + let trait_name = ident_to_string(self_ident); + let callback_interface_items = create_metadata_items( + "callback_interface", + &trait_name, + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE) + .concat_str(#module_path) + .concat_str(#trait_name) + }, + None, + ); + + iter::once(Ok(callback_interface_items)) + .chain(items.iter().map(|item| match item { + ImplItem::Method(sig) => sig.metadata_items(), + _ => unreachable!("traits have no constructors"), + })) + .collect() +} diff --git a/uniffi_macros/src/export/item.rs b/uniffi_macros/src/export/item.rs index b7793c44f8..7a832e4aa9 100644 --- a/uniffi_macros/src/export/item.rs +++ b/uniffi_macros/src/export/item.rs @@ -2,10 +2,11 @@ * 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 proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; +use crate::fnsig::FnSignature; +use proc_macro2::{Ident, Span}; +use quote::ToTokens; -use super::attributes::ExportedImplFnAttributes; +use super::attributes::{ExportAttributeArguments, ExportedImplFnAttributes}; pub(super) enum ExportItem { Function { @@ -18,18 +19,19 @@ pub(super) enum ExportItem { Trait { self_ident: Ident, items: Vec>, + callback_interface: bool, }, } impl ExportItem { - pub fn new(item: syn::Item) -> syn::Result { + pub fn new(item: syn::Item, args: &ExportAttributeArguments) -> syn::Result { match item { syn::Item::Fn(item) => { - let sig = FnSignature::new(item.sig)?; + let sig = FnSignature::new_function(item.sig)?; Ok(Self::Function { sig }) } syn::Item::Impl(item) => Self::from_impl(item), - syn::Item::Trait(item) => Self::from_trait(item), + syn::Item::Trait(item) => Self::from_trait(item, args.callback_interface.is_some()), // FIXME: Support const / static? _ => Err(syn::Error::new( Span::call_site(), @@ -82,9 +84,12 @@ impl ExportItem { let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?; let item = if attrs.constructor { - ImplItem::Constructor(ConstructorSignature::new(impl_fn.sig)?) + ImplItem::Constructor(FnSignature::new_constructor( + self_ident.clone(), + impl_fn.sig, + )?) } else { - ImplItem::Method(FnSignature::new(impl_fn.sig)?) + ImplItem::Method(FnSignature::new_method(self_ident.clone(), impl_fn.sig)?) }; Ok(item) @@ -97,7 +102,7 @@ impl ExportItem { }) } - fn from_trait(item: syn::ItemTrait) -> syn::Result { + fn from_trait(item: syn::ItemTrait, callback_interface: bool) -> syn::Result { if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { return Err(syn::Error::new_spanned( &item.generics, @@ -109,7 +114,8 @@ impl ExportItem { let items = item .items .into_iter() - .map(|item| { + .enumerate() + .map(|(i, item)| { let tim = match item { syn::TraitItem::Fn(tim) => tim, _ => { @@ -124,80 +130,33 @@ impl ExportItem { let item = if attrs.constructor { return Err(syn::Error::new_spanned( tim, - "traits can not have constructors", + "exported traits can not have constructors", )); } else { - ImplItem::Method(FnSignature::new(tim.sig)?) + ImplItem::Method(FnSignature::new_trait_method( + self_ident.clone(), + tim.sig, + i as u32, + )?) }; Ok(item) }) .collect(); - Ok(Self::Trait { items, self_ident }) + Ok(Self::Trait { + items, + self_ident, + callback_interface, + }) } } pub(super) enum ImplItem { - Constructor(ConstructorSignature), + Constructor(FnSignature), Method(FnSignature), } -pub(super) struct FnSignature { - pub ident: Ident, - pub is_async: bool, - pub inputs: Vec, - pub output: TokenStream, -} - -impl FnSignature { - fn new(item: syn::Signature) -> syn::Result { - let output = match item.output { - syn::ReturnType::Default => quote! { () }, - syn::ReturnType::Type(_, ty) => quote! { #ty }, - }; - - Ok(Self { - ident: item.ident, - is_async: item.asyncness.is_some(), - inputs: item.inputs.into_iter().collect(), - output, - }) - } -} - -pub(super) struct ConstructorSignature { - pub ident: Ident, - pub inputs: Vec, - pub output: TokenStream, -} - -impl ConstructorSignature { - fn new(item: syn::Signature) -> syn::Result { - let output = match item.output { - syn::ReturnType::Default => quote! { () }, - syn::ReturnType::Type(_, ty) => quote! { #ty }, - }; - - Ok(Self { - ident: item.ident, - inputs: item.inputs.into_iter().collect(), - output, - }) - } -} - -impl From for FnSignature { - fn from(value: ConstructorSignature) -> Self { - Self { - ident: value.ident, - is_async: false, - inputs: value.inputs, - output: value.output, - } - } -} - fn type_as_type_path(ty: &syn::Type) -> syn::Result<&syn::TypePath> { match ty { syn::Type::Group(g) => type_as_type_path(&g.elem), diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index f6265fbd6e..1fdd2af8af 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -2,401 +2,170 @@ * 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 proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use syn::{FnArg, Pat}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use std::iter; -use super::{ - attributes::{AsyncRuntime, ExportAttributeArguments}, - item::{ConstructorSignature, FnSignature}, -}; -use crate::util::{create_metadata_items, ident_to_string, try_metadata_value_from_usize}; +use super::attributes::{AsyncRuntime, ExportAttributeArguments}; +use crate::fnsig::{FnKind, FnSignature, NamedArg}; pub(super) fn gen_fn_scaffolding( sig: FnSignature, - mod_path: &str, arguments: &ExportAttributeArguments, ) -> syn::Result { - let name = &sig.ident; - let name_s = ident_to_string(name); - - let ffi_ident = Ident::new( - &uniffi_meta::fn_symbol_name(mod_path, &name_s), - Span::call_site(), - ); - - const ERROR_MSG: &str = - "uniffi::export must be used on the impl block, not its containing fn's"; - let mut bits = ScaffoldingBits::new(); - bits.collect_params(&sig.inputs, ERROR_MSG); - bits.set_rust_fn_call(quote! { #name }); - let metadata_var = bits.gen_function_meta_static_var(&sig, mod_path)?; - let scaffolding_func = gen_ffi_function(&sig, ffi_ident, &bits, arguments); + if sig.receiver.is_some() { + return Err(syn::Error::new( + sig.span, + "Unexpected self param (Note: uniffi::export must be used on the impl block, not its containing fn's)" + )); + } + let metadata_items = sig.metadata_items()?; + let scaffolding_func = gen_ffi_function(&sig, arguments)?; Ok(quote! { #scaffolding_func - #metadata_var + #metadata_items }) } pub(super) fn gen_constructor_scaffolding( - sig: ConstructorSignature, - mod_path: &str, - self_ident: &Ident, + sig: FnSignature, arguments: &ExportAttributeArguments, ) -> syn::Result { - let ident = &sig.ident; - let name_s = ident_to_string(ident); - - let ffi_ident = Ident::new( - &uniffi_meta::constructor_symbol_name(mod_path, &ident_to_string(self_ident), &name_s), - Span::call_site(), - ); - - const RECEIVER_ERROR: &str = "constructors must not have a self parameter"; - - let mut bits = ScaffoldingBits::new(); - bits.collect_params(&sig.inputs, RECEIVER_ERROR); - bits.set_rust_fn_call(quote! { #self_ident::#ident }); - - let metadata_var = bits.gen_constructor_meta_static_var(self_ident, &sig, mod_path); - let scaffolding_func = gen_ffi_function(&sig.into(), ffi_ident, &bits, arguments); + if sig.receiver.is_some() { + return Err(syn::Error::new( + sig.span, + "constructors must not have a self parameter", + )); + } + let metadata_items = sig.metadata_items()?; + let scaffolding_func = gen_ffi_function(&sig, arguments)?; Ok(quote! { #scaffolding_func - #metadata_var + #metadata_items }) } pub(super) fn gen_method_scaffolding( sig: FnSignature, - mod_path: &str, - self_ident: &Ident, arguments: &ExportAttributeArguments, - is_trait: bool, ) -> syn::Result { - let ident = &sig.ident; - let name_s = ident_to_string(ident); - - let ffi_ident = Ident::new( - &uniffi_meta::method_symbol_name(mod_path, &ident_to_string(self_ident), &name_s), - Span::call_site(), - ); - - const RECEIVER_ERROR: &str = "unreachable: only first parameter can be method receiver"; - let bits = match sig.inputs.first() { - // Method calls - Some(arg) if is_receiver(arg) => { - let ffi_converter = if is_trait { - quote! { - <::std::sync::Arc as ::uniffi::FfiConverter> - } - } else { - quote! { - <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter> - } - }; - let mut bits = ScaffoldingBits::new(); - // The first scaffolding parameter is `this` -- the lowered value for `self` - bits.add_self_param(quote! { this: #ffi_converter::FfiType }); - // This is followed by the method arguments - bits.collect_params(sig.inputs.iter().skip(1), RECEIVER_ERROR); - // Lift self before calling the method. This avoids any issues with temporary - // references. - bits.set_pre_fn_call(quote! { - let this = #ffi_converter::try_lift(this).unwrap_or_else(|err| { - ::std::panic!("Failed to convert arg 'self': {}", err) - }); - }); - bits.set_rust_fn_call(quote! { this.#ident }); - bits - } - // Associated functions - _ => { - return Err(syn::Error::new_spanned( - &sig.ident, - "associated functions are not currently supported", - )) - } + let scaffolding_func = if sig.receiver.is_none() { + return Err(syn::Error::new( + sig.span, + "associated functions are not currently supported", + )); + } else { + gen_ffi_function(&sig, arguments)? }; - let metadata_var = bits.gen_method_meta_static_var(self_ident, &sig, mod_path, is_trait); - let scaffolding_func = gen_ffi_function(&sig, ffi_ident, &bits, arguments); + let metadata_items = sig.metadata_items()?; Ok(quote! { #scaffolding_func - #metadata_var + #metadata_items }) } -fn is_receiver(fn_arg: &FnArg) -> bool { - match fn_arg { - FnArg::Receiver(_) => true, - FnArg::Typed(pat_ty) => matches!(&*pat_ty.pat, Pat::Ident(i) if i.ident == "self"), - } -} - -// Pieces of code for the scaffolding args +// Pieces of code for the scaffolding function struct ScaffoldingBits { - /// Tokenstream that represents the function to call - /// - /// For functions, this is simple the function ident. - /// For methods, this will be `self.{method_name}`. - rust_fn_call: Option, - /// Tokenstream of statements that we should have before `rust_fn_call` - pre_fn_call: Option, /// Parameters for the scaffolding function params: Vec, - /// Expressions to lift the arguments in order to pass them to the exported function - param_lifts: Vec, - /// MetadataBuffer calls to build up the metadata - arg_metadata_calls: Vec, + /// Statements to execute before `rust_fn_call` + pre_fn_call: TokenStream, + /// Tokenstream for the call to the actual Rust function + rust_fn_call: TokenStream, } impl ScaffoldingBits { - fn new() -> Self { + fn new_for_function(sig: &FnSignature) -> Self { + let ident = &sig.ident; + let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect(); + let param_lifts = sig.lift_exprs(); + Self { - rust_fn_call: None, - pre_fn_call: None, - params: vec![], - param_lifts: vec![], - arg_metadata_calls: vec![], + params, + pre_fn_call: quote! {}, + rust_fn_call: quote! { #ident(#(#param_lifts,)*) }, } } - fn collect_param( - &mut self, - param: TokenStream, - param_lift: TokenStream, - metadata_builder_call: TokenStream, - ) { - self.params.push(param); - self.param_lifts.push(param_lift); - self.arg_metadata_calls.push(metadata_builder_call); - } - - fn collect_param_receiver_error(&mut self, receiver: impl ToTokens, receiver_error_msg: &str) { - self.collect_param( - quote! { &self }, - syn::Error::new_spanned(receiver, receiver_error_msg).into_compile_error(), + fn new_for_method(sig: &FnSignature, self_ident: &Ident, is_trait: bool) -> Self { + let ident = &sig.ident; + let ffi_converter = if is_trait { quote! { - .concat_str("") - .concat(::uniffi::metadata::codes::UNKNOWN) - }, - ); - } - - fn collect_params<'a>( - &mut self, - inputs: impl IntoIterator, - receiver_error_msg: &'static str, - ) { - for (i, arg) in inputs.into_iter().enumerate() { - let (ty, name) = match arg { - FnArg::Receiver(r) => { - self.collect_param_receiver_error(r, receiver_error_msg); - continue; - } - FnArg::Typed(pat_ty) => { - let name = match &*pat_ty.pat { - Pat::Ident(i) if i.ident == "self" => { - self.collect_param_receiver_error(i, receiver_error_msg); - continue; - } - Pat::Ident(i) => Some(ident_to_string(&i.ident)), - _ => None, - }; - - (&pat_ty.ty, name) - } - }; - - let arg_n = format_ident!("arg{i}"); - - // FIXME: With UDL, fallible functions use uniffi::lower_anyhow_error_or_panic instead of - // panicking unconditionally. This seems cleaner though. - let panic_fmt = match &name { - Some(name) => format!("Failed to convert arg '{name}': {{}}"), - None => format!("Failed to convert arg #{i}: {{}}"), - }; - let meta_name = name.unwrap_or_else(|| String::from("")); - - self.collect_param( - quote! { #arg_n: <#ty as ::uniffi::FfiConverter>::FfiType }, - quote! { - <#ty as ::uniffi::FfiConverter>::try_lift(#arg_n) - .unwrap_or_else(|err| ::std::panic!(#panic_fmt, err)) - }, - quote! { - .concat_str(#meta_name) - .concat(<#ty as ::uniffi::FfiConverter>::TYPE_ID_META) - }, - ); - } - } - - fn set_rust_fn_call(&mut self, rust_fn_call: TokenStream) { - self.rust_fn_call = Some(rust_fn_call); - } - - fn set_pre_fn_call(&mut self, pre_fn_call: TokenStream) { - self.pre_fn_call = Some(pre_fn_call) - } - - fn add_self_param(&mut self, param: TokenStream) { - self.params.insert(0, param); - } - - fn rust_fn_call(&self) -> TokenStream { - match &self.rust_fn_call { - Some(rust_fn_call) => { - let param_lifts = &self.param_lifts; - quote! { #rust_fn_call(#(#param_lifts),*) } + <::std::sync::Arc as ::uniffi::FfiConverter> } - None => panic!("UniFFI Internal error: ScaffoldingBits.func not set"), - } - } - - fn gen_function_meta_static_var( - &self, - sig: &FnSignature, - mod_path: &str, - ) -> syn::Result { - let name = ident_to_string(&sig.ident); - let return_ty = &sig.output; - let is_async = sig.is_async; - let args_len = try_metadata_value_from_usize( - // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self - // params - self.param_lifts.len(), - "UniFFI limits functions to 256 arguments", - )?; - let arg_metadata_calls = &self.arg_metadata_calls; - Ok(create_metadata_items( - "func", - &name, + } else { quote! { - ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::FUNC) - .concat_str(#mod_path) - .concat_str(#name) - .concat_bool(#is_async) - .concat_value(#args_len) - #(#arg_metadata_calls)* - .concat(<#return_ty as ::uniffi::FfiConverter>::TYPE_ID_META) - }, - Some(uniffi_meta::fn_checksum_symbol_name(mod_path, &name)), - )) - } - - fn gen_constructor_meta_static_var( - &self, - self_ident: &Ident, - sig: &ConstructorSignature, - mod_path: &str, - ) -> TokenStream { - let object_name = ident_to_string(self_ident); - let name = ident_to_string(&sig.ident); - - let args_len = try_metadata_value_from_usize( - // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self - // params - self.param_lifts.len(), - "UniFFI limits functions to 256 arguments", - ); - let metadata_expr = match args_len { - Ok(args_len) => { - let return_ty = &sig.output; - let arg_metadata_calls = &self.arg_metadata_calls; - - quote! { - ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CONSTRUCTOR) - .concat_str(#mod_path) - .concat_str(#object_name) - .concat_str(#name) - .concat_value(#args_len) - #(#arg_metadata_calls)* - .concat( - <#return_ty as ::uniffi::FfiConverter>::TYPE_ID_META, - ) - } + <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter> } - Err(e) => e.into_compile_error(), }; + let params: Vec<_> = iter::once(quote! { uniffi_self_lowered: #ffi_converter::FfiType }) + .chain(sig.scaffolding_params()) + .collect(); + let param_lifts = sig.lift_exprs(); - let symbol_name = - uniffi_meta::constructor_checksum_symbol_name(mod_path, &object_name, &name); - let name = format!("{object_name}_{name}"); - create_metadata_items("constructor", &name, metadata_expr, Some(symbol_name)) + Self { + params, + pre_fn_call: quote! { + let uniffi_self = #ffi_converter::try_lift(uniffi_self_lowered).unwrap_or_else(|err| { + ::std::panic!("Failed to convert arg 'self': {}", err) + }); + }, + rust_fn_call: quote! { uniffi_self.#ident(#(#param_lifts,)*) }, + } } - fn gen_method_meta_static_var( - &self, - self_ident: &Ident, - sig: &FnSignature, - mod_path: &str, - self_is_trait: bool, - ) -> TokenStream { - let object_name = ident_to_string(self_ident); - let name = ident_to_string(&sig.ident); - - let args_len = try_metadata_value_from_usize( - // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self - // params - self.param_lifts.len(), - "UniFFI limits functions to 256 arguments", - ); - let metadata_expr = match args_len { - Ok(args_len) => { - let return_ty = &sig.output; - let is_async = sig.is_async; - let arg_metadata_calls = &self.arg_metadata_calls; + fn new_for_constructor(sig: &FnSignature, self_ident: &Ident) -> Self { + let ident = &sig.ident; + let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect(); + let param_lifts = sig.lift_exprs(); - quote! { - ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::METHOD) - .concat_str(#mod_path) - .concat_str(#object_name) - .concat_bool(#self_is_trait) - .concat_str(#name) - .concat_bool(#is_async) - .concat_value(#args_len) - #(#arg_metadata_calls)* - .concat( - <#return_ty as ::uniffi::FfiConverter>::TYPE_ID_META, - ) - } - } - Err(e) => e.into_compile_error(), - }; - - let symbol_name = uniffi_meta::method_checksum_symbol_name(mod_path, &object_name, &name); - let name = format!("{object_name}_{name}"); - create_metadata_items("method", &name, metadata_expr, Some(symbol_name)) + Self { + params, + pre_fn_call: quote! {}, + rust_fn_call: quote! { #self_ident::#ident(#(#param_lifts,)*) }, + } } } +/// Generate a scaffolding function +/// +/// `pre_fn_call` is the statements that we should execute before the rust call +/// `rust_fn` is the Rust function to call. fn gen_ffi_function( sig: &FnSignature, - ffi_ident: Ident, - bits: &ScaffoldingBits, arguments: &ExportAttributeArguments, -) -> TokenStream { - let name = ident_to_string(&sig.ident); - let pre_fn_call = &bits.pre_fn_call; - let rust_fn_call = bits.rust_fn_call(); - let fn_params = &bits.params; - let return_ty = &sig.output; +) -> syn::Result { + let ScaffoldingBits { + params, + pre_fn_call, + rust_fn_call, + } = match &sig.kind { + FnKind::Function => ScaffoldingBits::new_for_function(sig), + FnKind::Method { self_ident } => ScaffoldingBits::new_for_method(sig, self_ident, false), + FnKind::TraitMethod { self_ident, .. } => { + ScaffoldingBits::new_for_method(sig, self_ident, true) + } + FnKind::Constructor { self_ident } => ScaffoldingBits::new_for_constructor(sig, self_ident), + }; - if !sig.is_async { + let ffi_ident = sig.scaffolding_fn_ident()?; + let name = &sig.name; + let return_ty = &sig.return_ty; + + Ok(if !sig.is_async { if let Some(async_runtime) = &arguments.async_runtime { - return syn::Error::new_spanned( + return Err(syn::Error::new_spanned( async_runtime, "this attribute is only allowed on async functions", - ) - .into_compile_error(); + )); } quote! { #[doc(hidden)] #[no_mangle] pub extern "C" fn #ffi_ident( - #(#fn_params,)* + #(#params,)* call_status: &mut ::uniffi::RustCallStatus, ) -> <#return_ty as ::uniffi::FfiConverter>::ReturnType { ::uniffi::deps::log::debug!(#name); @@ -416,7 +185,7 @@ fn gen_ffi_function( #[doc(hidden)] #[no_mangle] pub extern "C" fn #ffi_ident( - #(#fn_params,)* + #(#params,)* uniffi_executor_handle: ::uniffi::ForeignExecutorHandle, uniffi_callback: <#return_ty as ::uniffi::FfiConverter>::FutureCallback, uniffi_callback_data: *const (), @@ -436,5 +205,5 @@ fn gen_ffi_function( }); } } - } + }) } diff --git a/uniffi_macros/src/fnsig.rs b/uniffi_macros/src/fnsig.rs new file mode 100644 index 0000000000..1b414c59bc --- /dev/null +++ b/uniffi_macros/src/fnsig.rs @@ -0,0 +1,368 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::util::{ + create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize, +}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{spanned::Spanned, FnArg, Ident, Pat, Receiver, ReturnType}; + +pub(crate) struct FnSignature { + pub kind: FnKind, + pub span: Span, + pub mod_path: String, + pub ident: Ident, + pub name: String, + pub is_async: bool, + pub receiver: Option, + pub args: Vec, + pub return_ty: TokenStream, +} + +impl FnSignature { + pub(crate) fn new_function(sig: syn::Signature) -> syn::Result { + Self::new(FnKind::Function, sig) + } + + pub(crate) fn new_method(self_ident: Ident, sig: syn::Signature) -> syn::Result { + Self::new(FnKind::Method { self_ident }, sig) + } + + pub(crate) fn new_constructor(self_ident: Ident, sig: syn::Signature) -> syn::Result { + Self::new(FnKind::Constructor { self_ident }, sig) + } + + pub(crate) fn new_trait_method( + self_ident: Ident, + sig: syn::Signature, + index: u32, + ) -> syn::Result { + Self::new(FnKind::TraitMethod { self_ident, index }, sig) + } + + pub(crate) fn new(kind: FnKind, sig: syn::Signature) -> syn::Result { + let span = sig.span(); + let ident = sig.ident; + let output = match sig.output { + ReturnType::Default => quote! { () }, + ReturnType::Type(_, ty) => quote! { #ty }, + }; + let is_async = sig.asyncness.is_some(); + + if is_async && matches!(kind, FnKind::Constructor { .. }) { + return Err(syn::Error::new( + span, + "Async constructors are not supported", + )); + } + + let mut input_iter = sig.inputs.into_iter().map(Arg::try_from).peekable(); + + let receiver = input_iter + .next_if(|a| matches!(a, Ok(a) if a.is_receiver())) + .map(|a| match a { + Ok(Arg { + kind: ArgKind::Receiver(r), + .. + }) => r, + _ => unreachable!(), + }); + let args = input_iter + .map(|a| { + a.and_then(|a| match a.kind { + ArgKind::Named(named) => Ok(named), + ArgKind::Receiver(_) => { + Err(syn::Error::new(a.span, "Unexpected receiver argument")) + } + }) + }) + .collect::>>()?; + + Ok(Self { + kind, + span, + mod_path: mod_path()?, + name: ident_to_string(&ident), + ident, + is_async, + receiver, + args, + return_ty: output, + }) + } + + /// Lift expressions for each of our arguments + pub fn lift_exprs(&self) -> impl Iterator + '_ { + self.args.iter().map(NamedArg::lift_expr) + } + + /// Write expressions for each of our arguments + pub fn write_exprs<'a>( + &'a self, + buf_ident: &'a Ident, + ) -> impl Iterator + 'a { + self.args.iter().map(|a| a.write_expr(buf_ident)) + } + + /// Parameters expressions for each of our arguments + pub fn params(&self) -> impl Iterator + '_ { + self.args.iter().map(NamedArg::param) + } + + /// Name of the scaffolding function to generate for this function + pub fn scaffolding_fn_ident(&self) -> syn::Result { + let mod_path = &self.mod_path; + let name = &self.name; + let name = match &self.kind { + FnKind::Function => uniffi_meta::fn_symbol_name(mod_path, name), + FnKind::Method { self_ident } | FnKind::TraitMethod { self_ident, .. } => { + uniffi_meta::method_symbol_name(mod_path, &ident_to_string(self_ident), name) + } + FnKind::Constructor { self_ident } => { + uniffi_meta::constructor_symbol_name(mod_path, &ident_to_string(self_ident), name) + } + }; + Ok(Ident::new(&name, Span::call_site())) + } + + /// Scaffolding parameters expressions for each of our arguments + pub fn scaffolding_params(&self) -> impl Iterator + '_ { + self.args.iter().map(NamedArg::scaffolding_param) + } + + /// Generate metadata items for this function + pub(crate) fn metadata_items(&self) -> syn::Result { + let Self { + name, + return_ty, + is_async, + mod_path, + .. + } = &self; + let args_len = try_metadata_value_from_usize( + // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self + // params + self.args.len(), + "UniFFI limits functions to 256 arguments", + )?; + let arg_metadata_calls = self.args.iter().map(NamedArg::metadata_calls); + + match &self.kind { + FnKind::Function => Ok(create_metadata_items( + "func", + name, + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::FUNC) + .concat_str(#mod_path) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::FfiConverter>::TYPE_ID_META) + }, + Some(self.checksum_symbol_name()), + )), + + FnKind::Method { self_ident } => { + let object_name = ident_to_string(self_ident); + Ok(create_metadata_items( + "method", + &format!("{object_name}_{name}"), + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::METHOD) + .concat_str(#mod_path) + .concat_str(#object_name) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::FfiConverter>::TYPE_ID_META) + }, + Some(self.checksum_symbol_name()), + )) + } + + FnKind::TraitMethod { self_ident, index } => { + let object_name = ident_to_string(self_ident); + Ok(create_metadata_items( + "method", + &format!("{object_name}_{name}"), + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TRAIT_METHOD) + .concat_str(#mod_path) + .concat_str(#object_name) + .concat_u32(#index) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::FfiConverter>::TYPE_ID_META) + }, + Some(self.checksum_symbol_name()), + )) + } + + FnKind::Constructor { self_ident } => { + let object_name = ident_to_string(self_ident); + Ok(create_metadata_items( + "constructor", + &format!("{object_name}_{name}"), + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CONSTRUCTOR) + .concat_str(#mod_path) + .concat_str(#object_name) + .concat_str(#name) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::FfiConverter>::TYPE_ID_META) + }, + Some(self.checksum_symbol_name()), + )) + } + } + } + + pub(crate) fn checksum_symbol_name(&self) -> String { + let mod_path = &self.mod_path; + let name = &self.name; + match &self.kind { + FnKind::Function => uniffi_meta::fn_checksum_symbol_name(mod_path, name), + FnKind::Method { self_ident } | FnKind::TraitMethod { self_ident, .. } => { + uniffi_meta::method_checksum_symbol_name( + mod_path, + &ident_to_string(self_ident), + name, + ) + } + FnKind::Constructor { self_ident } => uniffi_meta::constructor_checksum_symbol_name( + mod_path, + &ident_to_string(self_ident), + name, + ), + } + } +} + +pub(crate) struct Arg { + pub(crate) span: Span, + pub(crate) kind: ArgKind, +} + +pub(crate) enum ArgKind { + Receiver(ReceiverArg), + Named(NamedArg), +} + +impl Arg { + pub(crate) fn is_receiver(&self) -> bool { + matches!(self.kind, ArgKind::Receiver(_)) + } +} + +impl TryFrom for Arg { + type Error = syn::Error; + + fn try_from(syn_arg: FnArg) -> syn::Result { + let span = syn_arg.span(); + let kind = match syn_arg { + FnArg::Typed(p) => match *p.pat { + Pat::Ident(i) => { + let ty = *p.ty; + let ty = quote! { #ty }; + if i.ident == "self" { + Ok(ArgKind::Receiver(ReceiverArg)) + } else { + Ok(ArgKind::Named(NamedArg::new(i.ident, ty))) + } + } + _ => Err(syn::Error::new_spanned(p, "Argument name missing")), + }, + FnArg::Receiver(Receiver { .. }) => Ok(ArgKind::Receiver(ReceiverArg)), + }?; + + Ok(Self { span, kind }) + } +} + +// A unit struct is kind of silly now, but eventually we may want to differentiate between methods +// that input &self vs Arc Self { + Self { + name: ident_to_string(&ident), + ident, + ty, + } + } + + /// Generate the expression for this argument's FfiConverter + pub(crate) fn ffi_converter(&self) -> TokenStream { + let ty = &self.ty; + quote! { <#ty as ::uniffi::FfiConverter> } + } + + /// Generate the expression for this argument's FfiType + pub(crate) fn ffi_type(&self) -> TokenStream { + let ffi_converter = self.ffi_converter(); + quote! { #ffi_converter::FfiType } + } + + /// Generate the parameter for this Arg + pub(crate) fn param(&self) -> TokenStream { + let ident = &self.ident; + let ty = &self.ty; + quote! { #ident: #ty } + } + + /// Generate the scaffolding parameter for this Arg + pub(crate) fn scaffolding_param(&self) -> TokenStream { + let ident = &self.ident; + let ffi_type = self.ffi_type(); + quote! { #ident: #ffi_type } + } + + /// Generate the expression to lift the scaffolding parameter for this arg + pub(crate) fn lift_expr(&self) -> TokenStream { + let ident = &self.ident; + let ffi_converter = self.ffi_converter(); + let panic_fmt = format!("Failed to convert arg '{}': {{}}", self.name); + quote! { + #ffi_converter::try_lift(#ident) + .unwrap_or_else(|err| ::std::panic!(#panic_fmt, err)) + } + } + + /// Generate the expression to write the scaffolding parameter for this arg + pub(crate) fn write_expr(&self, buf_ident: &Ident) -> TokenStream { + let ident = &self.ident; + let ffi_converter = self.ffi_converter(); + quote! { #ffi_converter::write(#ident, &mut #buf_ident) } + } + + pub(crate) fn metadata_calls(&self) -> TokenStream { + let name = &self.name; + let ffi_converter = self.ffi_converter(); + quote! { + .concat_str(#name) + .concat(#ffi_converter::TYPE_ID_META) + } + } +} + +#[derive(Debug)] +pub(crate) enum FnKind { + Function, + Constructor { self_ident: Ident }, + Method { self_ident: Ident }, + TraitMethod { self_ident: Ident, index: u32 }, +} diff --git a/uniffi_macros/src/lib.rs b/uniffi_macros/src/lib.rs index 7cf20185d5..ee4f600939 100644 --- a/uniffi_macros/src/lib.rs +++ b/uniffi_macros/src/lib.rs @@ -17,6 +17,7 @@ use syn::{parse_macro_input, LitStr}; mod enum_; mod error; mod export; +mod fnsig; mod object; mod record; mod test; diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index dc36887e88..b516666dac 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -149,6 +149,12 @@ pub fn parse_comma_separated(input: ParseStream<'_>) -> #[derive(Default)] pub struct ArgumentNotAllowedHere; +impl Parse for ArgumentNotAllowedHere { + fn parse(input: ParseStream<'_>) -> syn::Result { + parse_comma_separated(input) + } +} + impl UniffiAttributeArgs for ArgumentNotAllowedHere { fn parse_one(input: ParseStream<'_>) -> syn::Result { Err(syn::Error::new( diff --git a/uniffi_meta/src/group.rs b/uniffi_meta/src/group.rs index b019c2eda3..3c96c10e80 100644 --- a/uniffi_meta/src/group.rs +++ b/uniffi_meta/src/group.rs @@ -4,7 +4,7 @@ use super::{Metadata, NamespaceMetadata}; use anyhow::{bail, Result}; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; /// Group metadata by namespace pub fn group_metadata(items: Vec) -> Result> { @@ -15,7 +15,7 @@ pub fn group_metadata(items: Vec) -> Result> { Metadata::Namespace(namespace) => { let group = MetadataGroup { namespace: namespace.clone(), - items: vec![], + items: BTreeSet::new(), }; Some((namespace.crate_name.clone(), group)) } @@ -39,6 +39,13 @@ pub fn group_metadata(items: Vec) -> Result> { Metadata::Record(meta) => (format!("record `{}`", meta.name), &meta.module_path), Metadata::Enum(meta) => (format!("enum `{}`", meta.name), &meta.module_path), Metadata::Object(meta) => (format!("object `{}`", meta.name), &meta.module_path), + Metadata::CallbackInterface(meta) => ( + format!("callback interface `{}`", meta.name), + &meta.module_path, + ), + Metadata::TraitMethod(meta) => { + (format!("trait method`{}`", meta.name), &meta.module_path) + } Metadata::Error(meta) => (format!("error `{}`", meta.name()), meta.module_path()), }; @@ -47,7 +54,10 @@ pub fn group_metadata(items: Vec) -> Result> { Some(ns) => ns, None => bail!("Unknown namespace for {item_desc} ({crate_name})"), }; - group.items.push(item); + if group.items.contains(&item) { + bail!("Duplicate metadata item: {item:?}"); + } + group.items.insert(item); } Ok(group_map.into_values().collect()) } @@ -55,5 +65,5 @@ pub fn group_metadata(items: Vec) -> Result> { #[derive(Debug)] pub struct MetadataGroup { pub namespace: NamespaceMetadata, - pub items: Vec, + pub items: BTreeSet, } diff --git a/uniffi_meta/src/lib.rs b/uniffi_meta/src/lib.rs index d64904e9b8..074fdb2098 100644 --- a/uniffi_meta/src/lib.rs +++ b/uniffi_meta/src/lib.rs @@ -107,7 +107,7 @@ impl Checksum for &str { // The namespace of a Component interface. // // This is used to match up the macro metadata with the UDL items. -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct NamespaceMetadata { pub crate_name: String, pub name: String, @@ -116,13 +116,13 @@ pub struct NamespaceMetadata { // UDL file included with `include_scaffolding!()` // // This is to find the UDL files in library mode generation -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct UdlFile { pub module_path: String, pub name: String, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct FnMetadata { pub module_path: String, pub name: String, @@ -143,7 +143,7 @@ impl FnMetadata { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct ConstructorMetadata { pub module_path: String, pub self_name: String, @@ -163,11 +163,10 @@ impl ConstructorMetadata { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct MethodMetadata { pub module_path: String, pub self_name: String, - pub self_is_trait: bool, pub name: String, pub is_async: bool, pub inputs: Vec, @@ -186,7 +185,32 @@ impl MethodMetadata { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] +pub struct TraitMethodMetadata { + pub module_path: String, + pub trait_name: String, + // Note: the position of `index` is important since it causes callback interface methods to be + // ordered correctly in MetadataGroup.items + pub index: u32, + pub name: String, + pub is_async: bool, + pub inputs: Vec, + pub return_type: Option, + pub throws: Option, + pub checksum: u16, +} + +impl TraitMethodMetadata { + pub fn ffi_symbol_name(&self) -> String { + method_symbol_name(&self.module_path, &self.trait_name, &self.name) + } + + pub fn checksum_symbol_name(&self) -> String { + method_checksum_symbol_name(&self.module_path, &self.trait_name, &self.name) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct FnParamMetadata { pub name: String, #[serde(rename = "type")] @@ -239,7 +263,7 @@ pub enum Type { }, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub enum Literal { Str { value: String }, Int { base10_digits: String }, @@ -247,14 +271,14 @@ pub enum Literal { Bool { value: bool }, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct RecordMetadata { pub module_path: String, pub name: String, pub fields: Vec, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct FieldMetadata { pub name: String, #[serde(rename = "type")] @@ -262,26 +286,32 @@ pub struct FieldMetadata { pub default: Option, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct EnumMetadata { pub module_path: String, pub name: String, pub variants: Vec, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct VariantMetadata { pub name: String, pub fields: Vec, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub struct ObjectMetadata { pub module_path: String, pub name: String, pub is_trait: bool, } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] +pub struct CallbackInterfaceMetadata { + pub module_path: String, + pub name: String, +} + impl ObjectMetadata { /// FFI symbol name for the `free` function for this object. /// @@ -291,7 +321,7 @@ impl ObjectMetadata { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub enum ErrorMetadata { Enum { enum_: EnumMetadata, is_flat: bool }, } @@ -321,17 +351,19 @@ pub fn checksum(val: &T) -> u16 { } /// Enum covering all the possible metadata types -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub enum Metadata { Namespace(NamespaceMetadata), UdlFile(UdlFile), Func(FnMetadata), - Constructor(ConstructorMetadata), - Method(MethodMetadata), + Object(ObjectMetadata), + CallbackInterface(CallbackInterfaceMetadata), Record(RecordMetadata), Enum(EnumMetadata), - Object(ObjectMetadata), Error(ErrorMetadata), + Constructor(ConstructorMetadata), + Method(MethodMetadata), + TraitMethod(TraitMethodMetadata), } impl Metadata { @@ -393,3 +425,15 @@ impl From for Metadata { Self::Object(v) } } + +impl From for Metadata { + fn from(v: CallbackInterfaceMetadata) -> Self { + Self::CallbackInterface(v) + } +} + +impl From for Metadata { + fn from(v: TraitMethodMetadata) -> Self { + Self::TraitMethod(v) + } +} diff --git a/uniffi_meta/src/reader.rs b/uniffi_meta/src/reader.rs index 9657e5372a..037d1d010c 100644 --- a/uniffi_meta/src/reader.rs +++ b/uniffi_meta/src/reader.rs @@ -54,6 +54,8 @@ impl<'a> MetadataReader<'a> { codes::ENUM => self.read_enum(false)?.into(), codes::ERROR => self.read_error()?.into(), codes::INTERFACE => self.read_object()?.into(), + codes::CALLBACK_INTERFACE => self.read_callback_interface()?.into(), + codes::TRAIT_METHOD => self.read_trait_method()?.into(), _ => bail!("Unexpected metadata code: {value:?}"), }) } @@ -76,6 +78,20 @@ impl<'a> MetadataReader<'a> { } } + fn read_u32(&mut self) -> Result { + if self.buf.len() >= 4 { + // read the value as little-endian + let value = self.buf[0] as u32 + + ((self.buf[1] as u32) << 8) + + ((self.buf[2] as u32) << 16) + + ((self.buf[3] as u32) << 24); + self.buf = &self.buf[4..]; + Ok(value) + } else { + bail!("Not enough data left in buffer to read a u32 value"); + } + } + fn read_bool(&mut self) -> Result { Ok(self.read_u8()? == 1) } @@ -208,7 +224,6 @@ impl<'a> MetadataReader<'a> { fn read_method(&mut self) -> Result { let module_path = self.read_string()?; let self_name = self.read_string()?; - let self_is_trait = self.read_bool()?; let name = self.read_string()?; let is_async = self.read_bool()?; let inputs = self.read_inputs()?; @@ -216,7 +231,6 @@ impl<'a> MetadataReader<'a> { Ok(MethodMetadata { module_path, self_name, - self_is_trait, name, is_async, inputs, @@ -264,6 +278,34 @@ impl<'a> MetadataReader<'a> { }) } + fn read_callback_interface(&mut self) -> Result { + Ok(CallbackInterfaceMetadata { + module_path: self.read_string()?, + name: self.read_string()?, + }) + } + + fn read_trait_method(&mut self) -> Result { + let module_path = self.read_string()?; + let trait_name = self.read_string()?; + let index = self.read_u32()?; + let name = self.read_string()?; + let is_async = self.read_bool()?; + let inputs = self.read_inputs()?; + let (return_type, throws) = self.read_return_type()?; + Ok(TraitMethodMetadata { + module_path, + trait_name, + index, + name, + is_async, + inputs, + return_type, + throws, + checksum: self.calc_checksum(), + }) + } + fn read_fields(&mut self) -> Result> { let len = self.read_u8()?; (0..len)