From 5367a3de7b83128458f261507d53055bb3faef38 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 2 Feb 2023 11:19:14 -0500 Subject: [PATCH] New system for interface metadata - Added the `uniffi_core::metadata` module, which provides a system for building metadata buffers using const code. - Added the `FfiConverter::TYPE_ID_META` const, which holds metadata to identify the type. - Made the proc-macros generate use the new system to generate metadata consts, then export them using static variables. - Removed the current code to generate/store metadata based on the syn parsing. - Removed the type assertions and the requirement for a `uniffi_types` module. We don't need them now that we`re getting the type ids from the type itself. - Made the `FnMetadata.throws` field a Type rather than a String. - Calculate module paths with the `module_path!()` macro. This means we get accurate module paths without nightly. Changed mod_path to be a String, rather than a Vec since this is what `module_path!()` outputs. - Added code to strip the `r#` part out of raw idents before serializing it into the metadata. - Replaced the `collect_params()` function with the `ScaffoldingBits` struct. There was too much complexity for a single function -- for example unzip() only works with pairs, not 3-tuples. In general, the new metadata system is more reliable doing everything in the proc-macros. Proc-macros can only see the type identifiers, but they don't anything about the underlying type, since users can rename types with type aliases. It's also simpler since you don't have to walk the AST so much. One TODO is getting checksum working again. One limitation of the const code is that we can't use it to generate function identifiers. --- CHANGELOG.md | 2 + Cargo.toml | 1 + docs/manual/src/proc_macro/index.md | 10 - fixtures/futures/src/lib.rs | 6 - fixtures/metadata/Cargo.toml | 15 + fixtures/metadata/src/lib.rs | 11 + fixtures/metadata/src/tests.rs | 534 ++++++++++++++++++ fixtures/proc-macro/src/lib.rs | 6 - fixtures/simple-fns/src/lib.rs | 11 +- fixtures/simple-iface/src/lib.rs | 4 - .../ui/interface_not_sync_and_send.stderr | 2 +- uniffi/tests/ui/proc_macro_arc.rs | 4 - uniffi/tests/ui/proc_macro_arc.stderr | 16 + uniffi_bindgen/Cargo.toml | 1 - .../src/bindings/kotlin/gen_kotlin/mod.rs | 3 - .../src/bindings/python/gen_python/mod.rs | 3 - .../src/bindings/ruby/gen_ruby/mod.rs | 9 - .../src/bindings/swift/gen_swift/mod.rs | 3 - uniffi_bindgen/src/interface/function.rs | 8 +- uniffi_bindgen/src/interface/mod.rs | 27 +- uniffi_bindgen/src/interface/object.rs | 8 +- uniffi_bindgen/src/interface/types/mod.rs | 21 - uniffi_bindgen/src/macro_metadata/ci.rs | 54 +- uniffi_bindgen/src/macro_metadata/extract.rs | 9 +- uniffi_bindgen/src/scaffolding/mod.rs | 5 +- .../templates/CallbackInterfaceTemplate.rs | 3 + .../templates/ExternalTypesTemplate.rs | 4 + .../templates/namespace_metadata.rs | 12 + .../templates/scaffolding_template.rs | 2 + uniffi_core/src/ffi/rustcalls.rs | 7 +- uniffi_core/src/ffi_converter_impls.rs | 96 ++-- uniffi_core/src/lib.rs | 13 +- uniffi_core/src/metadata.rs | 180 ++++++ uniffi_macros/src/enum_.rs | 166 +++--- uniffi_macros/src/error.rs | 95 ++-- uniffi_macros/src/export.rs | 156 +---- uniffi_macros/src/export/metadata.rs | 6 +- uniffi_macros/src/export/metadata/convert.rs | 140 ----- uniffi_macros/src/export/metadata/function.rs | 27 +- uniffi_macros/src/export/metadata/impl_.rs | 49 +- uniffi_macros/src/export/scaffolding.rs | 325 ++++++++--- uniffi_macros/src/lib.rs | 28 +- uniffi_macros/src/object.rs | 28 +- uniffi_macros/src/record.rs | 90 ++- uniffi_macros/src/util.rs | 52 +- uniffi_meta/Cargo.toml | 2 + uniffi_meta/src/lib.rs | 101 +++- uniffi_meta/src/reader.rs | 281 +++++++++ 48 files changed, 1776 insertions(+), 860 deletions(-) create mode 100644 fixtures/metadata/Cargo.toml create mode 100644 fixtures/metadata/src/lib.rs create mode 100644 fixtures/metadata/src/tests.rs create mode 100644 uniffi_bindgen/src/scaffolding/templates/namespace_metadata.rs create mode 100644 uniffi_core/src/metadata.rs create mode 100644 uniffi_meta/src/reader.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 607101d6e1..6d930b2cff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ ### What's changed - 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. ## v0.23.0 (backend crates: v0.23.0) - (_2023-01-27_) diff --git a/Cargo.toml b/Cargo.toml index ac20cc0fa9..4e6cf0a768 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "fixtures/keywords/kotlin", "fixtures/keywords/rust", "fixtures/keywords/swift", + "fixtures/metadata", "fixtures/proc-macro", "fixtures/reexport-scaffolding-macro", "fixtures/regressions/enum-without-i32-helpers", diff --git a/docs/manual/src/proc_macro/index.md b/docs/manual/src/proc_macro/index.md index 2516fa2e8a..ed1bee5979 100644 --- a/docs/manual/src/proc_macro/index.md +++ b/docs/manual/src/proc_macro/index.md @@ -63,16 +63,6 @@ User-defined types are also supported in a limited manner: records (structs with their definition. Opaque objects (`interface` in UDL) can always be used regardless of whether they are defined in UDL and / or via derive macro; they just need to be put inside an `Arc` as always. -User-defined types also have to be (re-)exported from a module called `uniffi_types` at the crate -root. This is required to ensure that a given type name always means the same thing across all uses -of `#[uniffi::export]` across the whole module tree. - -```rust -mod uniffi_types { - pub(crate) use path::to::MyObject; -} -``` - ## The `uniffi::Record` derive The `Record` derive macro exposes a `struct` with named fields over FFI. All types that are diff --git a/fixtures/futures/src/lib.rs b/fixtures/futures/src/lib.rs index 213ec7fb21..b4875381cc 100644 --- a/fixtures/futures/src/lib.rs +++ b/fixtures/futures/src/lib.rs @@ -234,9 +234,3 @@ pub async fn broken_sleep(ms: u16, fail_after: u16) { } include!(concat!(env!("OUT_DIR"), "/uniffi_futures.uniffi.rs")); - -mod uniffi_types { - pub(crate) use super::Megaphone; - pub(crate) use super::MyError; - pub(crate) use super::MyRecord; -} diff --git a/fixtures/metadata/Cargo.toml b/fixtures/metadata/Cargo.toml new file mode 100644 index 0000000000..212f97a279 --- /dev/null +++ b/fixtures/metadata/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "uniffi_fixture_metadata" +version = "0.1.0" +authors = ["Firefox Sync Team "] +edition = "2021" +license = "MPL-2.0" +publish = false + +[lib] +name = "uniffi_fixture_metadata" + +[dependencies] +uniffi = { path = "../../uniffi" } +uniffi_meta = { path = "../../uniffi_meta" } +uniffi_core = { path = "../../uniffi_core" } diff --git a/fixtures/metadata/src/lib.rs b/fixtures/metadata/src/lib.rs new file mode 100644 index 0000000000..517f906304 --- /dev/null +++ b/fixtures/metadata/src/lib.rs @@ -0,0 +1,11 @@ +/* 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/. */ + +/// This entire crate is just a set of tests for metadata handling. We use a separate crate +/// for testing because the metadata handling is split up between several crates, and no crate +/// on all the functionality. +#[cfg(test)] +mod tests; + +pub struct UniFfiTag; diff --git a/fixtures/metadata/src/tests.rs b/fixtures/metadata/src/tests.rs new file mode 100644 index 0000000000..a3620fb30b --- /dev/null +++ b/fixtures/metadata/src/tests.rs @@ -0,0 +1,534 @@ +/* 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/. */ + +/// This entire crate is just a set of tests for metadata handling. We use a separate crate +/// for testing because the metadata handling is split up between several crates, and no crate +/// on all the functionality. +use crate::UniFfiTag; +use uniffi_meta::*; + +mod person { + #[derive(uniffi::Record, Debug)] + pub struct Person { + name: String, + age: u16, + } +} + +mod weapon { + #[derive(uniffi::Enum, Debug)] + pub enum Weapon { + Rock, + Paper, + Scissors, + } +} + +mod state { + use super::Person; + + #[derive(uniffi::Enum, Debug)] + pub enum State { + Uninitialized, + Initialized { data: String }, + Complete { result: Person }, + } +} + +mod error { + use super::Weapon; + use std::fmt; + + #[derive(uniffi::Error)] + #[uniffi(flat_error)] + #[allow(dead_code)] + pub enum FlatError { + Overflow(String), // UniFFI should ignore this field, since `flat_error` was specified + DivideByZero, + } + + #[derive(uniffi::Error)] + pub enum ComplexError { + NotFound, + PermissionDenied { reason: String }, + InvalidWeapon { weapon: Weapon }, + } + + impl fmt::Display for FlatError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Overflow(s) => write!(f, "FlatError::Overflow({s})"), + Self::DivideByZero => write!(f, "FlatError::DivideByZero"), + } + } + } + + impl fmt::Display for ComplexError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::NotFound => write!(f, "ComplexError::NotFound()"), + Self::PermissionDenied { reason } => { + write!(f, "ComplexError::PermissionDenied({reason})") + } + Self::InvalidWeapon { weapon } => { + write!(f, "ComplexError::InvalidWeapon({weapon:?})") + } + } + } + } +} + +mod calc { + #[derive(uniffi::Object)] + pub struct Calculator {} +} + +pub use calc::Calculator; +pub use error::{ComplexError, FlatError}; +pub use person::Person; +pub use state::State; +pub use weapon::Weapon; + +mod test_type_ids { + use super::*; + use std::collections::HashMap; + use std::sync::Arc; + use uniffi_core::FfiConverter; + + fn check_type_id>(correct_type: Type) { + let buf = &mut T::TYPE_ID_META.as_ref(); + assert_eq!( + buf.read_type().unwrap(), + correct_type, + "Expected: {correct_type:?} data: {:?}", + T::TYPE_ID_META.as_ref() + ); + } + + #[test] + fn simple_types() { + check_type_id::(Type::U8); + check_type_id::(Type::U16); + check_type_id::(Type::U32); + check_type_id::(Type::U64); + check_type_id::(Type::I8); + check_type_id::(Type::I16); + check_type_id::(Type::I32); + check_type_id::(Type::I64); + check_type_id::(Type::F32); + check_type_id::(Type::F64); + check_type_id::(Type::Bool); + check_type_id::(Type::String); + } + + #[test] + fn test_user_types() { + check_type_id::(Type::Record { + name: "Person".into(), + }); + check_type_id::(Type::Enum { + name: "Weapon".into(), + }); + check_type_id::>(Type::ArcObject { + object_name: "Calculator".into(), + }); + } + + #[test] + fn test_generics() { + check_type_id::>(Type::Option { + inner_type: Box::new(Type::U8), + }); + check_type_id::>(Type::Vec { + inner_type: Box::new(Type::U8), + }); + check_type_id::>(Type::HashMap { + key_type: Box::new(Type::String), + value_type: Box::new(Type::U8), + }); + } +} + +fn check_metadata(mut encoded: &[u8], correct_metadata: impl Into) { + assert_eq!( + (&mut encoded).read_metadata().unwrap(), + correct_metadata.into() + ) +} + +mod test_metadata { + use super::*; + + #[test] + fn test_record() { + check_metadata( + &person::UNIFFI_META_UNIFFI_FIXTURE_METADATA_RECORD_PERSON, + RecordMetadata { + module_path: "uniffi_fixture_metadata::tests::person".into(), + name: "Person".into(), + fields: vec![ + FieldMetadata { + name: "name".into(), + ty: Type::String, + }, + FieldMetadata { + name: "age".into(), + ty: Type::U16, + }, + ], + }, + ); + } + + #[test] + fn test_simple_enum() { + check_metadata( + &weapon::UNIFFI_META_UNIFFI_FIXTURE_METADATA_ENUM_WEAPON, + EnumMetadata { + module_path: "uniffi_fixture_metadata::tests::weapon".into(), + name: "Weapon".into(), + variants: vec![ + VariantMetadata { + name: "Rock".into(), + fields: vec![], + }, + VariantMetadata { + name: "Paper".into(), + fields: vec![], + }, + VariantMetadata { + name: "Scissors".into(), + fields: vec![], + }, + ], + }, + ); + } + + #[test] + fn test_complex_enum() { + check_metadata( + &state::UNIFFI_META_UNIFFI_FIXTURE_METADATA_ENUM_STATE, + EnumMetadata { + module_path: "uniffi_fixture_metadata::tests::state".into(), + name: "State".into(), + variants: vec![ + VariantMetadata { + name: "Uninitialized".into(), + fields: vec![], + }, + VariantMetadata { + name: "Initialized".into(), + fields: vec![FieldMetadata { + name: "data".into(), + ty: Type::String, + }], + }, + VariantMetadata { + name: "Complete".into(), + fields: vec![FieldMetadata { + name: "result".into(), + ty: Type::Record { + name: "Person".into(), + }, + }], + }, + ], + }, + ); + } + + #[test] + fn test_simple_error() { + check_metadata( + &error::UNIFFI_META_UNIFFI_FIXTURE_METADATA_ERROR_FLATERROR, + ErrorMetadata { + module_path: "uniffi_fixture_metadata::tests::error".into(), + name: "FlatError".into(), + flat: true, + variants: vec![ + VariantMetadata { + name: "Overflow".into(), + fields: vec![], + }, + VariantMetadata { + name: "DivideByZero".into(), + fields: vec![], + }, + ], + }, + ); + } + + #[test] + fn test_complex_error() { + check_metadata( + &error::UNIFFI_META_UNIFFI_FIXTURE_METADATA_ERROR_COMPLEXERROR, + ErrorMetadata { + module_path: "uniffi_fixture_metadata::tests::error".into(), + name: "ComplexError".into(), + flat: false, + variants: vec![ + VariantMetadata { + name: "NotFound".into(), + fields: vec![], + }, + VariantMetadata { + name: "PermissionDenied".into(), + fields: vec![FieldMetadata { + name: "reason".into(), + ty: Type::String, + }], + }, + VariantMetadata { + name: "InvalidWeapon".into(), + fields: vec![FieldMetadata { + name: "weapon".into(), + ty: Type::Enum { + name: "Weapon".into(), + }, + }], + }, + ], + }, + ); + } + + #[test] + fn test_interface() { + check_metadata( + &calc::UNIFFI_META_UNIFFI_FIXTURE_METADATA_INTERFACE_CALCULATOR, + ObjectMetadata { + module_path: "uniffi_fixture_metadata::tests::calc".into(), + name: "Calculator".into(), + }, + ); + } +} + +mod test_function_metadata { + use super::*; + + #[uniffi::export] + #[allow(unused)] + pub fn test_func(person: Person, weapon: Weapon) -> String { + unimplemented!() + } + + #[uniffi::export] + pub fn test_func_no_return() { + unimplemented!() + } + + #[uniffi::export] + pub fn test_func_that_throws() -> Result { + unimplemented!() + } + + #[uniffi::export] + pub fn test_func_no_return_that_throws() -> Result<(), FlatError> { + unimplemented!() + } + + #[uniffi::export] + #[allow(unused)] + pub async fn test_async_func(person: Person, weapon: Weapon) -> String { + unimplemented!() + } + + #[uniffi::export] + pub async fn test_async_func_that_throws() -> Result { + unimplemented!() + } + + + #[uniffi::export] + impl Calculator { + #[allow(unused)] + pub fn add(&self, a: u8, b: u8) -> u8 { + unimplemented!() + } + + #[allow(unused)] + pub async fn async_sub(&self, a: u8, b: u8) -> u8 { + unimplemented!() + } + } + + #[test] + fn test_function() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_FUNC_TEST_FUNC, + FnMetadata { + module_path: "uniffi_fixture_metadata::tests::test_function_metadata".into(), + name: "test_func".into(), + is_async: false, + inputs: vec![ + FnParamMetadata { + name: "person".into(), + ty: Type::Record { + name: "Person".into(), + }, + }, + FnParamMetadata { + name: "weapon".into(), + ty: Type::Enum { + name: "Weapon".into(), + }, + }, + ], + return_type: Some(Type::String), + throws: None, + }, + ); + } + + #[test] + fn test_function_no_return() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_FUNC_TEST_FUNC_NO_RETURN, + FnMetadata { + module_path: "uniffi_fixture_metadata::tests::test_function_metadata".into(), + name: "test_func_no_return".into(), + is_async: false, + inputs: vec![], + return_type: None, + throws: None, + }, + ); + } + + #[test] + fn test_function_that_throws() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_FUNC_TEST_FUNC_THAT_THROWS, + FnMetadata { + module_path: "uniffi_fixture_metadata::tests::test_function_metadata".into(), + name: "test_func_that_throws".into(), + is_async: false, + inputs: vec![], + return_type: Some(Type::Enum { + name: "State".into(), + }), + throws: Some(Type::Error { + name: "FlatError".into(), + }), + }, + ); + } + + #[test] + fn test_function_that_throws_no_return() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_FUNC_TEST_FUNC_NO_RETURN_THAT_THROWS, + FnMetadata { + module_path: "uniffi_fixture_metadata::tests::test_function_metadata".into(), + name: "test_func_no_return_that_throws".into(), + is_async: false, + inputs: vec![], + return_type: None, + throws: Some(Type::Error { + name: "FlatError".into(), + }), + }, + ); + } + + #[test] + fn test_method() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_METHOD_CALCULATOR_ADD, + MethodMetadata { + module_path: "uniffi_fixture_metadata::tests::test_function_metadata".into(), + self_name: "Calculator".into(), + name: "add".into(), + is_async: false, + inputs: vec![ + FnParamMetadata { + name: "a".into(), + ty: Type::U8, + }, + FnParamMetadata { + name: "b".into(), + ty: Type::U8, + }, + ], + return_type: Some(Type::U8), + throws: None, + }, + ); + } + + #[test] + fn test_async_function() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_FUNC_TEST_ASYNC_FUNC, + FnMetadata { + module_path: "uniffi_fixture_metadata::tests::test_function_metadata".into(), + name: "test_async_func".into(), + is_async: true, + inputs: vec![ + FnParamMetadata { + name: "person".into(), + ty: Type::Record { + name: "Person".into(), + }, + }, + FnParamMetadata { + name: "weapon".into(), + ty: Type::Enum { + name: "Weapon".into(), + }, + }, + ], + return_type: Some(Type::String), + throws: None, + }, + ); + } + + #[test] + fn test_async_function_that_throws() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_FUNC_TEST_ASYNC_FUNC_THAT_THROWS, + FnMetadata { + module_path: "uniffi_fixture_metadata::tests::test_function_metadata".into(), + name: "test_async_func_that_throws".into(), + is_async: true, + inputs: vec![], + return_type: Some(Type::Enum { + name: "State".into(), + }), + throws: Some(Type::Error { + name: "FlatError".into(), + }), + }, + ); + } + + #[test] + fn test_async_method() { + check_metadata( + &UNIFFI_META_UNIFFI_FIXTURE_METADATA_METHOD_CALCULATOR_ASYNC_SUB, + MethodMetadata { + module_path: "uniffi_fixture_metadata::tests::test_function_metadata".into(), + self_name: "Calculator".into(), + name: "async_sub".into(), + is_async: true, + inputs: vec![ + FnParamMetadata { + name: "a".into(), + ty: Type::U8, + }, + FnParamMetadata { + name: "b".into(), + ty: Type::U8, + }, + ], + return_type: Some(Type::U8), + throws: None, + }, + ); + } +} diff --git a/fixtures/proc-macro/src/lib.rs b/fixtures/proc-macro/src/lib.rs index 2a6226661c..1f408f0da8 100644 --- a/fixtures/proc-macro/src/lib.rs +++ b/fixtures/proc-macro/src/lib.rs @@ -113,9 +113,3 @@ impl Object { } include!(concat!(env!("OUT_DIR"), "/proc-macro.uniffi.rs")); - -mod uniffi_types { - pub use crate::{ - BasicError, FlatError, MaybeBool, NestedRecord, Object, One, Three, Two, Zero, - }; -} diff --git a/fixtures/simple-fns/src/lib.rs b/fixtures/simple-fns/src/lib.rs index 6abcea6be9..302d622a7d 100644 --- a/fixtures/simple-fns/src/lib.rs +++ b/fixtures/simple-fns/src/lib.rs @@ -2,9 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use std::sync::Arc; +use std::collections::HashSet; +use std::sync::{Arc, Mutex}; -use uniffi_types::MyHashSet; +pub type MyHashSet = Mutex>; #[uniffi::export] fn get_string() -> String { @@ -46,10 +47,4 @@ fn set_contains(set: Arc, value: String) -> bool { #[uniffi::export] fn dummy(_arg: Option) {} -mod uniffi_types { - use std::{collections::HashSet, sync::Mutex}; - - pub type MyHashSet = Mutex>; -} - include!(concat!(env!("OUT_DIR"), "/simple-fns.uniffi.rs")); diff --git a/fixtures/simple-iface/src/lib.rs b/fixtures/simple-iface/src/lib.rs index d1f7c4292a..66ce085adc 100644 --- a/fixtures/simple-iface/src/lib.rs +++ b/fixtures/simple-iface/src/lib.rs @@ -27,7 +27,3 @@ impl Object { } include!(concat!(env!("OUT_DIR"), "/simple-iface.uniffi.rs")); - -mod uniffi_types { - pub use crate::Object; -} diff --git a/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr b/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr index 5154cf854a..11492553a7 100644 --- a/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr +++ b/fixtures/uitests/tests/ui/interface_not_sync_and_send.stderr @@ -13,6 +13,6 @@ note: required because it appears within the type `Counter` note: required by a bound in `Interface` --> $WORKSPACE/uniffi_core/src/lib.rs | - | pub trait Interface: Send + Sync + Sized {} + | pub trait Interface: Send + Sync + Sized { | ^^^^ required by this bound in `Interface` = note: this error originates in the attribute macro `::uniffi::ffi_converter_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/uniffi/tests/ui/proc_macro_arc.rs b/uniffi/tests/ui/proc_macro_arc.rs index 8841598acd..9db3376594 100644 --- a/uniffi/tests/ui/proc_macro_arc.rs +++ b/uniffi/tests/ui/proc_macro_arc.rs @@ -22,7 +22,3 @@ mod child { match &*foo {} } } - -mod uniffi_types { - pub use super::Foo; -} diff --git a/uniffi/tests/ui/proc_macro_arc.stderr b/uniffi/tests/ui/proc_macro_arc.stderr index 93ab376f79..9a5a6417be 100644 --- a/uniffi/tests/ui/proc_macro_arc.stderr +++ b/uniffi/tests/ui/proc_macro_arc.stderr @@ -8,6 +8,14 @@ error[E0277]: the trait bound `Foo: Interface` is not satisfied = note: required for `Arc` to implement `FfiConverter` = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0080]: evaluation of constant value failed + --> tests/ui/proc_macro_arc.rs:10:1 + | +10 | #[uniffi::export] + | ^^^^^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `child::Foo: Interface` is not satisfied --> tests/ui/proc_macro_arc.rs:20:5 | @@ -17,3 +25,11 @@ error[E0277]: the trait bound `child::Foo: Interface` is not satisfie = help: the trait `FfiConverter` is implemented for `Arc` = note: required for `Arc` to implement `FfiConverter` = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> tests/ui/proc_macro_arc.rs:20:5 + | +20 | #[uniffi::export] + | ^^^^^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/uniffi_bindgen/Cargo.toml b/uniffi_bindgen/Cargo.toml index 6088fe6668..56de42ccbb 100644 --- a/uniffi_bindgen/Cargo.toml +++ b/uniffi_bindgen/Cargo.toml @@ -13,7 +13,6 @@ keywords = ["ffi", "bindgen"] [dependencies] anyhow = "1" askama = { version = "0.11", default-features = false, features = ["config"] } -bincode = "1.3" camino = "1.0.8" fs-err = "2.7.0" glob = "0.3" diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index 4c6d10e09f..67edf00ac1 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -258,9 +258,6 @@ impl KotlinCodeOracle { Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)), Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), - Type::Unresolved { name } => { - unreachable!("Type `{name}` must be resolved before calling create_code_type") - } } } } diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index e821e0dc9d..c9cf716edf 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -305,9 +305,6 @@ impl PythonCodeOracle { Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)), Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), - Type::Unresolved { name } => { - unreachable!("Type `{name}` must be resolved before calling create_code_type") - } } } } diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index e8ff25027c..e61405064e 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -190,9 +190,6 @@ mod filters { } Type::External { .. } => panic!("No support for external types, yet"), Type::Custom { .. } => panic!("No support for custom types, yet"), - Type::Unresolved { name } => { - unreachable!("Type `{name}` must be resolved before calling coerce_rb") - } }) } @@ -226,9 +223,6 @@ mod filters { ), Type::External { .. } => panic!("No support for lowering external types, yet"), Type::Custom { .. } => panic!("No support for lowering custom types, yet"), - Type::Unresolved { name } => { - unreachable!("Type `{name}` must be resolved before calling lower_rb") - } }) } @@ -261,9 +255,6 @@ mod filters { ), Type::External { .. } => panic!("No support for lifting external types, yet"), Type::Custom { .. } => panic!("No support for lifting custom types, yet"), - Type::Unresolved { name } => { - unreachable!("Type `{name}` must be resolved before calling lift_rb") - } }) } } diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index b86508a329..b05bb40c4b 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -326,9 +326,6 @@ impl SwiftCodeOracle { Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)), Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), - Type::Unresolved { name } => { - unreachable!("Type `{name}` must be resolved before calling create_code_type") - } } } } diff --git a/uniffi_bindgen/src/interface/function.rs b/uniffi_bindgen/src/interface/function.rs index 142d1fad42..2fc6e3807f 100644 --- a/uniffi_bindgen/src/interface/function.rs +++ b/uniffi_bindgen/src/interface/function.rs @@ -148,7 +148,13 @@ impl From for Function { arguments, return_type, ffi_func, - attributes: meta.throws.map(Attribute::Throws).into_iter().collect(), + attributes: match meta.throws { + None => FunctionAttributes::from_iter(vec![]), + Some(uniffi_meta::Type::Error { name }) => { + FunctionAttributes::from_iter(vec![Attribute::Throws(name)]) + } + Some(ty) => panic!("Invalid throws type: {ty:?}"), + }, } } } diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index e3f7e03dc0..6fe31c8411 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -637,9 +637,6 @@ impl ComponentInterface { f: impl Fn(&str) -> Result + Clone, ) -> Result<()> { match ty { - Type::Unresolved { name } => { - *ty = f(name)?; - } Type::Optional(inner) => { handle_unresolved_in(inner, f)?; } @@ -689,13 +686,7 @@ impl ComponentInterface { for ty in possibly_unresolved_types { handle_unresolved_in(ty, |unresolved_ty_name| { match self.types.get_type_definition(unresolved_ty_name) { - Some(def) => { - assert!( - !matches!(&def, Type::Unresolved { .. }), - "unresolved types must not be part of TypeUniverse" - ); - Ok(def) - } + Some(def) => Ok(def), None => bail!("Failed to resolve type `{unresolved_ty_name}`"), } })?; @@ -751,9 +742,6 @@ impl ComponentInterface { "Enum `{name}` has no definition", ); } - Type::Unresolved { name } => { - bail!("Type `{name}` should be resolved at this point"); - } _ => {} } } @@ -1001,6 +989,17 @@ fn convert_type(s: &uniffi_meta::Type) -> Type { Ty::F64 => Type::Float64, Ty::Bool => Type::Boolean, Ty::String => Type::String, + Ty::SystemTime => Type::Timestamp, + Ty::Duration => Type::Duration, + Ty::Record { name } => Type::Record(name.clone()), + Ty::Enum { name } => Type::Enum(name.clone()), + Ty::ArcObject { object_name } => Type::Object(object_name.clone()), + Ty::Error { name } => Type::Error(name.clone()), + Ty::CallbackInterface { name } => Type::CallbackInterface(name.clone()), + Ty::Custom { name, builtin } => Type::Custom { + name: name.clone(), + builtin: convert_type(builtin).into(), + }, Ty::Option { inner_type } => Type::Optional(convert_type(inner_type).into()), Ty::Vec { inner_type } => Type::Sequence(convert_type(inner_type).into()), Ty::HashMap { @@ -1010,8 +1009,6 @@ fn convert_type(s: &uniffi_meta::Type) -> Type { convert_type(key_type).into(), convert_type(value_type).into(), ), - Ty::ArcObject { object_name } => Type::Object(object_name.clone()), - Ty::Unresolved { name } => Type::Unresolved { name: name.clone() }, } } diff --git a/uniffi_bindgen/src/interface/object.rs b/uniffi_bindgen/src/interface/object.rs index 8e62bd63b3..48c14fd725 100644 --- a/uniffi_bindgen/src/interface/object.rs +++ b/uniffi_bindgen/src/interface/object.rs @@ -443,7 +443,13 @@ impl From for Method { arguments, return_type, ffi_func, - attributes: meta.throws.map(Attribute::Throws).into_iter().collect(), + attributes: match meta.throws { + None => MethodAttributes::from_iter(vec![]), + Some(uniffi_meta::Type::Error { name }) => { + MethodAttributes::from_iter(vec![Attribute::Throws(name)]) + } + Some(ty) => panic!("Invalid throws type: {ty:?}"), + }, } } } diff --git a/uniffi_bindgen/src/interface/types/mod.rs b/uniffi_bindgen/src/interface/types/mod.rs index 0a6910f901..ba8df284ad 100644 --- a/uniffi_bindgen/src/interface/types/mod.rs +++ b/uniffi_bindgen/src/interface/types/mod.rs @@ -75,11 +75,6 @@ pub enum Type { name: String, builtin: Box, }, - // An unresolved user-defined type inside a proc-macro exported function - // signature. Must be replaced by another type before bindings generation. - Unresolved { - name: String, - }, } #[derive(Debug, Clone, Copy, Eq, PartialEq, Checksum, Ord, PartialOrd)] @@ -138,9 +133,6 @@ impl Type { ), // A type that exists externally. Type::External { name, .. } | Type::Custom { name, .. } => format!("Type{name}"), - Type::Unresolved { name } => { - unreachable!("Type `{name}` must be resolved before calling canonical_name") - } } } @@ -206,9 +198,6 @@ impl From<&Type> for FfiType { .. } => FfiType::RustBuffer(Some(name.clone())), Type::Custom { builtin, .. } => FfiType::from(builtin.as_ref()), - Type::Unresolved { name } => { - unreachable!("Type `{name}` must be resolved before lowering to FfiType") - } } } } @@ -250,11 +239,6 @@ impl TypeUniverse { /// /// This will fail if you try to add a name for which an existing type definition exists. pub fn add_type_definition(&mut self, name: &str, type_: Type) -> Result<()> { - if let Type::Unresolved { name: name_ } = &type_ { - assert_eq!(name, name_); - bail!("attempted to add type definition of Unresolved for `{name}`"); - } - if resolve_builtin_type(name).is_some() { bail!( "please don't shadow builtin types ({name}, {})", @@ -302,11 +286,6 @@ 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<()> { - // Adding potentially-unresolved types is a footgun, make sure we don't do that. - if matches!(type_, Type::Unresolved { .. }) { - bail!("Unresolved types must be resolved before being added to known types"); - } - // 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()); diff --git a/uniffi_bindgen/src/macro_metadata/ci.rs b/uniffi_bindgen/src/macro_metadata/ci.rs index 50847e9651..e72f08716d 100644 --- a/uniffi_bindgen/src/macro_metadata/ci.rs +++ b/uniffi_bindgen/src/macro_metadata/ci.rs @@ -3,7 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::interface::{ComponentInterface, Enum, Error, Record, Type}; -use anyhow::{anyhow, Context}; +use anyhow::{anyhow, bail, Context}; +use std::collections::HashMap; use uniffi_meta::Metadata; /// Add Metadata items to the ComponentInterface @@ -18,44 +19,45 @@ pub fn add_to_ci( iface: &mut ComponentInterface, metadata_items: Vec, ) -> anyhow::Result<()> { + // Map crate names to namespaces + let namespace_map = metadata_items + .iter() + .filter_map(|i| match i { + Metadata::Namespace(meta) => Some((meta.crate_name.clone(), meta.name.clone())), + _ => None, + }) + .collect::>(); + for item in metadata_items { - let (item_desc, crate_name) = match &item { - Metadata::Func(meta) => ( - format!("function `{}`", meta.name), - meta.module_path.first().unwrap(), - ), + let (item_desc, module_path) = match &item { + Metadata::Namespace(_) => continue, + Metadata::Func(meta) => (format!("function `{}`", meta.name), &meta.module_path), Metadata::Method(meta) => ( format!("method `{}.{}`", meta.self_name, meta.name), - meta.module_path.first().unwrap(), - ), - Metadata::Record(meta) => ( - format!("record `{}`", meta.name), - meta.module_path.first().unwrap(), - ), - Metadata::Enum(meta) => ( - format!("enum `{}`", meta.name), - meta.module_path.first().unwrap(), - ), - Metadata::Object(meta) => ( - format!("object `{}`", meta.name), - meta.module_path.first().unwrap(), - ), - Metadata::Error(meta) => ( - format!("error `{}`", meta.name), - meta.module_path.first().unwrap(), + &meta.module_path, ), + 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::Error(meta) => (format!("error `{}`", meta.name), &meta.module_path), }; - let ns = iface.namespace(); - if crate_name != ns { + let iface_ns = iface.namespace(); + let crate_name = module_path.split("::").next().unwrap(); + let item_ns = match namespace_map.get(crate_name) { + Some(ns) => ns, + None => bail!("Unknown namespace for {item_desc} ({crate_name})"), + }; + if item_ns != &iface_ns { return Err(anyhow!("Found {item_desc} from crate `{crate_name}`.") .context(format!( - "Main crate is expected to be named `{ns}` based on the UDL namespace." + "Main crate is expected to be named `{iface_ns}` based on the UDL namespace." )) .context("Mixing symbols from multiple crates is not supported yet.")); } match item { + Metadata::Namespace(_) => unreachable!(), Metadata::Func(meta) => { iface.add_fn_meta(meta)?; } diff --git a/uniffi_bindgen/src/macro_metadata/extract.rs b/uniffi_bindgen/src/macro_metadata/extract.rs index af2d7defef..25b5ef17ba 100644 --- a/uniffi_bindgen/src/macro_metadata/extract.rs +++ b/uniffi_bindgen/src/macro_metadata/extract.rs @@ -172,13 +172,10 @@ impl ExtractedItems { // always know the end position, because goblin reports the symbol size as 0 for PE and // MachO files. // - // This works fine, because bincode knows when the serialized data is terminated and will - // just ignore the trailing data. + // This works fine, because `MetadataReader` knows when the serialized data is terminated + // and will just ignore the trailing data. let data = &file_data[offset..]; - self.items - .push(bincode::deserialize::(data).with_context(|| { - format!("Failed to deserialize bincode data at offset {offset:#x}") - })?); + self.items.push(Metadata::read(data)?); self.names.insert(name.to_string()); Ok(()) } diff --git a/uniffi_bindgen/src/scaffolding/mod.rs b/uniffi_bindgen/src/scaffolding/mod.rs index ec69b802d0..d56c2ad4f1 100644 --- a/uniffi_bindgen/src/scaffolding/mod.rs +++ b/uniffi_bindgen/src/scaffolding/mod.rs @@ -7,7 +7,7 @@ use askama::Template; use std::borrow::Borrow; use super::interface::*; -use heck::ToSnakeCase; +use heck::{ToShoutySnakeCase, ToSnakeCase}; #[derive(Template)] #[template(syntax = "rs", escape = "none", path = "scaffolding_template.rs")] @@ -54,9 +54,6 @@ mod filters { ), Type::Custom { name, .. } => format!("r#{name}"), Type::External { name, .. } => format!("r#{name}"), - Type::Unresolved { .. } => { - unreachable!("UDL scaffolding code never contains unresolved types") - } }) } diff --git a/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs index 3429222ebc..fd11872f6b 100644 --- a/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs +++ b/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs @@ -115,4 +115,7 @@ unsafe impl ::uniffi::FfiConverter for Box for r#{{ name }} { fn try_read(buf: &mut &[u8]) -> uniffi::Result { <{{ name }} as UniffiCustomTypeConverter>::into_custom({{ builtin|ffi_converter }}::try_read(buf)?) } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_CUSTOM) + .concat_str("{{ name }}") + .concat({{ builtin|ffi_converter }}::TYPE_ID_META); } {%- endfor -%} diff --git a/uniffi_bindgen/src/scaffolding/templates/namespace_metadata.rs b/uniffi_bindgen/src/scaffolding/templates/namespace_metadata.rs new file mode 100644 index 0000000000..1fd24e2a17 --- /dev/null +++ b/uniffi_bindgen/src/scaffolding/templates/namespace_metadata.rs @@ -0,0 +1,12 @@ + +/// Export namespace metadata. +/// +/// See `uniffi_bidgen::macro_metadata` for how this is used. +{%- let const_var = "UNIFFI_META_CONST_NAMESPACE_{}"|format(ci.namespace().to_shouty_snake_case()) %} +{%- let static_var = "UNIFFI_META_NAMESPACE_{}"|format(ci.namespace().to_shouty_snake_case()) %} + +const {{ const_var }}: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::NAMESPACE) + .concat_str(module_path!()) // This is the crate name, since we're in the crate root + .concat_str("{{ ci.namespace() }}"); +#[no_mangle] +pub static {{ static_var }}: [u8; {{ const_var }}.size] = {{ const_var }}.into_array(); diff --git a/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs b/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs index e0ae500a55..79133dda71 100644 --- a/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs +++ b/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs @@ -12,6 +12,8 @@ #[doc(hidden)] pub struct UniFfiTag; +{%- include "namespace_metadata.rs" %} + // Check for compatibility between `uniffi` and `uniffi_bindgen` versions. // Note that we have an error message on the same line as the assertion. // This is important, because if the assertion fails, the compiler only diff --git a/uniffi_core/src/ffi/rustcalls.rs b/uniffi_core/src/ffi/rustcalls.rs index 9a4d989cf7..908caa22b9 100644 --- a/uniffi_core/src/ffi/rustcalls.rs +++ b/uniffi_core/src/ffi/rustcalls.rs @@ -180,7 +180,9 @@ where #[cfg(test)] mod test { use super::*; - use crate::{ffi_converter_rust_buffer_lift_and_lower, FfiConverter, UniFfiTag}; + use crate::{ + ffi_converter_rust_buffer_lift_and_lower, FfiConverter, MetadataBuffer, UniFfiTag, + }; fn function(a: u8) -> i8 { match a { @@ -228,6 +230,9 @@ mod test { fn try_read(buf: &mut &[u8]) -> Result { >::try_read(buf).map(TestError) } + + // Use a dummy value here since we don't actually need TYPE_ID_META + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::new(); } fn function_with_result(a: u8) -> Result { diff --git a/uniffi_core/src/ffi_converter_impls.rs b/uniffi_core/src/ffi_converter_impls.rs index a514a73efe..748ce0d1e9 100644 --- a/uniffi_core/src/ffi_converter_impls.rs +++ b/uniffi_core/src/ffi_converter_impls.rs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::{ - check_remaining, ffi_converter_rust_buffer_lift_and_lower, FfiConverter, Interface, Result, - RustBuffer, + check_remaining, ffi_converter_rust_buffer_lift_and_lower, metadata, FfiConverter, Interface, + MetadataBuffer, Result, RustBuffer, }; /// This module contains builtin `FFIConverter` implementations. These cover: /// - Simple privitive types: u8, i32, String, Arc, etc @@ -38,39 +38,45 @@ use std::{ /// /// Numeric primitives have a straightforward mapping into C-compatible numeric types, /// sice they are themselves a C-compatible numeric type! -macro_rules! impl_via_ffi_for_num_primitive { - ($($T:ty,)+) => { impl_via_ffi_for_num_primitive!($($T),+); }; - ($($T:ty),*) => { - $( - paste! { - unsafe impl FfiConverter for $T { - type FfiType = $T; - - fn lower(obj: $T) -> Self::FfiType { - obj - } - - fn try_lift(v: Self::FfiType) -> Result<$T> { - Ok(v) - } - - fn write(obj: $T, buf: &mut Vec) { - buf.[](obj); - } - - fn try_read(buf: &mut &[u8]) -> Result<$T> { - check_remaining(buf, std::mem::size_of::<$T>())?; - Ok(buf.[]()) - } - } +macro_rules! impl_ffi_converter_for_num_primitive { + ($T:ty, $type_code:expr) => { + paste! { + unsafe impl FfiConverter for $T { + type FfiType = $T; + + fn lower(obj: $T) -> Self::FfiType { + obj } - )* + + fn try_lift(v: Self::FfiType) -> Result<$T> { + Ok(v) + } + + fn write(obj: $T, buf: &mut Vec) { + buf.[](obj); + } + + fn try_read(buf: &mut &[u8]) -> Result<$T> { + check_remaining(buf, std::mem::size_of::<$T>())?; + Ok(buf.[]()) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code($type_code); + } + } }; } -impl_via_ffi_for_num_primitive! { - i8, u8, i16, u16, i32, u32, i64, u64, f32, f64 -} +impl_ffi_converter_for_num_primitive!(u8, metadata::codes::TYPE_U8); +impl_ffi_converter_for_num_primitive!(i8, metadata::codes::TYPE_I8); +impl_ffi_converter_for_num_primitive!(u16, metadata::codes::TYPE_U16); +impl_ffi_converter_for_num_primitive!(i16, metadata::codes::TYPE_I16); +impl_ffi_converter_for_num_primitive!(u32, metadata::codes::TYPE_U32); +impl_ffi_converter_for_num_primitive!(i32, metadata::codes::TYPE_I32); +impl_ffi_converter_for_num_primitive!(u64, metadata::codes::TYPE_U64); +impl_ffi_converter_for_num_primitive!(i64, metadata::codes::TYPE_I64); +impl_ffi_converter_for_num_primitive!(f32, metadata::codes::TYPE_F32); +impl_ffi_converter_for_num_primitive!(f64, metadata::codes::TYPE_F64); /// Support for passing boolean values via the FFI. /// @@ -99,9 +105,11 @@ unsafe impl FfiConverter for bool { check_remaining(buf, 1)?; >::try_lift(buf.get_i8()) } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_BOOL); } -/// Support for passing the unit struct via the FFI. This is currently only used for void returns +/// Support for passing the unit type via the FFI. This is currently only used for void returns unsafe impl FfiConverter for () { type FfiType = (); @@ -116,6 +124,8 @@ unsafe impl FfiConverter for () { fn try_read(_: &mut &[u8]) -> Result<()> { Ok(()) } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); } unsafe impl FfiConverter for Infallible { @@ -136,6 +146,8 @@ unsafe impl FfiConverter for Infallible { fn try_read(_: &mut &[u8]) -> Result { unreachable!() } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::new(); } /// Support for passing Strings via the FFI. @@ -191,6 +203,8 @@ unsafe impl FfiConverter for String { buf.advance(len); Ok(res) } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_STRING); } /// Support for passing timestamp values via the FFI. @@ -239,6 +253,9 @@ unsafe impl FfiConverter for SystemTime { Ok(SystemTime::UNIX_EPOCH - epoch_offset) } } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_SYSTEM_TIME); } /// Support for passing duration values via the FFI. @@ -261,6 +278,8 @@ unsafe impl FfiConverter for Duration { check_remaining(buf, 12)?; Ok(Duration::new(buf.get_u64(), buf.get_u32())) } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_DURATION); } /// Support for passing optional values via the FFI. @@ -293,6 +312,9 @@ unsafe impl> FfiConverter for Option { _ => bail!("unexpected tag byte for Option"), }) } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_OPTION).concat(T::TYPE_ID_META); } /// Support for passing vectors of values via the FFI. @@ -325,6 +347,9 @@ unsafe impl> FfiConverter for Vec { } Ok(vec) } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_VEC).concat(T::TYPE_ID_META); } /// Support for associative arrays via the FFI. @@ -363,6 +388,10 @@ where } Ok(map) } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_HASH_MAP) + .concat(K::TYPE_ID_META) + .concat(V::TYPE_ID_META); } /// Support for passing reference-counted shared objects via the FFI. @@ -423,4 +452,7 @@ unsafe impl> FfiConverter for std::sync::Arc { check_remaining(buf, 8)?; >::try_lift(buf.get_u64() as Self::FfiType) } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_INTERFACE).concat_str(T::NAME); } diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index b08440586c..0bac06b368 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -38,7 +38,10 @@ pub use anyhow::Result; pub mod ffi; mod ffi_converter_impls; +pub mod metadata; + pub use ffi::*; +pub use metadata::*; // Re-export the libs that we use in the generated code, // so the consumer doesn't have to depend on them directly. @@ -195,13 +198,18 @@ pub unsafe trait FfiConverter: Sized { /// because we want to be able to advance the start of the slice after reading an item /// from it (but will not mutate the actual contents of the slice). fn try_read(buf: &mut &[u8]) -> Result; + + /// Type ID metadata, serialized into a [MetadataBuffer] + const TYPE_ID_META: MetadataBuffer; } /// Implemented for exported interface types /// /// Like, FfiConverter this has a generic parameter, that's filled in with a type local to the /// UniFFI consumer crate. -pub trait Interface: Send + Sync + Sized {} +pub trait Interface: Send + Sync + Sized { + const NAME: &'static str; +} /// Struct to use when we want to lift/lower/serialize types inside the `uniffi` crate. struct UniFfiTag; @@ -289,6 +297,9 @@ macro_rules! ffi_converter_forward { fn try_read(buf: &mut &[u8]) -> $crate::Result<$T> { <$T as $crate::FfiConverter<$existing_impl_tag>>::try_read(buf) } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = + <$T as $crate::FfiConverter<$existing_impl_tag>>::TYPE_ID_META; } }; } diff --git a/uniffi_core/src/metadata.rs b/uniffi_core/src/metadata.rs new file mode 100644 index 0000000000..1942368338 --- /dev/null +++ b/uniffi_core/src/metadata.rs @@ -0,0 +1,180 @@ +/* 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/. */ + +//! Pack UniFFI interface metadata into byte arrays +//! +//! In order to generate foreign bindings, we store interface metadata inside the library file +//! using exported static byte arrays. The foreign bindings code reads that metadata from the +//! library files and generates bindings based on that. +//! +//! The metadata static variables are generated by the proc-macros, which is an issue because the +//! proc-macros don't have knowledge of the entire interface -- they can only see the item they're +//! wrapping. For example, when a proc-macro sees a type name, it doesn't know anything about the +//! actual type: it could be a Record, an Enum, or even a type alias for a `Vec<>`/`Result<>`. +//! +//! This module helps bridge the gap by providing tools that allow the proc-macros to generate code +//! to encode the interface metadata: +//! - A set of const functions to build up metadata buffers with const expressions +//! - The `export_static_metadata_var!` macro, which creates the static variable from a const metadata +//! buffer. +//! - The `FfiConverter::TYPE_ID_META` const which encodes an identifier for that type in a +//! metadata buffer. +//! +//! `uniffi_bindgen::macro_metadata` contains the code to read the metadata from a library file. +//! `fixtures/metadata` has the tests. + +/// Metadata constants, make sure to keep this in sync with copy in `uniffi_meta::reader` +pub mod codes { + // Top-level metadata item codes + pub const FUNC: u8 = 0; + pub const METHOD: u8 = 1; + pub const RECORD: u8 = 2; + pub const ENUM: u8 = 3; + pub const INTERFACE: u8 = 4; + pub const ERROR: u8 = 5; + pub const NAMESPACE: u8 = 6; + pub const UNKNOWN: u8 = 255; + + // Type codes + pub const TYPE_U8: u8 = 0; + pub const TYPE_U16: u8 = 1; + pub const TYPE_U32: u8 = 2; + pub const TYPE_U64: u8 = 3; + pub const TYPE_I8: u8 = 4; + pub const TYPE_I16: u8 = 5; + pub const TYPE_I32: u8 = 6; + pub const TYPE_I64: u8 = 7; + pub const TYPE_F32: u8 = 8; + pub const TYPE_F64: u8 = 9; + pub const TYPE_BOOL: u8 = 10; + pub const TYPE_STRING: u8 = 11; + pub const TYPE_OPTION: u8 = 12; + pub const TYPE_RECORD: u8 = 13; + pub const TYPE_ENUM: u8 = 14; + pub const TYPE_ERROR: u8 = 15; + pub const TYPE_INTERFACE: u8 = 16; + pub const TYPE_VEC: u8 = 17; + pub const TYPE_HASH_MAP: u8 = 18; + pub const TYPE_SYSTEM_TIME: u8 = 19; + pub const TYPE_DURATION: u8 = 20; + pub const TYPE_CALLBACK_INTERFACE: u8 = 21; + pub const TYPE_CUSTOM: u8 = 22; + pub const TYPE_UNIT: u8 = 255; +} + +const BUF_SIZE: usize = 2048; + +// This struct is a kludge around the fact that Rust const generic support doesn't quite handle our +// needs. +// +// We'd like to have code like this in `FfiConverter`: +// +// ``` +// const TYPE_ID_META_SIZE: usize; +// const TYPE_ID_META: [u8, Self::TYPE_ID_META_SIZE]; +// ``` +// +// This would define a metadata buffer, correctly size for the data needed. However, associated +// consts as generic params aren't supported yet. +// +// To work around this, we use `const MetadataBuffer` values, which contain fixed-sized buffers +// with enough capacity to store our largest metadata arrays. Since the `MetadataBuffer` values +// are const, they're only stored at compile time and the extra bytes don't end up contributing to +// the final binary size. This was tested on Rust `1.66.0` with `--release` by increasing +// `BUF_SIZE` and checking the compiled library sizes. +#[derive(Debug)] +pub struct MetadataBuffer { + pub bytes: [u8; BUF_SIZE], + pub size: usize, +} + +impl MetadataBuffer { + pub const fn new() -> Self { + Self { + bytes: [0; BUF_SIZE], + size: 0, + } + } + + pub const fn from_code(value: u8) -> Self { + Self::new().concat_value(value) + } + + // Concatenate another buffer to this one. + // + // 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(mut self, other: MetadataBuffer) -> MetadataBuffer { + assert!(self.size + other.size <= BUF_SIZE); + // It would be nice to use `copy_from_slice()`, but that's not allowed in const functions + // as of Rust 1.66. + let mut i = 0; + while i < other.size { + self.bytes[self.size] = other.bytes[i]; + self.size += 1; + i += 1; + } + self + } + + // Concatenate a `u8` 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_value(mut self, value: u8) -> Self { + assert!(self.size < BUF_SIZE); + self.bytes[self.size] = value; + self.size += 1; + 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 + // allocated an extra buffer + pub const fn concat_bool(self, value: bool) -> Self { + self.concat_value(value as u8) + } + + // Concatenate a string to this buffer. + // + // Strings are encoded as a `u8` length, followed by the utf8 data. + // + // 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_str(mut self, string: &str) -> Self { + assert!(string.len() < 256); + assert!(self.size + string.len() < BUF_SIZE); + self.bytes[self.size] = string.len() as u8; + self.size += 1; + let bytes = string.as_bytes(); + let mut i = 0; + while i < bytes.len() { + self.bytes[self.size] = bytes[i]; + self.size += 1; + i += 1; + } + self + } + + // Create an array from this MetadataBuffer + // + // SIZE should always be `self.size`. This is part of the kludge to hold us over until Rust + // gets better const generic support. + pub const fn into_array(self) -> [u8; SIZE] { + let mut result: [u8; SIZE] = [0; SIZE]; + let mut i = 0; + while i < SIZE { + result[i] = self.bytes[i]; + i += 1; + } + result + } +} + +impl AsRef<[u8]> for MetadataBuffer { + fn as_ref(&self) -> &[u8] { + &self.bytes[..self.size] + } +} diff --git a/uniffi_macros/src/enum_.rs b/uniffi_macros/src/enum_.rs index bdf637bc06..3df669c449 100644 --- a/uniffi_macros/src/enum_.rs +++ b/uniffi_macros/src/enum_.rs @@ -1,19 +1,13 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{ - punctuated::Punctuated, Data, DataEnum, DeriveInput, Field, Index, Path, Token, Variant, -}; -use uniffi_meta::{EnumMetadata, FieldMetadata, VariantMetadata}; - -use crate::{ - export::metadata::convert::convert_type, - util::{ - assert_type_eq, create_metadata_static_var, tagged_impl_header, try_read_field, - AttributeSliceExt, CommonAttr, - }, +use syn::{Data, DataEnum, DeriveInput, Field, Index, Path}; + +use crate::util::{ + create_metadata_static_var, tagged_impl_header, try_metadata_value_from_usize, try_read_field, + type_name, AttributeSliceExt, CommonAttr, }; -pub fn expand_enum(input: DeriveInput, module_path: Vec) -> syn::Result { +pub fn expand_enum(input: DeriveInput) -> syn::Result { let enum_ = match input.data { Data::Enum(e) => e, _ => { @@ -28,19 +22,12 @@ pub fn expand_enum(input: DeriveInput, module_path: Vec) -> syn::Result< let attr = input.attrs.parse_uniffi_attributes::()?; let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, attr.tag.as_ref()); - let meta_static_var = { - match enum_metadata(ident, enum_.variants, module_path) { - Ok(metadata) => create_metadata_static_var(ident, metadata.into()), - Err(e) => e.into_compile_error(), - } - }; - - let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); + let meta_static_var = + enum_meta_static_var(ident, &enum_).unwrap_or_else(|e| e.into_compile_error()); Ok(quote! { #ffi_converter_impl #meta_static_var - #type_assertion }) } @@ -60,6 +47,34 @@ pub(crate) fn enum_ffi_converter_impl( enum_: &DataEnum, tag: Option<&Path>, ) -> TokenStream { + enum_or_error_ffi_converter_impl( + ident, + enum_, + tag, + quote! { ::uniffi::metadata::codes::TYPE_ENUM }, + ) +} + +pub(crate) fn rich_error_ffi_converter_impl( + ident: &Ident, + enum_: &DataEnum, + tag: Option<&Path>, +) -> TokenStream { + enum_or_error_ffi_converter_impl( + ident, + enum_, + tag, + quote! { ::uniffi::metadata::codes::TYPE_ERROR }, + ) +} + +fn enum_or_error_ffi_converter_impl( + ident: &Ident, + enum_: &DataEnum, + tag: Option<&Path>, + metadata_type_code: TokenStream, +) -> TokenStream { + let name = ident.to_string(); let impl_spec = tagged_impl_header("FfiConverter", ident, tag); let write_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { let v_ident = &v.ident; @@ -109,57 +124,13 @@ pub(crate) fn enum_ffi_converter_impl( fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { #try_read_impl } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_type_code) + .concat_str(#name); } } } -fn enum_metadata( - ident: &Ident, - variants: Punctuated, - module_path: Vec, -) -> syn::Result { - let name = ident.to_string(); - let variants = variants - .iter() - .map(variant_metadata) - .collect::>()?; - - Ok(EnumMetadata { - module_path, - name, - variants, - }) -} - -pub(crate) fn variant_metadata(v: &Variant) -> syn::Result { - let name = v.ident.to_string(); - let fields = v - .fields - .iter() - .map(|f| field_metadata(f, v)) - .collect::>()?; - - Ok(VariantMetadata { name, fields }) -} - -fn field_metadata(f: &Field, v: &Variant) -> syn::Result { - let name = f - .ident - .as_ref() - .ok_or_else(|| { - syn::Error::new_spanned( - v, - "UniFFI only supports enum variants with named fields (or no fields at all)", - ) - })? - .to_string(); - - Ok(FieldMetadata { - name, - ty: convert_type(&f.ty)?, - }) -} - fn write_field(f: &Field) -> TokenStream { let ident = &f.ident; let ty = &f.ty; @@ -168,3 +139,62 @@ fn write_field(f: &Field) -> TokenStream { <#ty as ::uniffi::FfiConverter>::write(#ident, buf); } } + +pub(crate) fn enum_meta_static_var(ident: &Ident, enum_: &DataEnum) -> syn::Result { + let name = ident.to_string(); + + let mut metadata_expr = quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM) + .concat_str(module_path!()) + .concat_str(#name) + }; + metadata_expr.extend(variant_metadata(enum_)?); + Ok(create_metadata_static_var( + "ENUM", + &ident.to_string(), + metadata_expr, + )) +} + +pub fn variant_metadata(enum_: &DataEnum) -> syn::Result> { + let variants_len = + try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?; + std::iter::once(Ok(quote! { .concat_value(#variants_len) })) + .chain( + enum_.variants + .iter() + .map(|v| { + let fields_len = try_metadata_value_from_usize( + v.fields.len(), + "UniFFI limits enum variants to 256 fields", + )?; + + let field_names = v.fields + .iter() + .map(|f| { + f.ident + .as_ref() + .ok_or_else(|| + syn::Error::new_spanned( + v, + "UniFFI only supports enum variants with named fields (or no fields at all)", + ) + ) + .map(|i| i.to_string()) + }) + .collect::>>()?; + + let name = type_name(&v.ident); + let field_types = v.fields.iter().map(|f| &f.ty); + Ok(quote! { + .concat_str(#name) + .concat_value(#fields_len) + #( + .concat_str(#field_names) + .concat(<#field_types as ::uniffi::FfiConverter>::TYPE_ID_META) + )* + }) + }) + ) + .collect() +} diff --git a/uniffi_macros/src/error.rs b/uniffi_macros/src/error.rs index 38cf6092c6..d14f4bf740 100644 --- a/uniffi_macros/src/error.rs +++ b/uniffi_macros/src/error.rs @@ -2,20 +2,19 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{ parse::{Parse, ParseStream}, - punctuated::Punctuated, - Data, DataEnum, DeriveInput, Index, Path, Token, Variant, + Data, DataEnum, DeriveInput, Index, Path, Token, }; -use uniffi_meta::{ErrorMetadata, VariantMetadata}; use crate::{ - enum_::{enum_ffi_converter_impl, variant_metadata}, + enum_::{rich_error_ffi_converter_impl, variant_metadata}, util::{ - assert_type_eq, chain, create_metadata_static_var, either_attribute_arg, - parse_comma_separated, tagged_impl_header, AttributeSliceExt, UniffiAttribute, + chain, create_metadata_static_var, either_attribute_arg, parse_comma_separated, + tagged_impl_header, try_metadata_value_from_usize, type_name, AttributeSliceExt, + UniffiAttribute, }, }; -pub fn expand_error(input: DeriveInput, module_path: Vec) -> syn::Result { +pub fn expand_error(input: DeriveInput) -> syn::Result { let enum_ = match input.data { Data::Enum(e) => e, _ => { @@ -29,13 +28,8 @@ pub fn expand_error(input: DeriveInput, module_path: Vec) -> syn::Result let ident = &input.ident; let attr = input.attrs.parse_uniffi_attributes::()?; let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr); + let meta_static_var = error_meta_static_var(ident, &enum_, attr.flat.is_some())?; - let meta_static_var = match error_metadata(ident, &enum_.variants, module_path, &attr) { - Ok(metadata) => create_metadata_static_var(ident, metadata.into()), - Err(e) => e.into_compile_error(), - }; - - let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); let variant_errors: TokenStream = enum_ .variants .iter() @@ -54,24 +48,21 @@ pub fn expand_error(input: DeriveInput, module_path: Vec) -> syn::Result Ok(quote! { #ffi_converter_impl #meta_static_var - #type_assertion #variant_errors }) } pub(crate) fn expand_ffi_converter_error(attr: ErrorAttr, input: DeriveInput) -> TokenStream { - let enum_ = match input.data { - Data::Enum(e) => e, + match input.data { + Data::Enum(e) => error_ffi_converter_impl(&input.ident, &e, &attr), _ => { - return syn::Error::new( + syn::Error::new( proc_macro2::Span::call_site(), "This attribute must only be used on enums", ) - .into_compile_error(); + .into_compile_error() } - }; - - error_ffi_converter_impl(&input.ident, &enum_, &attr) + } } fn error_ffi_converter_impl(ident: &Ident, enum_: &DataEnum, attr: &ErrorAttr) -> TokenStream { @@ -83,16 +74,20 @@ fn error_ffi_converter_impl(ident: &Ident, enum_: &DataEnum, attr: &ErrorAttr) - attr.with_try_read.is_some(), ) } else { - enum_ffi_converter_impl(ident, enum_, attr.tag.as_ref()) + rich_error_ffi_converter_impl(ident, enum_, attr.tag.as_ref()) } } +// FfiConverters for "flat errors" +// +// These are errors where we only expose the variants, not the data. fn flat_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, tag: Option<&Path>, implement_try_read: bool, ) -> TokenStream { + let name = ident.to_string(); let impl_spec = tagged_impl_header("FfiConverter", ident, tag); let write_impl = { @@ -103,7 +98,7 @@ fn flat_error_ffi_converter_impl( quote! { Self::#v_ident { .. } => { ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx); - <::std::string::String as ::uniffi::FfiConverter<()>>::write(error_msg, buf); + <::std::string::String as ::uniffi::FfiConverter>::write(error_msg, buf); } } }); @@ -145,39 +140,43 @@ fn flat_error_ffi_converter_impl( fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { #try_read_impl } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_ERROR) + .concat_str(#name); } } } -fn error_metadata( +pub(crate) fn error_meta_static_var( ident: &Ident, - variants: &Punctuated, - module_path: Vec, - attr: &ErrorAttr, -) -> syn::Result { + enum_: &DataEnum, + flat: bool, +) -> syn::Result { let name = ident.to_string(); - let flat = attr.flat.is_some(); - let variants = if flat { - variants - .iter() - .map(|v| VariantMetadata { - name: v.ident.to_string(), - fields: vec![], - }) - .collect() - } else { - variants - .iter() - .map(variant_metadata) - .collect::>()? + let flat_code = if flat { 1u8 } else { 0u8 }; + let mut metadata_expr = quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ERROR) + .concat_str(module_path!()) + .concat_str(#name) + .concat_value(#flat_code) }; + if flat { + metadata_expr.extend(flat_error_variant_metadata(enum_)?); + } else { + metadata_expr.extend(variant_metadata(enum_)?); + } + Ok(create_metadata_static_var("ERROR", &name, metadata_expr)) +} - Ok(ErrorMetadata { - module_path, - name, - variants, - flat, - }) +pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result> { + let variants_len = + try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?; + Ok(std::iter::once(quote! { .concat_value(#variants_len) }) + .chain(enum_.variants.iter().map(|v| { + let name = type_name(&v.ident); + quote! { .concat_str(#name) } + })) + .collect()) } mod kw { diff --git a/uniffi_macros/src/export.rs b/uniffi_macros/src/export.rs index f2e5e6e9cb..3a3844ca17 100644 --- a/uniffi_macros/src/export.rs +++ b/uniffi_macros/src/export.rs @@ -2,29 +2,23 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use std::collections::BTreeMap; - use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, quote_spanned}; +use quote::quote_spanned; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned, LitStr, Token, }; -use uniffi_meta::{checksum, FnMetadata, MethodMetadata, Type}; pub(crate) mod metadata; mod scaffolding; pub use self::metadata::gen_metadata; use self::{ - metadata::convert::{convert_type, try_split_result}, + metadata::convert::try_split_result, scaffolding::{gen_fn_scaffolding, gen_method_scaffolding}, }; -use crate::util::{ - assert_type_eq, create_metadata_static_var, either_attribute_arg, parse_comma_separated, - UniffiAttribute, -}; +use crate::util::{either_attribute_arg, parse_comma_separated, UniffiAttribute}; // TODO(jplatte): Ensure no generics, … // TODO(jplatte): Aggregate errors instead of short-circuiting, wherever possible @@ -32,7 +26,6 @@ use crate::util::{ pub enum ExportItem { Function { sig: Signature, - metadata: FnMetadata, }, Impl { self_ident: Ident, @@ -42,7 +35,6 @@ pub enum ExportItem { pub struct Method { sig: Signature, - metadata: MethodMetadata, } pub struct Signature { @@ -88,63 +80,25 @@ impl FunctionReturn { pub fn expand_export( metadata: ExportItem, arguments: ExportAttributeArguments, - mod_path: &[String], -) -> TokenStream { + mod_path: &str, +) -> syn::Result { match metadata { - ExportItem::Function { sig, metadata } => { - let checksum = checksum(&metadata); - let scaffolding = gen_fn_scaffolding(&sig, mod_path, checksum, &arguments); - let type_assertions = fn_type_assertions(&sig); - let meta_static_var = create_metadata_static_var(&sig.ident, metadata.into()); - - quote! { - #scaffolding - #type_assertions - #meta_static_var - } - } + ExportItem::Function { sig } => gen_fn_scaffolding(&sig, mod_path, &arguments), ExportItem::Impl { methods, self_ident, } => { - let method_tokens: TokenStream = methods - .into_iter() - .map(|res| { - res.map_or_else( - syn::Error::into_compile_error, - |Method { sig, metadata }| { - let checksum = checksum(&metadata); - let scaffolding = gen_method_scaffolding( - &sig, - mod_path, - checksum, - &self_ident, - &arguments, - ); - let type_assertions = fn_type_assertions(&sig); - let meta_static_var = create_metadata_static_var( - &format_ident!("{}_{}", metadata.self_name, sig.ident), - metadata.into(), - ); - - quote! { - #scaffolding - #type_assertions - #meta_static_var - } - }, - ) - }) - .collect(); - - quote_spanned! {self_ident.span()=> - ::uniffi::deps::static_assertions::assert_type_eq_all!( - #self_ident, - crate::uniffi_types::#self_ident - ); - - #method_tokens + let mut method_tokens = vec![]; + for method in methods { + let sig = method?.sig; + method_tokens.push(gen_method_scaffolding( + &sig, + mod_path, + &self_ident, + &arguments, + )?) } + Ok(quote_spanned! { self_ident.span() => #(#method_tokens)* }) } } } @@ -205,81 +159,3 @@ impl Spanned for AsyncRuntime { } } } -fn fn_type_assertions(sig: &Signature) -> TokenStream { - // Convert uniffi_meta::Type back to a Rust type - fn convert_type_back(ty: &Type) -> TokenStream { - match &ty { - Type::U8 => quote! { ::std::primitive::u8 }, - Type::U16 => quote! { ::std::primitive::u16 }, - Type::U32 => quote! { ::std::primitive::u32 }, - Type::U64 => quote! { ::std::primitive::u64 }, - Type::I8 => quote! { ::std::primitive::i8 }, - Type::I16 => quote! { ::std::primitive::i16 }, - Type::I32 => quote! { ::std::primitive::i32 }, - Type::I64 => quote! { ::std::primitive::i64 }, - Type::F32 => quote! { ::std::primitive::f32 }, - Type::F64 => quote! { ::std::primitive::f64 }, - Type::Bool => quote! { ::std::primitive::bool }, - Type::String => quote! { ::std::string::String }, - Type::Option { inner_type } => { - let inner = convert_type_back(inner_type); - quote! { ::std::option::Option<#inner> } - } - Type::Vec { inner_type } => { - let inner = convert_type_back(inner_type); - quote! { ::std::vec::Vec<#inner> } - } - Type::HashMap { - key_type, - value_type, - } => { - let key = convert_type_back(key_type); - let value = convert_type_back(value_type); - quote! { ::std::collections::HashMap<#key, #value> } - } - Type::ArcObject { object_name } => { - let object_ident = format_ident!("{object_name}"); - quote! { ::std::sync::Arc } - } - Type::Unresolved { name } => { - let ident = format_ident!("{name}"); - quote! { crate::uniffi_types::#ident } - } - } - } - - let input_types = sig.inputs.iter().filter_map(|input| match input { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(pat_ty) => match &*pat_ty.pat { - // Self type is asserted separately for impl blocks - syn::Pat::Ident(i) if i.ident == "self" => None, - _ => Some(&pat_ty.ty), - }, - }); - let output_type = sig.output.as_ref().map(|s| &s.ty); - - let type_assertions: BTreeMap<_, _> = input_types - .chain(output_type) - .filter_map(|ty| { - convert_type(ty).ok().map(|meta_ty| { - let expected_ty = convert_type_back(&meta_ty); - let assert = assert_type_eq(ty, expected_ty); - (meta_ty, assert) - }) - }) - .collect(); - let input_output_type_assertions: TokenStream = type_assertions.into_values().collect(); - - let throws_type_assertion = sig.output.as_ref().and_then(|s| { - let ident = s.throws.as_ref()?; - Some(assert_type_eq( - ident, - quote! { crate::uniffi_types::#ident }, - )) - }); - - quote! { - #input_output_type_assertions - #throws_type_assertion - } -} diff --git a/uniffi_macros/src/export/metadata.rs b/uniffi_macros/src/export/metadata.rs index 2d0b284333..1386b9d908 100644 --- a/uniffi_macros/src/export/metadata.rs +++ b/uniffi_macros/src/export/metadata.rs @@ -12,10 +12,10 @@ mod impl_; use self::{function::gen_fn_metadata, impl_::gen_impl_metadata}; -pub fn gen_metadata(item: syn::Item, mod_path: &[String]) -> syn::Result { +pub fn gen_metadata(item: syn::Item) -> syn::Result { match item { - syn::Item::Fn(item) => gen_fn_metadata(item.sig, mod_path), - syn::Item::Impl(item) => gen_impl_metadata(item, mod_path), + syn::Item::Fn(item) => gen_fn_metadata(item.sig), + syn::Item::Impl(item) => gen_impl_metadata(item), // FIXME: Support const / static? _ => Err(syn::Error::new( Span::call_site(), diff --git a/uniffi_macros/src/export/metadata/convert.rs b/uniffi_macros/src/export/metadata/convert.rs index c19ae579c2..d53c7404ef 100644 --- a/uniffi_macros/src/export/metadata/convert.rs +++ b/uniffi_macros/src/export/metadata/convert.rs @@ -4,146 +4,6 @@ use proc_macro2::Ident; use quote::ToTokens; -use uniffi_meta::{FnParamMetadata, Type}; - -pub(super) fn fn_param_metadata(params: &[syn::FnArg]) -> syn::Result> { - params - .iter() - .filter_map(|a| { - let _is_method = false; - let (name, ty) = match a { - // methods currently have an implicit self parameter in uniffi_meta - syn::FnArg::Receiver(_) => return None, - syn::FnArg::Typed(pat_ty) => { - let name = match &*pat_ty.pat { - syn::Pat::Ident(pat_id) => pat_id.ident.to_string(), - _ => unimplemented!(), - }; - - // methods currently have an implicit self parameter in uniffi_meta - if name == "self" { - return None; - } - - (name, &pat_ty.ty) - } - }; - - Some(convert_type(ty).map(|ty| FnParamMetadata { name, ty })) - }) - .collect() -} - -pub(crate) fn convert_return_type(ty: &syn::Type) -> syn::Result> { - match ty { - syn::Type::Tuple(tup) if tup.elems.is_empty() => Ok(None), - _ => convert_type(ty).map(Some), - } -} - -pub(crate) fn convert_type(ty: &syn::Type) -> syn::Result { - let type_path = type_as_type_path(ty)?; - - if type_path.qself.is_some() { - return Err(syn::Error::new_spanned( - type_path, - "qualified self types are not currently supported by uniffi::export", - )); - } - - if type_path.path.segments.len() > 1 { - return Err(syn::Error::new_spanned( - type_path, - "qualified paths in types are not currently supported by uniffi::export", - )); - } - - match &type_path.path.segments.first() { - Some(seg) => match &seg.arguments { - syn::PathArguments::None => Ok(convert_bare_type_name(&seg.ident)), - syn::PathArguments::AngleBracketed(a) => convert_generic_type(&seg.ident, a), - syn::PathArguments::Parenthesized(_) => Err(type_not_supported(type_path)), - }, - None => Err(syn::Error::new_spanned( - type_path, - "unreachable: TypePath must have non-empty segments", - )), - } -} - -fn convert_generic_type( - ident: &Ident, - a: &syn::AngleBracketedGenericArguments, -) -> syn::Result { - let mut it = a.args.iter(); - match it.next() { - // `u8<>` is a valid way to write `u8` in the type namespace, so why not? - None => Ok(convert_bare_type_name(ident)), - Some(arg1) => match it.next() { - None => convert_generic_type1(ident, arg1), - Some(arg2) => match it.next() { - None => convert_generic_type2(ident, arg1, arg2), - Some(_) => Err(syn::Error::new_spanned( - ident, - "types with more than two generics are not currently - supported by uniffi::export", - )), - }, - }, - } -} - -fn convert_bare_type_name(ident: &Ident) -> Type { - let name = ident.to_string(); - match name.as_str() { - "u8" => Type::U8, - "u16" => Type::U16, - "u32" => Type::U32, - "u64" => Type::U64, - "i8" => Type::I8, - "i16" => Type::I16, - "i32" => Type::I32, - "i64" => Type::I64, - "f32" => Type::F32, - "f64" => Type::F64, - "bool" => Type::Bool, - "String" => Type::String, - _ => Type::Unresolved { name }, - } -} - -fn convert_generic_type1(ident: &Ident, arg: &syn::GenericArgument) -> syn::Result { - let arg = arg_as_type(arg)?; - match ident.to_string().as_str() { - "Arc" => Ok(Type::ArcObject { - object_name: type_as_type_name(arg)?.to_string(), - }), - "Option" => Ok(Type::Option { - inner_type: convert_type(arg)?.into(), - }), - "Vec" => Ok(Type::Vec { - inner_type: convert_type(arg)?.into(), - }), - _ => Err(type_not_supported(ident)), - } -} - -fn convert_generic_type2( - ident: &Ident, - arg1: &syn::GenericArgument, - arg2: &syn::GenericArgument, -) -> syn::Result { - let arg1 = arg_as_type(arg1)?; - let arg2 = arg_as_type(arg2)?; - - match ident.to_string().as_str() { - "HashMap" => Ok(Type::HashMap { - key_type: convert_type(arg1)?.into(), - value_type: convert_type(arg2)?.into(), - }), - _ => Err(type_not_supported(ident)), - } -} fn type_as_type_name(arg: &syn::Type) -> syn::Result<&Ident> { type_as_type_path(arg)? diff --git a/uniffi_macros/src/export/metadata/function.rs b/uniffi_macros/src/export/metadata/function.rs index 18d35fe7eb..7626ed0772 100644 --- a/uniffi_macros/src/export/metadata/function.rs +++ b/uniffi_macros/src/export/metadata/function.rs @@ -2,32 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use uniffi_meta::FnMetadata; - -use super::convert::{convert_return_type, fn_param_metadata}; use crate::export::{ExportItem, Signature}; -pub(super) fn gen_fn_metadata(sig: syn::Signature, mod_path: &[String]) -> syn::Result { +pub(super) fn gen_fn_metadata(sig: syn::Signature) -> syn::Result { let sig = Signature::new(sig)?; - let metadata = fn_metadata(&sig, mod_path)?; - Ok(ExportItem::Function { sig, metadata }) -} - -fn fn_metadata(sig: &Signature, mod_path: &[String]) -> syn::Result { - let (return_type, throws) = match &sig.output { - Some(ret) => ( - convert_return_type(&ret.ty)?, - ret.throws.as_ref().map(ToString::to_string), - ), - None => (None, None), - }; - - Ok(FnMetadata { - module_path: mod_path.to_owned(), - name: sig.ident.to_string(), - is_async: sig.is_async, - inputs: fn_param_metadata(&sig.inputs)?, - return_type, - throws, - }) + Ok(ExportItem::Function { sig }) } diff --git a/uniffi_macros/src/export/metadata/impl_.rs b/uniffi_macros/src/export/metadata/impl_.rs index e94e5269f6..c49ccf5d73 100644 --- a/uniffi_macros/src/export/metadata/impl_.rs +++ b/uniffi_macros/src/export/metadata/impl_.rs @@ -2,15 +2,10 @@ * 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 uniffi_meta::MethodMetadata; - -use super::convert::{convert_return_type, fn_param_metadata, type_as_type_path}; +use super::convert::type_as_type_path; use crate::export::{ExportItem, Method, Signature}; -pub(super) fn gen_impl_metadata( - item: syn::ItemImpl, - mod_path: &[String], -) -> syn::Result { +pub(super) fn gen_impl_metadata(item: syn::ItemImpl) -> syn::Result { if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { return Err(syn::Error::new_spanned( &item.generics, @@ -37,11 +32,7 @@ pub(super) fn gen_impl_metadata( } }; - let methods = item - .items - .into_iter() - .map(|it| gen_method_metadata(it, &self_ident.to_string(), mod_path)) - .collect(); + let methods = item.items.into_iter().map(gen_method_metadata).collect(); Ok(ExportItem::Impl { methods, @@ -49,11 +40,7 @@ pub(super) fn gen_impl_metadata( }) } -fn gen_method_metadata( - it: syn::ImplItem, - self_name: &str, - mod_path: &[String], -) -> syn::Result { +fn gen_method_metadata(it: syn::ImplItem) -> syn::Result { let sig = match it { syn::ImplItem::Method(m) => Signature::new(m.sig)?, _ => { @@ -64,31 +51,5 @@ fn gen_method_metadata( } }; - let metadata = method_metadata(self_name, &sig, mod_path)?; - - Ok(Method { sig, metadata }) -} - -fn method_metadata( - self_name: &str, - sig: &Signature, - mod_path: &[String], -) -> syn::Result { - let (return_type, throws) = match &sig.output { - Some(ret) => ( - convert_return_type(&ret.ty)?, - ret.throws.as_ref().map(ToString::to_string), - ), - None => (None, None), - }; - - Ok(MethodMetadata { - module_path: mod_path.to_owned(), - self_name: self_name.to_owned(), - is_async: sig.is_async, - name: sig.ident.to_string(), - inputs: fn_param_metadata(&sig.inputs)?, - return_type, - throws, - }) + Ok(Method { sig }) } diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index 0f10faaf70..f95c577bb5 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -4,93 +4,89 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; -use syn::{spanned::Spanned, FnArg, Pat}; +use syn::{ext::IdentExt, spanned::Spanned, FnArg, Pat}; use super::{AsyncRuntime, ExportAttributeArguments, FunctionReturn, Signature}; +use crate::util::{create_metadata_static_var, try_metadata_value_from_usize, type_name}; pub(super) fn gen_fn_scaffolding( sig: &Signature, - mod_path: &[String], - checksum: u16, + mod_path: &str, arguments: &ExportAttributeArguments, -) -> TokenStream { +) -> syn::Result { let name = &sig.ident; let name_s = name.to_string(); let ffi_ident = Ident::new( - &uniffi_meta::fn_ffi_symbol_name(mod_path, &name_s, checksum), + &uniffi_meta::fn_ffi_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 (params, args): (Vec<_>, Vec<_>) = collect_params(&sig.inputs, ERROR_MSG).unzip(); - - let fn_call = quote! { - #name(#(#args),*) - }; - - gen_ffi_function(sig, ffi_ident, ¶ms, fn_call, arguments) + 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)?; + let scaffolding_func = gen_ffi_function(sig, ffi_ident, &bits, arguments); + Ok(quote! { + #scaffolding_func + #metadata_var + }) } pub(super) fn gen_method_scaffolding( sig: &Signature, - mod_path: &[String], - checksum: u16, + mod_path: &str, self_ident: &Ident, arguments: &ExportAttributeArguments, -) -> TokenStream { - let name = &sig.ident; - let name_s = name.to_string(); +) -> syn::Result { + let ident = &sig.ident; + let name_s = ident.unraw().to_string(); let ffi_name = format!("impl_{self_ident}_{name_s}"); let ffi_ident = Ident::new( - &uniffi_meta::fn_ffi_symbol_name(mod_path, &ffi_name, checksum), + &uniffi_meta::fn_ffi_symbol_name(mod_path, &ffi_name), Span::call_site(), ); - let mut params_args = (Vec::new(), Vec::new()); - const RECEIVER_ERROR: &str = "unreachable: only first parameter can be method receiver"; - let mut assoc_fn_error = None; - let fn_call_prefix = match sig.inputs.first() { + let bits = match sig.inputs.first() { + // Method calls Some(arg) if is_receiver(arg) => { let ffi_converter = quote! { <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter> }; - - params_args.0.push(quote! { this: #ffi_converter::FfiType }); - - let remaining_args = sig.inputs.iter().skip(1); - params_args.extend(collect_params(remaining_args, RECEIVER_ERROR)); - - quote! { + 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); + // To call the method: + // - lift the `this` param to get the object + // - Add `.#ident` to get the method + bits.set_rust_fn_call(quote! { #ffi_converter::try_lift(this).unwrap_or_else(|err| { ::std::panic!("Failed to convert arg 'self': {}", err) - }). - } + }).#ident + }); + bits } + // Associated functions _ => { - assoc_fn_error = Some( - syn::Error::new_spanned( - &sig.ident, - "associated functions are not currently supported", - ) - .into_compile_error(), - ); - params_args.extend(collect_params(&sig.inputs, RECEIVER_ERROR)); - quote! { #self_ident:: } + return Err(syn::Error::new_spanned( + &sig.ident, + "associated functions are not currently supported", + )) } }; - let (params, args) = params_args; - - let fn_call = quote! { - #assoc_fn_error - #fn_call_prefix #name(#(#args),*) - }; - - gen_ffi_function(sig, ffi_ident, ¶ms, fn_call, arguments) + let metadata_var = bits.gen_method_meta_static_var(self_ident, sig)?; + let scaffolding_func = gen_ffi_function(sig, ffi_ident, &bits, arguments); + Ok(quote! { + #scaffolding_func + #metadata_var + }) } fn is_receiver(fn_arg: &FnArg) -> bool { @@ -100,66 +96,211 @@ fn is_receiver(fn_arg: &FnArg) -> bool { } } -fn collect_params<'a>( - inputs: impl IntoIterator + 'a, - receiver_error_msg: &'static str, -) -> impl Iterator + 'a { - fn receiver_error( - receiver: impl ToTokens, - receiver_error_msg: &str, - ) -> (TokenStream, TokenStream) { - let param = quote! { &self }; - let arg = syn::Error::new_spanned(receiver, receiver_error_msg).into_compile_error(); - (param, arg) +// Pieces of code for the scaffolding args +struct ScaffoldingBits { + /// Tokenstream that represents the function to call + /// + /// For functions, this is simple the function ident. + /// For methods, this will lift for the `self` param, followed by the method name. + rust_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, +} + +impl ScaffoldingBits { + fn new() -> Self { + Self { + rust_fn_call: None, + params: vec![], + param_lifts: vec![], + arg_metadata_calls: vec![], + } } - inputs.into_iter().enumerate().map(|(i, arg)| { - let (ty, name) = match arg { - FnArg::Receiver(r) => { - return receiver_error(r, receiver_error_msg); - } - FnArg::Typed(pat_ty) => { - let name = match &*pat_ty.pat { - Pat::Ident(i) if i.ident == "self" => { - return receiver_error(i, receiver_error_msg); - } - Pat::Ident(i) => Some(i.ident.to_string()), - _ => None, - }; + 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(), + quote! { + .concat_str("") + .concat(::uniffi::metadata::codes::UNKNOWN) + }, + ); + } + + fn collect_params<'a>( + &mut self, + inputs: impl IntoIterator + 'a, + 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(i.ident.to_string()), + _ => 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("")); - (&pat_ty.ty, name) + 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 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),*) } } - }; + None => panic!("UniFFI Internal error: ScaffoldingBits.func not set"), + } + } - let arg_n = format_ident!("arg{i}"); - let param = quote! { #arg_n: <#ty as ::uniffi::FfiConverter>::FfiType }; + fn gen_function_meta_static_var(&self, sig: &Signature) -> syn::Result { + let name = type_name(&sig.ident); + 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; + let result_metadata_calls = self.result_metadata_calls(sig); + Ok(create_metadata_static_var( + "FUNC", + &name, + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::FUNC) + .concat_str(module_path!()) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + #result_metadata_calls + }, + )) + } - // 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 arg = quote! { - <#ty as ::uniffi::FfiConverter>::try_lift(#arg_n).unwrap_or_else(|err| { - ::std::panic!(#panic_fmt, err) - }) - }; + fn gen_method_meta_static_var( + &self, + self_ident: &Ident, + sig: &Signature, + ) -> syn::Result { + let object_name = type_name(self_ident); + let name = type_name(&sig.ident); + 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; + let result_metadata_calls = self.result_metadata_calls(sig); + Ok(create_metadata_static_var( + "METHOD", + &format!("{}_{}", object_name, name), + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::METHOD) + .concat_str(module_path!()) + .concat_str(#object_name) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + #result_metadata_calls + }, + )) + } - (param, arg) - }) + fn result_metadata_calls(&self, sig: &Signature) -> TokenStream { + match &sig.output { + Some(FunctionReturn { + ty, + throws: Some(throws), + }) => quote! { + .concat(<#ty as ::uniffi::FfiConverter>::TYPE_ID_META) + .concat(<#throws as ::uniffi::FfiConverter>::TYPE_ID_META) + }, + Some(FunctionReturn { ty, throws: None }) => quote! { + .concat(<#ty as ::uniffi::FfiConverter>::TYPE_ID_META) + .concat_value(::uniffi::metadata::codes::TYPE_UNIT) + }, + None => quote! { + .concat_value(::uniffi::metadata::codes::TYPE_UNIT) + .concat_value(::uniffi::metadata::codes::TYPE_UNIT) + }, + } + } } fn gen_ffi_function( sig: &Signature, ffi_ident: Ident, - params: &[TokenStream], - rust_fn_call: TokenStream, + bits: &ScaffoldingBits, arguments: &ExportAttributeArguments, ) -> TokenStream { let name = sig.ident.to_string(); let mut extra_functions = Vec::new(); let is_async = sig.is_async; + let rust_fn_call = bits.rust_fn_call(); + let fn_params = &bits.params; let (return_ty, throw_ty, return_expr, throws) = match &sig.output { Some(FunctionReturn { ty, throws: None }) if is_async => { @@ -306,7 +447,7 @@ fn gen_ffi_function( #[doc(hidden)] #[no_mangle] pub extern "C" fn #ffi_ident( - #(#params,)* + #(#fn_params,)* call_status: &mut ::uniffi::RustCallStatus, ) -> #return_expr { ::uniffi::deps::log::debug!(#name); diff --git a/uniffi_macros/src/lib.rs b/uniffi_macros/src/lib.rs index 8e1c58c843..dd5c82185f 100644 --- a/uniffi_macros/src/lib.rs +++ b/uniffi_macros/src/lib.rs @@ -61,8 +61,8 @@ pub fn export(attr_args: TokenStream, input: TokenStream) -> TokenStream { // new functions outside of the `impl`). rewrite_self_type(&mut item); - let metadata = export::gen_metadata(item, &mod_path)?; - Ok(expand_export(metadata, args, &mod_path)) + let metadata = export::gen_metadata(item)?; + expand_export(metadata, args, &mod_path) }; let output = gen_output().unwrap_or_else(syn::Error::into_compile_error); @@ -75,26 +75,14 @@ pub fn export(attr_args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_derive(Record)] pub fn derive_record(input: TokenStream) -> TokenStream { - let mod_path = match util::mod_path() { - Ok(p) => p, - Err(e) => return e.into_compile_error().into(), - }; - let input = parse_macro_input!(input); - - expand_record(input, mod_path) + expand_record(parse_macro_input!(input)) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_derive(Enum)] pub fn derive_enum(input: TokenStream) -> TokenStream { - let mod_path = match util::mod_path() { - Ok(p) => p, - Err(e) => return e.into_compile_error().into(), - }; - let input = parse_macro_input!(input); - - expand_enum(input, mod_path) + expand_enum(parse_macro_input!(input)) .unwrap_or_else(syn::Error::into_compile_error) .into() } @@ -114,13 +102,7 @@ pub fn derive_object(input: TokenStream) -> TokenStream { #[proc_macro_derive(Error, attributes(uniffi))] pub fn derive_error(input: TokenStream) -> TokenStream { - let mod_path = match util::mod_path() { - Ok(p) => p, - Err(e) => return e.into_compile_error().into(), - }; - let input = parse_macro_input!(input); - - expand_error(input, mod_path) + expand_error(parse_macro_input!(input)) .unwrap_or_else(syn::Error::into_compile_error) .into() } diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index a50e906457..72bf86fdab 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -4,17 +4,16 @@ use syn::{DeriveInput, Path}; use uniffi_meta::ObjectMetadata; use crate::util::{ - assert_type_eq, create_metadata_static_var, tagged_impl_header, AttributeSliceExt, CommonAttr, + create_metadata_static_var, tagged_impl_header, type_name, AttributeSliceExt, CommonAttr, }; -pub fn expand_object(input: DeriveInput, module_path: Vec) -> syn::Result { +pub fn expand_object(input: DeriveInput, module_path: String) -> syn::Result { let ident = &input.ident; let attr = input.attrs.parse_uniffi_attributes::()?; - let name = ident.to_string(); + let name = type_name(ident); let metadata = ObjectMetadata { module_path, name }; let free_fn_ident = Ident::new(&metadata.free_ffi_symbol_name(), Span::call_site()); - let meta_static_var = create_metadata_static_var(ident, metadata.into()); - let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); + let meta_static_var = interface_meta_static_var(ident)?; let interface_impl = interface_impl(ident, attr.tag.as_ref()); Ok(quote! { @@ -35,7 +34,6 @@ pub fn expand_object(input: DeriveInput, module_path: Vec) -> syn::Resul #interface_impl #meta_static_var - #type_assertion }) } @@ -44,10 +42,26 @@ pub(crate) fn expand_ffi_converter_interface(attr: CommonAttr, input: DeriveInpu } pub(crate) fn interface_impl(ident: &Ident, tag: Option<&Path>) -> TokenStream { + let name = type_name(ident); let impl_spec = tagged_impl_header("Interface", ident, tag); quote! { #[doc(hidden)] #[automatically_derived] - #impl_spec { } + #impl_spec { + const NAME: &'static str = #name; + } } } + +pub(crate) fn interface_meta_static_var(ident: &Ident) -> syn::Result { + let name = type_name(ident); + Ok(create_metadata_static_var( + "INTERFACE", + &name, + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::INTERFACE) + .concat_str(module_path!()) + .concat_str(#name) + }, + )) +} diff --git a/uniffi_macros/src/record.rs b/uniffi_macros/src/record.rs index b2cd641d8e..5b30bdc742 100644 --- a/uniffi_macros/src/record.rs +++ b/uniffi_macros/src/record.rs @@ -1,17 +1,13 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{Data, DataStruct, DeriveInput, Field, Fields, Path}; -use uniffi_meta::{FieldMetadata, RecordMetadata}; +use syn::{Data, DataStruct, DeriveInput, Field, Path}; -use crate::{ - export::metadata::convert::convert_type, - util::{ - assert_type_eq, create_metadata_static_var, tagged_impl_header, try_read_field, - AttributeSliceExt, CommonAttr, - }, +use crate::util::{ + create_metadata_static_var, tagged_impl_header, try_metadata_value_from_usize, try_read_field, + type_name, AttributeSliceExt, CommonAttr, }; -pub fn expand_record(input: DeriveInput, module_path: Vec) -> syn::Result { +pub fn expand_record(input: DeriveInput) -> syn::Result { let record = match input.data { Data::Struct(s) => s, _ => { @@ -25,16 +21,11 @@ pub fn expand_record(input: DeriveInput, module_path: Vec) -> syn::Resul let ident = &input.ident; let attr = input.attrs.parse_uniffi_attributes::()?; let ffi_converter = record_ffi_converter_impl(ident, &record, attr.tag.as_ref()); - let meta_static_var = match record_metadata(ident, record.fields, module_path) { - Ok(metadata) => create_metadata_static_var(ident, metadata.into()), - Err(e) => e.into_compile_error(), - }; - let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); + let meta_static_var = record_meta_static_var(ident, &record)?; Ok(quote! { #ffi_converter #meta_static_var - #type_assertion }) } @@ -55,6 +46,7 @@ pub(crate) fn record_ffi_converter_impl( tag: Option<&Path>, ) -> TokenStream { let impl_spec = tagged_impl_header("FfiConverter", ident, tag); + let name = type_name(ident); let write_impl: TokenStream = record.fields.iter().map(write_field).collect(); let try_read_fields: TokenStream = record.fields.iter().map(try_read_field).collect(); @@ -70,45 +62,11 @@ pub(crate) fn record_ffi_converter_impl( fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { Ok(Self { #try_read_fields }) } - } - } -} -fn record_metadata( - ident: &Ident, - fields: Fields, - module_path: Vec, -) -> syn::Result { - let name = ident.to_string(); - let fields = match fields { - Fields::Named(fields) => fields.named, - _ => { - return Err(syn::Error::new( - Span::call_site(), - "UniFFI only supports structs with named fields", - )); + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_RECORD) + .concat_str(#name); } - }; - - let fields = fields - .iter() - .map(field_metadata) - .collect::>()?; - - Ok(RecordMetadata { - module_path, - name, - fields, - }) -} - -fn field_metadata(f: &Field) -> syn::Result { - let name = f.ident.as_ref().unwrap().to_string(); - - Ok(FieldMetadata { - name, - ty: convert_type(&f.ty)?, - }) + } } fn write_field(f: &Field) -> TokenStream { @@ -119,3 +77,31 @@ fn write_field(f: &Field) -> TokenStream { <#ty as ::uniffi::FfiConverter>::write(obj.#ident, buf); } } + +pub(crate) fn record_meta_static_var( + ident: &Ident, + record: &DataStruct, +) -> syn::Result { + let name = type_name(ident); + let fields_len = + try_metadata_value_from_usize(record.fields.len(), "UniFFI limits structs to 256 fields")?; + let field_names = record + .fields + .iter() + .map(|f| f.ident.as_ref().unwrap().to_string()); + let field_types = record.fields.iter().map(|f| &f.ty); + Ok(create_metadata_static_var( + "RECORD", + &name, + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::RECORD) + .concat_str(module_path!()) + .concat_str(#name) + .concat_value(#fields_len) + #( + .concat_str(#field_names) + .concat(<#field_types as ::uniffi::FfiConverter>::TYPE_ID_META) + )* + }, + )) +} diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index 8158974d00..95c982e6ca 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -3,17 +3,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, quote_spanned, ToTokens}; +use quote::{format_ident, quote}; use syn::{ + ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, visit_mut::VisitMut, Attribute, Item, Path, Token, Type, }; -use uniffi_meta::Metadata; #[cfg(not(feature = "nightly"))] -pub fn mod_path() -> syn::Result> { +pub fn mod_path() -> syn::Result { // Without the nightly feature and TokenStream::expand_expr, just return the crate name use std::path::Path; @@ -39,7 +39,7 @@ pub fn mod_path() -> syn::Result> { name: Option, } - static LIB_CRATE_MOD_PATH: Lazy, String>> = Lazy::new(|| { + static LIB_CRATE_MOD_PATH: Lazy> = Lazy::new(|| { let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR").ok_or("`CARGO_MANIFEST_DIR` is not set")?; @@ -53,7 +53,7 @@ pub fn mod_path() -> syn::Result> { .name .unwrap_or_else(|| cargo_toml.package.name.replace('-', "_")); - Ok(vec![lib_crate_name]) + Ok(lib_crate_name) }); LIB_CRATE_MOD_PATH @@ -62,7 +62,7 @@ pub fn mod_path() -> syn::Result> { } #[cfg(feature = "nightly")] -pub fn mod_path() -> syn::Result> { +pub fn mod_path() -> syn::Result { use proc_macro::TokenStream; let module_path_invoc = TokenStream::from(quote! { ::core::module_path!() }); @@ -70,10 +70,7 @@ pub fn mod_path() -> syn::Result> { // This is a nightly feature, tracked at https://github.com/rust-lang/rust/issues/90765 let expanded_module_path = TokenStream::expand_expr(&module_path_invoc) .map_err(|e| syn::Error::new(Span::call_site(), e))?; - Ok(syn::parse::(expanded_module_path)? - .value() - .split("::") - .collect()) + Ok(syn::parse::(expanded_module_path)?.value()) } /// Rewrite Self type alias usage in an impl block to the type itself. @@ -137,25 +134,36 @@ pub fn try_read_field(f: &syn::Field) -> TokenStream { } } -pub fn create_metadata_static_var(name: &Ident, val: Metadata) -> TokenStream { - let data: Vec = bincode::serialize(&val).expect("Error serializing metadata item"); - let count = data.len(); - let var_name = format_ident!("UNIFFI_META_{}", name); +pub fn type_name(type_ident: &Ident) -> String { + type_ident.unraw().to_string() +} + +pub fn crate_name() -> String { + std::env::var("CARGO_CRATE_NAME").unwrap().replace('-', "_") +} + +pub fn create_metadata_static_var( + kind: &str, + name: &str, + metadata_expr: TokenStream, +) -> TokenStream { + let crate_name = crate_name().to_uppercase(); + let name = name.to_uppercase(); + let const_ident = format_ident!("UNIFFI_META_CONST_{crate_name}_{kind}_{name}"); + let static_ident = format_ident!("UNIFFI_META_{crate_name}_{kind}_{name}"); quote! { + const #const_ident: ::uniffi::MetadataBuffer = #metadata_expr; #[no_mangle] #[doc(hidden)] - pub static #var_name: [u8; #count] = [#(#data),*]; + pub static #static_ident: [u8; #const_ident.size] = #const_ident.into_array(); } } -pub fn assert_type_eq(a: impl ToTokens + Spanned, b: impl ToTokens) -> TokenStream { - quote_spanned! {a.span()=> - #[allow(unused_qualifications)] - const _: () = { - ::uniffi::deps::static_assertions::assert_type_eq_all!(#a, #b); - }; - } +pub fn try_metadata_value_from_usize(value: usize, error_message: &str) -> syn::Result { + value + .try_into() + .map_err(|_| syn::Error::new(Span::call_site(), error_message)) } pub fn chain( diff --git a/uniffi_meta/Cargo.toml b/uniffi_meta/Cargo.toml index 1a14eaa9e3..c4aa4ed40d 100644 --- a/uniffi_meta/Cargo.toml +++ b/uniffi_meta/Cargo.toml @@ -9,6 +9,8 @@ license = "MPL-2.0" keywords = ["ffi", "bindgen"] [dependencies] +anyhow = "1" +bytes = "1.0" serde = { version = "1.0.136", features = ["derive"] } siphasher = "0.3" uniffi_checksum_derive = { version = "0.23.0", path = "../uniffi_checksum_derive" } diff --git a/uniffi_meta/src/lib.rs b/uniffi_meta/src/lib.rs index 5094fdd083..7e3eb32778 100644 --- a/uniffi_meta/src/lib.rs +++ b/uniffi_meta/src/lib.rs @@ -7,6 +7,9 @@ pub use uniffi_checksum_derive::Checksum; use serde::{Deserialize, Serialize}; +mod reader; +pub use reader::MetadataReader; + /// Similar to std::hash::Hash. /// /// Implementations of this trait are expected to update the hasher state in @@ -95,41 +98,50 @@ impl Checksum for &str { } } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +// The namespace of a Component interface +// +// This is used to match up the macro metadata with the UDL items +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] +pub struct NamespaceMetadata { + pub crate_name: String, + pub name: String, +} + +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct FnMetadata { - pub module_path: Vec, + pub module_path: String, pub name: String, pub is_async: bool, pub inputs: Vec, pub return_type: Option, - pub throws: Option, + pub throws: Option, } impl FnMetadata { pub fn ffi_symbol_name(&self) -> String { - fn_ffi_symbol_name(&self.module_path, &self.name, checksum(self)) + fn_ffi_symbol_name(&self.module_path, &self.name) } } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct MethodMetadata { - pub module_path: Vec, + pub module_path: String, pub self_name: String, pub name: String, pub is_async: bool, pub inputs: Vec, pub return_type: Option, - pub throws: Option, + pub throws: Option, } impl MethodMetadata { pub fn ffi_symbol_name(&self) -> String { let full_name = format!("impl_{}_{}", self.self_name, self.name); - fn_ffi_symbol_name(&self.module_path, &full_name, checksum(self)) + fn_ffi_symbol_name(&self.module_path, &full_name) } } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct FnParamMetadata { pub name: String, #[serde(rename = "type")] @@ -150,6 +162,27 @@ pub enum Type { F64, Bool, String, + Duration, + SystemTime, + Enum { + name: String, + }, + Record { + name: String, + }, + ArcObject { + object_name: String, + }, + Error { + name: String, + }, + CallbackInterface { + name: String, + }, + Custom { + name: String, + builtin: Box, + }, Option { inner_type: Box, }, @@ -160,44 +193,38 @@ pub enum Type { key_type: Box, value_type: Box, }, - ArcObject { - object_name: String, - }, - Unresolved { - name: String, - }, } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct RecordMetadata { - pub module_path: Vec, + pub module_path: String, pub name: String, pub fields: Vec, } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct FieldMetadata { pub name: String, #[serde(rename = "type")] pub ty: Type, } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct EnumMetadata { - pub module_path: Vec, + pub module_path: String, pub name: String, pub variants: Vec, } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct VariantMetadata { pub name: String, pub fields: Vec, } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct ObjectMetadata { - pub module_path: Vec, + pub module_path: String, pub name: String, } @@ -207,13 +234,13 @@ impl ObjectMetadata { /// This function is used to free the memory used by this object. pub fn free_ffi_symbol_name(&self) -> String { let free_name = format!("object_free_{}", self.name); - fn_ffi_symbol_name(&self.module_path, &free_name, checksum(self)) + fn_ffi_symbol_name(&self.module_path, &free_name) } } -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub struct ErrorMetadata { - pub module_path: Vec, + pub module_path: String, pub name: String, pub variants: Vec, pub flat: bool, @@ -229,14 +256,15 @@ pub fn checksum(val: &T) -> u16 { (hasher.finish() & 0x000000000000FFFF) as u16 } -pub fn fn_ffi_symbol_name(mod_path: &[String], name: &str, checksum: u16) -> String { - let mod_path = mod_path.join("__"); - format!("_uniffi_{mod_path}_{name}_{checksum:x}") +pub fn fn_ffi_symbol_name(mod_path: &str, name: &str) -> String { + let mod_path = mod_path.replace("::", "__"); + format!("_uniffi_{mod_path}_{name}") } /// Enum covering all the possible metadata types -#[derive(Clone, Debug, Checksum, Deserialize, Serialize)] +#[derive(Clone, Debug, Checksum, Deserialize, PartialEq, Eq, Serialize)] pub enum Metadata { + Namespace(NamespaceMetadata), Func(FnMetadata), Method(MethodMetadata), Record(RecordMetadata), @@ -245,6 +273,19 @@ pub enum Metadata { Error(ErrorMetadata), } +impl Metadata { + pub fn read(data: &[u8]) -> anyhow::Result { + let reader = &mut &*data; + reader.read_metadata() + } +} + +impl From for Metadata { + fn from(value: NamespaceMetadata) -> Metadata { + Self::Namespace(value) + } +} + impl From for Metadata { fn from(value: FnMetadata) -> Metadata { Self::Func(value) diff --git a/uniffi_meta/src/reader.rs b/uniffi_meta/src/reader.rs new file mode 100644 index 0000000000..ccf3eac611 --- /dev/null +++ b/uniffi_meta/src/reader.rs @@ -0,0 +1,281 @@ +/* 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::*; +use anyhow::{bail, Context, Result}; + +/// Metadata constants, make sure to keep this in sync with copy in `uniffi_core::metadata` +#[allow(dead_code)] +pub mod codes { + // Top-level metadata item codes + pub const FUNC: u8 = 0; + pub const METHOD: u8 = 1; + pub const RECORD: u8 = 2; + pub const ENUM: u8 = 3; + pub const INTERFACE: u8 = 4; + pub const ERROR: u8 = 5; + pub const NAMESPACE: u8 = 6; + pub const UNKNOWN: u8 = 255; + + // Type codes + pub const TYPE_U8: u8 = 0; + pub const TYPE_U16: u8 = 1; + pub const TYPE_U32: u8 = 2; + pub const TYPE_U64: u8 = 3; + pub const TYPE_I8: u8 = 4; + pub const TYPE_I16: u8 = 5; + pub const TYPE_I32: u8 = 6; + pub const TYPE_I64: u8 = 7; + pub const TYPE_F32: u8 = 8; + pub const TYPE_F64: u8 = 9; + pub const TYPE_BOOL: u8 = 10; + pub const TYPE_STRING: u8 = 11; + pub const TYPE_OPTION: u8 = 12; + pub const TYPE_RECORD: u8 = 13; + pub const TYPE_ENUM: u8 = 14; + pub const TYPE_ERROR: u8 = 15; + pub const TYPE_INTERFACE: u8 = 16; + pub const TYPE_VEC: u8 = 17; + pub const TYPE_HASH_MAP: u8 = 18; + pub const TYPE_SYSTEM_TIME: u8 = 19; + pub const TYPE_DURATION: u8 = 20; + pub const TYPE_CALLBACK_INTERFACE: u8 = 21; + pub const TYPE_CUSTOM: u8 = 22; + pub const TYPE_UNIT: u8 = 255; +} + +/// Trait for types that can read Metadata +/// +/// We implement this on &[u8] byte buffers +pub trait MetadataReader { + fn read_u8(&mut self) -> Result; + fn read_bool(&mut self) -> Result; + fn read_string(&mut self) -> Result; + fn read_type(&mut self) -> Result; + fn read_optional_type(&mut self) -> Result>; + fn read_metadata(&mut self) -> Result; + fn read_func(&mut self) -> Result; + fn read_method(&mut self) -> Result; + fn read_record(&mut self) -> Result; + fn read_enum(&mut self) -> Result; + fn read_error(&mut self) -> Result; + fn read_object(&mut self) -> Result; + fn read_fields(&mut self) -> Result>; + fn read_variants(&mut self) -> Result>; + fn read_flat_variants(&mut self) -> Result>; + fn read_inputs(&mut self) -> Result>; +} + +impl MetadataReader for &[u8] { + fn read_u8(&mut self) -> Result { + if !self.is_empty() { + let value = self[0]; + *self = &self[1..]; + Ok(value) + } else { + bail!("Buffer is empty") + } + } + + fn read_bool(&mut self) -> Result { + Ok(self.read_u8()? == 1) + } + + fn read_string(&mut self) -> Result { + let size = self.read_u8()? as usize; + let slice; + (slice, *self) = self.split_at(size); + String::from_utf8(slice.into()).context("Invalid string data") + } + + fn read_type(&mut self) -> Result { + self.read_optional_type()? + .ok_or_else(|| anyhow::format_err!("saw TYPE_UNIT when a type was required")) + } + + fn read_optional_type(&mut self) -> Result> { + let value = self.read_u8()?; + Ok(match value { + codes::TYPE_UNIT => None, + v => Some(match v { + codes::TYPE_U8 => Type::U8, + codes::TYPE_I8 => Type::I8, + codes::TYPE_U16 => Type::U16, + codes::TYPE_I16 => Type::I16, + codes::TYPE_U32 => Type::U32, + codes::TYPE_I32 => Type::I32, + codes::TYPE_U64 => Type::U64, + codes::TYPE_I64 => Type::I64, + codes::TYPE_F32 => Type::F32, + codes::TYPE_F64 => Type::F64, + codes::TYPE_BOOL => Type::Bool, + codes::TYPE_STRING => Type::String, + codes::TYPE_DURATION => Type::Duration, + codes::TYPE_SYSTEM_TIME => Type::SystemTime, + codes::TYPE_RECORD => Type::Record { + name: self.read_string()?, + }, + codes::TYPE_ENUM => Type::Enum { + name: self.read_string()?, + }, + codes::TYPE_ERROR => Type::Error { + name: self.read_string()?, + }, + codes::TYPE_INTERFACE => Type::ArcObject { + object_name: self.read_string()?, + }, + codes::TYPE_CALLBACK_INTERFACE => Type::CallbackInterface { + name: self.read_string()?, + }, + codes::TYPE_CUSTOM => Type::Custom { + name: self.read_string()?, + builtin: Box::new(self.read_type()?), + }, + codes::TYPE_OPTION => Type::Option { + inner_type: Box::new(self.read_type()?), + }, + codes::TYPE_VEC => Type::Vec { + inner_type: Box::new(self.read_type()?), + }, + codes::TYPE_HASH_MAP => Type::HashMap { + key_type: Box::new(self.read_type()?), + value_type: Box::new(self.read_type()?), + }, + _ => bail!("Unexpected metadata type code: {value:?}"), + }), + }) + } + + fn read_metadata(&mut self) -> Result { + let value = self.read_u8()?; + Ok(match value { + codes::NAMESPACE => NamespaceMetadata { + crate_name: self.read_string()?, + name: self.read_string()?, + } + .into(), + codes::FUNC => self.read_func()?.into(), + codes::METHOD => self.read_method()?.into(), + codes::RECORD => self.read_record()?.into(), + codes::ENUM => self.read_enum()?.into(), + codes::ERROR => self.read_error()?.into(), + codes::INTERFACE => self.read_object()?.into(), + _ => bail!("Unexpected metadata code: {value:?}"), + }) + } + + fn read_func(&mut self) -> Result { + Ok(FnMetadata { + module_path: self.read_string()?, + name: self.read_string()?, + is_async: self.read_bool()?, + inputs: self.read_inputs()?, + return_type: self.read_optional_type()?, + throws: self.read_optional_type()?, + }) + } + + fn read_method(&mut self) -> Result { + Ok(MethodMetadata { + module_path: self.read_string()?, + self_name: self.read_string()?, + name: self.read_string()?, + is_async: self.read_bool()?, + inputs: self.read_inputs()?, + return_type: self.read_optional_type()?, + throws: self.read_optional_type()?, + }) + } + + fn read_record(&mut self) -> Result { + Ok(RecordMetadata { + module_path: self.read_string()?, + name: self.read_string()?, + fields: self.read_fields()?, + }) + } + + fn read_enum(&mut self) -> Result { + Ok(EnumMetadata { + module_path: self.read_string()?, + name: self.read_string()?, + variants: self.read_variants()?, + }) + } + + fn read_error(&mut self) -> Result { + let module_path = self.read_string()?; + let name = self.read_string()?; + let flat = self.read_bool()?; + Ok(ErrorMetadata { + module_path, + name, + flat, + variants: if flat { + self.read_flat_variants()? + } else { + self.read_variants()? + }, + }) + } + + fn read_object(&mut self) -> Result { + Ok(ObjectMetadata { + module_path: self.read_string()?, + name: self.read_string()?, + }) + } + + fn read_fields(&mut self) -> Result> { + let len = self.read_u8()?; + (0..len) + .into_iter() + .map(|_| { + Ok(FieldMetadata { + name: self.read_string()?, + ty: self.read_type()?, + }) + }) + .collect() + } + + fn read_variants(&mut self) -> Result> { + let len = self.read_u8()?; + (0..len) + .into_iter() + .map(|_| { + Ok(VariantMetadata { + name: self.read_string()?, + fields: self.read_fields()?, + }) + }) + .collect() + } + + fn read_flat_variants(&mut self) -> Result> { + let len = self.read_u8()?; + (0..len) + .into_iter() + .map(|_| { + Ok(VariantMetadata { + name: self.read_string()?, + fields: vec![], + }) + }) + .collect() + } + + fn read_inputs(&mut self) -> Result> { + let len = self.read_u8()?; + (0..len) + .into_iter() + .map(|_| { + Ok(FnParamMetadata { + name: self.read_string()?, + ty: self.read_type()?, + }) + }) + .collect() + } +}