diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/uniffi_bindgen/src/interface | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/interface')
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/callbacks.rs | 149 | ||||
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/enum_.rs | 567 | ||||
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/ffi.rs | 227 | ||||
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/function.rs | 367 | ||||
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/mod.rs | 1234 | ||||
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/object.rs | 773 | ||||
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/record.rs | 230 | ||||
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/universe.rs | 136 |
8 files changed, 3683 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs new file mode 100644 index 0000000000..e3bca4f966 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs @@ -0,0 +1,149 @@ +/* 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/. */ + +//! # Callback Interface definitions for a `ComponentInterface`. +//! +//! This module converts callback interface definitions from UDL into structures that +//! can be added to a `ComponentInterface`. A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! callback interface Example { +//! string hello(); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`CallbackInterface`] member being added to the resulting +//! [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # callback interface Example { +//! # string hello(); +//! # }; +//! # "##, "crate_name")?; +//! let callback = ci.get_callback_interface_definition("Example").unwrap(); +//! assert_eq!(callback.name(), "Example"); +//! assert_eq!(callback.methods()[0].name(), "hello"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use uniffi_meta::Checksum; + +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::object::Method; +use super::{AsType, Type, TypeIterator}; + +#[derive(Debug, Clone, Checksum)] +pub struct CallbackInterface { + pub(super) name: String, + pub(super) module_path: String, + pub(super) methods: Vec<Method>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_init_callback: FfiFunction, +} + +impl CallbackInterface { + pub fn new(name: String, module_path: String) -> CallbackInterface { + CallbackInterface { + name, + module_path, + methods: Default::default(), + ffi_init_callback: Default::default(), + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn methods(&self) -> Vec<&Method> { + self.methods.iter().collect() + } + + pub fn ffi_init_callback(&self) -> &FfiFunction { + &self.ffi_init_callback + } + + pub(super) fn derive_ffi_funcs(&mut self) { + self.ffi_init_callback.name = + uniffi_meta::init_callback_fn_symbol_name(&self.module_path, &self.name); + self.ffi_init_callback.arguments = vec![FfiArgument { + name: "callback_stub".to_string(), + type_: FfiType::ForeignCallback, + }]; + self.ffi_init_callback.return_type = None; + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.methods.iter().flat_map(Method::iter_types)) + } +} + +impl AsType for CallbackInterface { + fn as_type(&self) -> Type { + Type::CallbackInterface { + name: self.name.clone(), + module_path: self.module_path.clone(), + } + } +} + +#[cfg(test)] +mod test { + use super::super::ComponentInterface; + + #[test] + fn test_empty_interface() { + const UDL: &str = r#" + namespace test{}; + // Weird, but allowed. + callback interface Testing {}; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.callback_interface_definitions().len(), 1); + assert_eq!( + ci.get_callback_interface_definition("Testing") + .unwrap() + .methods() + .len(), + 0 + ); + } + + #[test] + fn test_multiple_interfaces() { + const UDL: &str = r#" + namespace test{}; + callback interface One { + void one(); + }; + callback interface Two { + u32 two(); + u64 too(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.callback_interface_definitions().len(), 2); + + let callbacks_one = ci.get_callback_interface_definition("One").unwrap(); + assert_eq!(callbacks_one.methods().len(), 1); + assert_eq!(callbacks_one.methods()[0].name(), "one"); + + let callbacks_two = ci.get_callback_interface_definition("Two").unwrap(); + assert_eq!(callbacks_two.methods().len(), 2); + assert_eq!(callbacks_two.methods()[0].name(), "two"); + assert_eq!(callbacks_two.methods()[1].name(), "too"); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs new file mode 100644 index 0000000000..82baf1dd50 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs @@ -0,0 +1,567 @@ +/* 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/. */ + +//! # Enum definitions for a `ComponentInterface`. +//! +//! This module converts enum definition from UDL into structures that can be +//! added to a `ComponentInterface`. A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! enum Example { +//! "one", +//! "two" +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Enum`] member being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # enum Example { +//! # "one", +//! # "two" +//! # }; +//! # "##, "crate_name")?; +//! let e = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(e.name(), "Example"); +//! assert_eq!(e.variants().len(), 2); +//! assert_eq!(e.variants()[0].name(), "one"); +//! assert_eq!(e.variants()[1].name(), "two"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Like in Rust, UniFFI enums can contain associated data, but this needs to be +//! declared with a different syntax in order to work within the restrictions of +//! WebIDL. A declaration like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Enum] +//! interface Example { +//! Zero(); +//! One(u32 first); +//! Two(u32 first, string second); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member whose variants have associated fields: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Enum] +//! # interface ExampleWithData { +//! # Zero(); +//! # One(u32 first); +//! # Two(u32 first, string second); +//! # }; +//! # "##, "crate_name")?; +//! let e = ci.get_enum_definition("ExampleWithData").unwrap(); +//! assert_eq!(e.name(), "ExampleWithData"); +//! assert_eq!(e.variants().len(), 3); +//! assert_eq!(e.variants()[0].name(), "Zero"); +//! assert_eq!(e.variants()[0].fields().len(), 0); +//! assert_eq!(e.variants()[1].name(), "One"); +//! assert_eq!(e.variants()[1].fields().len(), 1); +//! assert_eq!(e.variants()[1].fields()[0].name(), "first"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! # Enums are also used to represent error definitions for a `ComponentInterface`. +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Error] +//! enum Example { +//! "one", +//! "two" +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member with fieldless variants being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Error] +//! # enum Example { +//! # "one", +//! # "two" +//! # }; +//! # "##, "crate_name")?; +//! let err = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(err.name(), "Example"); +//! assert_eq!(err.variants().len(), 2); +//! assert_eq!(err.variants()[0].name(), "one"); +//! assert_eq!(err.variants()[1].name(), "two"); +//! assert_eq!(err.is_flat(), true); +//! assert!(ci.is_name_used_as_error(&err.name())); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Error] +//! interface Example { +//! one(i16 code); +//! two(string reason); +//! three(i32 x, i32 y); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member with variants that have fields being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Error] +//! # interface Example { +//! # one(); +//! # two(string reason); +//! # three(i32 x, i32 y); +//! # }; +//! # "##, "crate_name")?; +//! let err = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(err.name(), "Example"); +//! assert_eq!(err.variants().len(), 3); +//! assert_eq!(err.variants()[0].name(), "one"); +//! assert_eq!(err.variants()[1].name(), "two"); +//! assert_eq!(err.variants()[2].name(), "three"); +//! assert_eq!(err.variants()[0].fields().len(), 0); +//! assert_eq!(err.variants()[1].fields().len(), 1); +//! assert_eq!(err.variants()[1].fields()[0].name(), "reason"); +//! assert_eq!(err.variants()[2].fields().len(), 2); +//! assert_eq!(err.variants()[2].fields()[0].name(), "x"); +//! assert_eq!(err.variants()[2].fields()[1].name(), "y"); +//! assert_eq!(err.is_flat(), false); +//! assert!(ci.is_name_used_as_error(err.name())); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::Result; +use uniffi_meta::Checksum; + +use super::record::Field; +use super::{AsType, Type, TypeIterator}; + +/// Represents an enum with named variants, each of which may have named +/// and typed fields. +/// +/// Enums are passed across the FFI by serializing to a bytebuffer, with a +/// i32 indicating the variant followed by the serialization of each field. +#[derive(Debug, Clone, PartialEq, Eq, Checksum)] +pub struct Enum { + pub(super) name: String, + pub(super) module_path: String, + pub(super) variants: Vec<Variant>, + // NOTE: `flat` is a misleading name and to make matters worse, has 2 different + // meanings depending on the context :( + // * When used as part of Rust scaffolding generation, it means "is this enum + // used with an Error, and that error should we lowered to foreign bindings + // by converting each variant to a string and lowering the variant with that + // string?". In that context, it should probably be called `lowered_as_string` or + // similar. + // * When used as part of bindings generation, it means "does this enum have only + // variants with no associated data"? The foreign binding generators are likely + // to generate significantly different versions of the enum based on that value. + // + // The reason it is described as "has 2 different meanings" by way of example: + // * For an Enum described as being a flat error, but the enum itself has variants with data, + // `flat` will be `true` for the Enum when generating scaffolding and `false` when + // generating bindings. + // * For an Enum not used as an error but which has no variants with data, `flat` will be + // false when generating the scaffolding but `true` when generating bindings. + pub(super) flat: bool, +} + +impl Enum { + pub fn name(&self) -> &str { + &self.name + } + + pub fn variants(&self) -> &[Variant] { + &self.variants + } + + pub fn is_flat(&self) -> bool { + self.flat + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.variants.iter().flat_map(Variant::iter_types)) + } + + // Sadly can't use TryFrom due to the 'is_flat' complication. + pub fn try_from_meta(meta: uniffi_meta::EnumMetadata, flat: bool) -> Result<Self> { + // This is messy - error enums are considered "flat" if the user + // opted in via a special attribute, regardless of whether the enum + // is actually flat. + // Real enums are considered flat iff they are actually flat. + // We don't have that context here, so this is handled by our caller. + Ok(Self { + name: meta.name, + module_path: meta.module_path, + variants: meta + .variants + .into_iter() + .map(TryInto::try_into) + .collect::<Result<_>>()?, + flat, + }) + } +} + +impl AsType for Enum { + fn as_type(&self) -> Type { + Type::Enum { + name: self.name.clone(), + module_path: self.module_path.clone(), + } + } +} + +/// Represents an individual variant in an Enum. +/// +/// Each variant has a name and zero or more fields. +#[derive(Debug, Clone, Default, PartialEq, Eq, Checksum)] +pub struct Variant { + pub(super) name: String, + pub(super) fields: Vec<Field>, +} + +impl Variant { + pub fn name(&self) -> &str { + &self.name + } + + pub fn fields(&self) -> &[Field] { + &self.fields + } + + pub fn has_fields(&self) -> bool { + !self.fields.is_empty() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.fields.iter().flat_map(Field::iter_types)) + } +} + +impl TryFrom<uniffi_meta::VariantMetadata> for Variant { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::VariantMetadata) -> Result<Self> { + Ok(Self { + name: meta.name, + fields: meta + .fields + .into_iter() + .map(TryInto::try_into) + .collect::<Result<_>>()?, + }) + } +} + +#[cfg(test)] +mod test { + use super::super::{ComponentInterface, FfiType}; + use super::*; + + #[test] + fn test_duplicate_variants() { + const UDL: &str = r#" + namespace test{}; + // Weird, but currently allowed! + // We should probably disallow this... + enum Testing { "one", "two", "one" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants().len(), + 3 + ); + } + + #[test] + fn test_associated_data() { + const UDL: &str = r#" + namespace test { + void takes_an_enum(TestEnum e); + void takes_an_enum_with_data(TestEnumWithData ed); + TestEnum returns_an_enum(); + TestEnumWithData returns_an_enum_with_data(); + }; + + enum TestEnum { "one", "two" }; + + [Enum] + interface TestEnumWithData { + Zero(); + One(u32 first); + Two(u32 first, string second); + }; + + [Enum] + interface TestEnumWithoutData { + One(); + Two(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 3); + assert_eq!(ci.function_definitions().len(), 4); + + // The "flat" enum with no associated data. + let e = ci.get_enum_definition("TestEnum").unwrap(); + assert!(e.is_flat()); + assert_eq!(e.variants().len(), 2); + assert_eq!( + e.variants().iter().map(|v| v.name()).collect::<Vec<_>>(), + vec!["one", "two"] + ); + assert_eq!(e.variants()[0].fields().len(), 0); + assert_eq!(e.variants()[1].fields().len(), 0); + + // The enum with associated data. + let ed = ci.get_enum_definition("TestEnumWithData").unwrap(); + assert!(!ed.is_flat()); + assert_eq!(ed.variants().len(), 3); + assert_eq!( + ed.variants().iter().map(|v| v.name()).collect::<Vec<_>>(), + vec!["Zero", "One", "Two"] + ); + assert_eq!(ed.variants()[0].fields().len(), 0); + assert_eq!( + ed.variants()[1] + .fields() + .iter() + .map(|f| f.name()) + .collect::<Vec<_>>(), + vec!["first"] + ); + assert_eq!( + ed.variants()[1] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::<Vec<_>>(), + vec![Type::UInt32] + ); + assert_eq!( + ed.variants()[2] + .fields() + .iter() + .map(|f| f.name()) + .collect::<Vec<_>>(), + vec!["first", "second"] + ); + assert_eq!( + ed.variants()[2] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::<Vec<_>>(), + vec![Type::UInt32, Type::String] + ); + + // The enum declared via interface, but with no associated data. + let ewd = ci.get_enum_definition("TestEnumWithoutData").unwrap(); + assert_eq!(ewd.variants().len(), 2); + assert_eq!( + ewd.variants().iter().map(|v| v.name()).collect::<Vec<_>>(), + vec!["One", "Two"] + ); + assert_eq!(ewd.variants()[0].fields().len(), 0); + assert_eq!(ewd.variants()[1].fields().len(), 0); + + // Flat enums pass over the FFI as bytebuffers. + // (It might be nice to optimize these to pass as plain integers, but that's + // difficult atop the current factoring of `ComponentInterface` and friends). + let farg = ci.get_function_definition("takes_an_enum").unwrap(); + assert_eq!( + farg.arguments()[0].as_type(), + Type::Enum { + name: "TestEnum".into(), + module_path: "crate_name".into() + } + ); + assert_eq!( + farg.ffi_func().arguments()[0].type_(), + FfiType::RustBuffer(None) + ); + let fret = ci.get_function_definition("returns_an_enum").unwrap(); + assert!( + matches!(fret.return_type(), Some(Type::Enum { name, .. }) if name == "TestEnum" && !ci.is_name_used_as_error(name)) + ); + assert!(matches!( + fret.ffi_func().return_type(), + Some(FfiType::RustBuffer(None)) + )); + + // Enums with associated data pass over the FFI as bytebuffers. + let farg = ci + .get_function_definition("takes_an_enum_with_data") + .unwrap(); + assert_eq!( + farg.arguments()[0].as_type(), + Type::Enum { + name: "TestEnumWithData".into(), + module_path: "crate_name".into() + } + ); + assert_eq!( + farg.ffi_func().arguments()[0].type_(), + FfiType::RustBuffer(None) + ); + let fret = ci + .get_function_definition("returns_an_enum_with_data") + .unwrap(); + assert!( + matches!(fret.return_type(), Some(Type::Enum { name, .. }) if name == "TestEnumWithData" && !ci.is_name_used_as_error(name)) + ); + assert!(matches!( + fret.ffi_func().return_type(), + Some(FfiType::RustBuffer(None)) + )); + } + + // Tests for [Error], which are represented as `Enum` + #[test] + fn test_variants() { + const UDL: &str = r#" + namespace test{}; + [Error] + enum Testing { "one", "two", "three" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let error = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + error + .variants() + .iter() + .map(|v| v.name()) + .collect::<Vec<&str>>(), + vec!("one", "two", "three") + ); + assert!(error.is_flat()); + assert!(ci.is_name_used_as_error(&error.name)); + } + + #[test] + fn test_duplicate_error_variants() { + const UDL: &str = r#" + namespace test{}; + // Weird, but currently allowed! + // We should probably disallow this... + [Error] + enum Testing { "one", "two", "one" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants().len(), + 3 + ); + } + + #[test] + fn test_variant_data() { + const UDL: &str = r#" + namespace test{}; + + [Error] + interface Testing { + One(string reason); + Two(u8 code); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let error: &Enum = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + error + .variants() + .iter() + .map(|v| v.name()) + .collect::<Vec<&str>>(), + vec!("One", "Two") + ); + assert!(!error.is_flat()); + assert!(ci.is_name_used_as_error(&error.name)); + } + + #[test] + fn test_enum_variant_named_error() { + const UDL: &str = r#" + namespace test{}; + + [Enum] + interface Testing { + Normal(string first); + Error(string first); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let testing: &Enum = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + testing.variants()[0] + .fields() + .iter() + .map(|f| f.name()) + .collect::<Vec<_>>(), + vec!["first"] + ); + assert_eq!( + testing.variants()[0] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::<Vec<_>>(), + vec![Type::String] + ); + assert_eq!( + testing.variants()[1] + .fields() + .iter() + .map(|f| f.name()) + .collect::<Vec<_>>(), + vec!["first"] + ); + assert_eq!( + testing.variants()[1] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::<Vec<_>>(), + vec![Type::String] + ); + assert_eq!( + testing + .variants() + .iter() + .map(|v| v.name()) + .collect::<Vec<_>>(), + vec!["Normal", "Error"] + ); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs new file mode 100644 index 0000000000..d18aaf8262 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs @@ -0,0 +1,227 @@ +/* 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/. */ + +//! # Low-level typesystem for the FFI layer of a component interface. +//! +//! This module provides the "FFI-level" typesystem of a UniFFI Rust Component, that is, +//! the C-style functions and structs and primitive datatypes that are used to interface +//! between the Rust component code and the foreign-language bindings. +//! +//! These types are purely an implementation detail of UniFFI, so consumers shouldn't +//! need to know about them. But as a developer working on UniFFI itself, you're likely +//! to spend a lot of time thinking about how these low-level types are used to represent +//! the higher-level "interface types" from the [`Type`] enum. +/// Represents the restricted set of low-level types that can be used to construct +/// the C-style FFI layer between a rust component and its foreign language bindings. +/// +/// For the types that involve memory allocation, we make a distinction between +/// "owned" types (the recipient must free it, or pass it to someone else) and +/// "borrowed" types (the sender must keep it alive for the duration of the call). +use uniffi_meta::{ExternalKind, Type}; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum FfiType { + // N.B. there are no booleans at this layer, since they cause problems for JNA. + UInt8, + Int8, + UInt16, + Int16, + UInt32, + Int32, + UInt64, + Int64, + Float32, + Float64, + /// A `*const c_void` pointer to a rust-owned `Arc<T>`. + /// If you've got one of these, you must call the appropriate rust function to free it. + /// The templates will generate a unique `free` function for each T. + /// The inner string references the name of the `T` type. + RustArcPtr(String), + /// A byte buffer allocated by rust, and owned by whoever currently holds it. + /// If you've got one of these, you must either call the appropriate rust function to free it + /// or pass it to someone that will. + /// If the inner option is Some, it is the name of the external type. The bindings may need + /// to use this name to import the correct RustBuffer for that type. + RustBuffer(Option<String>), + /// A borrowed reference to some raw bytes owned by foreign language code. + /// The provider of this reference must keep it alive for the duration of the receiving call. + ForeignBytes, + /// Pointer to a callback function that handles all callbacks on the foreign language side. + ForeignCallback, + /// Pointer-sized opaque handle that represents a foreign executor. Foreign bindings can + /// either use an actual pointer or a usized integer. + ForeignExecutorHandle, + /// Pointer to the callback function that's invoked to schedule calls with a ForeignExecutor + ForeignExecutorCallback, + /// Pointer to a Rust future + RustFutureHandle, + /// Continuation function for a Rust future + RustFutureContinuationCallback, + RustFutureContinuationData, + // TODO: you can imagine a richer structural typesystem here, e.g. `Ref<String>` or something. + // We don't need that yet and it's possible we never will, so it isn't here for now. +} + +/// When passing data across the FFI, each `Type` value will be lowered into a corresponding +/// `FfiType` value. This conversion tells you which one. +/// +/// Note that the conversion is one-way - given an FfiType, it is not in general possible to +/// tell what the corresponding Type is that it's being used to represent. +impl From<&Type> for FfiType { + fn from(t: &Type) -> FfiType { + match t { + // Types that are the same map to themselves, naturally. + Type::UInt8 => FfiType::UInt8, + Type::Int8 => FfiType::Int8, + Type::UInt16 => FfiType::UInt16, + Type::Int16 => FfiType::Int16, + Type::UInt32 => FfiType::UInt32, + Type::Int32 => FfiType::Int32, + Type::UInt64 => FfiType::UInt64, + Type::Int64 => FfiType::Int64, + Type::Float32 => FfiType::Float32, + Type::Float64 => FfiType::Float64, + // Booleans lower into an Int8, to work around a bug in JNA. + Type::Boolean => FfiType::Int8, + // Strings are always owned rust values. + // We might add a separate type for borrowed strings in future. + Type::String => FfiType::RustBuffer(None), + // Byte strings are also always owned rust values. + // We might add a separate type for borrowed byte strings in future as well. + Type::Bytes => FfiType::RustBuffer(None), + // Objects are pointers to an Arc<> + Type::Object { name, .. } => FfiType::RustArcPtr(name.to_owned()), + // Callback interfaces are passed as opaque integer handles. + Type::CallbackInterface { .. } => FfiType::UInt64, + Type::ForeignExecutor => FfiType::ForeignExecutorHandle, + // Other types are serialized into a bytebuffer and deserialized on the other side. + Type::Enum { .. } + | Type::Record { .. } + | Type::Optional { .. } + | Type::Sequence { .. } + | Type::Map { .. } + | Type::Timestamp + | Type::Duration => FfiType::RustBuffer(None), + Type::External { + name, + kind: ExternalKind::Interface, + .. + } => FfiType::RustArcPtr(name.clone()), + Type::External { + name, + kind: ExternalKind::DataClass, + .. + } => FfiType::RustBuffer(Some(name.clone())), + Type::Custom { builtin, .. } => FfiType::from(builtin.as_ref()), + } + } +} + +// Needed for rust scaffolding askama template +impl From<Type> for FfiType { + fn from(ty: Type) -> Self { + (&ty).into() + } +} + +impl From<&&Type> for FfiType { + fn from(ty: &&Type) -> Self { + (*ty).into() + } +} + +/// Represents an "extern C"-style function that will be part of the FFI. +/// +/// These can't be declared explicitly in the UDL, but rather, are derived automatically +/// from the high-level interface. Each callable thing in the component API will have a +/// corresponding `FfiFunction` through which it can be invoked, and UniFFI also provides +/// some built-in `FfiFunction` helpers for use in the foreign language bindings. +#[derive(Debug, Clone)] +pub struct FfiFunction { + pub(super) name: String, + pub(super) is_async: bool, + pub(super) arguments: Vec<FfiArgument>, + pub(super) return_type: Option<FfiType>, + pub(super) has_rust_call_status_arg: bool, + /// Used by C# generator to differentiate the free function and call it with void* + /// instead of C# `SafeHandle` type. See <https://github.com/mozilla/uniffi-rs/pull/1488>. + pub(super) is_object_free_function: bool, +} + +impl FfiFunction { + pub fn name(&self) -> &str { + &self.name + } + + pub fn is_async(&self) -> bool { + self.is_async + } + + pub fn arguments(&self) -> Vec<&FfiArgument> { + self.arguments.iter().collect() + } + + pub fn return_type(&self) -> Option<&FfiType> { + self.return_type.as_ref() + } + + pub fn has_rust_call_status_arg(&self) -> bool { + self.has_rust_call_status_arg + } + + pub fn is_object_free_function(&self) -> bool { + self.is_object_free_function + } + + pub fn init( + &mut self, + return_type: Option<FfiType>, + args: impl IntoIterator<Item = FfiArgument>, + ) { + self.arguments = args.into_iter().collect(); + if self.is_async() { + self.return_type = Some(FfiType::RustFutureHandle); + self.has_rust_call_status_arg = false; + } else { + self.return_type = return_type; + } + } +} + +impl Default for FfiFunction { + fn default() -> Self { + Self { + name: "".into(), + is_async: false, + arguments: Vec::new(), + return_type: None, + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } +} + +/// Represents an argument to an FFI function. +/// +/// Each argument has a name and a type. +#[derive(Debug, Clone)] +pub struct FfiArgument { + pub(super) name: String, + pub(super) type_: FfiType, +} + +impl FfiArgument { + pub fn name(&self) -> &str { + &self.name + } + pub fn type_(&self) -> FfiType { + self.type_.clone() + } +} + +#[cfg(test)] +mod test { + // There's not really much to test here to be honest, + // it's mostly type declarations. +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/function.rs b/third_party/rust/uniffi_bindgen/src/interface/function.rs new file mode 100644 index 0000000000..2d18288c1c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/function.rs @@ -0,0 +1,367 @@ +/* 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/. */ + +//! # Function definitions for a `ComponentInterface`. +//! +//! This module converts function definitions from UDL into structures that +//! can be added to a `ComponentInterface`. A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! namespace example { +//! string hello(); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Function`] member being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # use uniffi_bindgen::interface::Type; +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example { +//! # string hello(); +//! # }; +//! # "##, "crate_name")?; +//! let func = ci.get_function_definition("hello").unwrap(); +//! assert_eq!(func.name(), "hello"); +//! assert!(matches!(func.return_type(), Some(Type::String))); +//! assert_eq!(func.arguments().len(), 0); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::Result; + +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::{AsType, ComponentInterface, Literal, ObjectImpl, Type, TypeIterator}; +use uniffi_meta::Checksum; + +/// Represents a standalone function. +/// +/// Each `Function` corresponds to a standalone function in the rust module, +/// and has a corresponding standalone function in the foreign language bindings. +/// +/// In the FFI, this will be a standalone function with appropriately lowered types. +#[derive(Debug, Clone, Checksum)] +pub struct Function { + pub(super) name: String, + pub(super) module_path: String, + pub(super) is_async: bool, + pub(super) arguments: Vec<Argument>, + pub(super) return_type: Option<Type>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_func: FfiFunction, + pub(super) throws: Option<Type>, + pub(super) checksum_fn_name: String, + // Force a checksum value, or we'll fallback to the trait. + #[checksum_ignore] + pub(super) checksum: Option<u16>, +} + +impl Function { + pub fn name(&self) -> &str { + &self.name + } + + pub fn is_async(&self) -> bool { + self.is_async + } + + pub fn arguments(&self) -> Vec<&Argument> { + self.arguments.iter().collect() + } + + pub fn full_arguments(&self) -> Vec<Argument> { + self.arguments.to_vec() + } + + pub fn return_type(&self) -> Option<&Type> { + self.return_type.as_ref() + } + + pub fn ffi_func(&self) -> &FfiFunction { + &self.ffi_func + } + + pub fn checksum_fn_name(&self) -> &str { + &self.checksum_fn_name + } + + pub fn checksum(&self) -> u16 { + self.checksum.unwrap_or_else(|| uniffi_meta::checksum(self)) + } + + pub fn throws(&self) -> bool { + self.throws.is_some() + } + + pub fn throws_name(&self) -> Option<&str> { + super::throws_name(&self.throws) + } + + pub fn throws_type(&self) -> Option<&Type> { + self.throws.as_ref() + } + + pub fn derive_ffi_func(&mut self) -> Result<()> { + assert!(!self.ffi_func.name.is_empty()); + self.ffi_func.init( + self.return_type.as_ref().map(Into::into), + self.arguments.iter().map(Into::into), + ); + Ok(()) + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + self.arguments + .iter() + .flat_map(Argument::iter_types) + .chain(self.return_type.iter().flat_map(Type::iter_types)), + ) + } +} + +impl From<uniffi_meta::FnParamMetadata> for Argument { + fn from(meta: uniffi_meta::FnParamMetadata) -> Self { + Argument { + name: meta.name, + type_: meta.ty, + by_ref: meta.by_ref, + optional: meta.optional, + default: meta.default, + } + } +} + +impl From<uniffi_meta::FnMetadata> for Function { + fn from(meta: uniffi_meta::FnMetadata) -> Self { + let ffi_name = meta.ffi_symbol_name(); + let checksum_fn_name = meta.checksum_symbol_name(); + let is_async = meta.is_async; + let return_type = meta.return_type.map(Into::into); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + + let ffi_func = FfiFunction { + name: ffi_name, + is_async, + ..FfiFunction::default() + }; + + Self { + name: meta.name, + module_path: meta.module_path, + is_async, + arguments, + return_type, + ffi_func, + throws: meta.throws, + checksum_fn_name, + checksum: meta.checksum, + } + } +} + +/// Represents an argument to a function/constructor/method call. +/// +/// Each argument has a name and a type, along with some optional metadata. +#[derive(Debug, Clone, Checksum)] +pub struct Argument { + pub(super) name: String, + pub(super) type_: Type, + pub(super) by_ref: bool, + pub(super) optional: bool, + pub(super) default: Option<Literal>, +} + +impl Argument { + pub fn name(&self) -> &str { + &self.name + } + + pub fn by_ref(&self) -> bool { + self.by_ref + } + + pub fn is_trait_ref(&self) -> bool { + matches!(&self.type_, Type::Object { imp, .. } if *imp == ObjectImpl::Trait) + } + + pub fn default_value(&self) -> Option<&Literal> { + self.default.as_ref() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + self.type_.iter_types() + } +} + +impl AsType for Argument { + fn as_type(&self) -> Type { + self.type_.clone() + } +} + +impl From<&Argument> for FfiArgument { + fn from(a: &Argument) -> FfiArgument { + FfiArgument { + name: a.name.clone(), + type_: (&a.type_).into(), + } + } +} + +/// Combines the return and throws type of a function/method +#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)] +pub struct ResultType { + pub return_type: Option<Type>, + pub throws_type: Option<Type>, +} + +impl ResultType { + /// Get the `T` parameters for the `FutureCallback<T>` for this ResultType + pub fn future_callback_param(&self) -> FfiType { + match &self.return_type { + Some(t) => t.into(), + None => FfiType::UInt8, + } + } +} + +/// Implemented by function-like types (Function, Method, Constructor) +pub trait Callable { + fn arguments(&self) -> Vec<&Argument>; + fn return_type(&self) -> Option<Type>; + fn throws_type(&self) -> Option<Type>; + fn is_async(&self) -> bool; + fn result_type(&self) -> ResultType { + ResultType { + return_type: self.return_type(), + throws_type: self.throws_type(), + } + } + + // Quick way to get the rust future scaffolding function that corresponds to our return type. + + fn ffi_rust_future_poll(&self, ci: &ComponentInterface) -> String { + ci.ffi_rust_future_poll(self.return_type().map(Into::into)) + .name() + .to_owned() + } + + fn ffi_rust_future_cancel(&self, ci: &ComponentInterface) -> String { + ci.ffi_rust_future_cancel(self.return_type().map(Into::into)) + .name() + .to_owned() + } + + fn ffi_rust_future_complete(&self, ci: &ComponentInterface) -> String { + ci.ffi_rust_future_complete(self.return_type().map(Into::into)) + .name() + .to_owned() + } + + fn ffi_rust_future_free(&self, ci: &ComponentInterface) -> String { + ci.ffi_rust_future_free(self.return_type().map(Into::into)) + .name() + .to_owned() + } +} + +impl Callable for Function { + fn arguments(&self) -> Vec<&Argument> { + self.arguments() + } + + fn return_type(&self) -> Option<Type> { + self.return_type().cloned() + } + + fn throws_type(&self) -> Option<Type> { + self.throws_type().cloned() + } + + fn is_async(&self) -> bool { + self.is_async + } +} + +// Needed because Askama likes to add extra refs to variables +impl<T: Callable> Callable for &T { + fn arguments(&self) -> Vec<&Argument> { + (*self).arguments() + } + + fn return_type(&self) -> Option<Type> { + (*self).return_type() + } + + fn throws_type(&self) -> Option<Type> { + (*self).throws_type() + } + + fn is_async(&self) -> bool { + (*self).is_async() + } +} + +#[cfg(test)] +mod test { + use super::super::ComponentInterface; + use super::*; + + #[test] + fn test_minimal_and_rich_function() -> Result<()> { + let ci = ComponentInterface::from_webidl( + r#" + namespace test { + void minimal(); + [Throws=TestError] + sequence<string?> rich(u32 arg1, TestDict arg2); + }; + [Error] + enum TestError { "err" }; + dictionary TestDict { + u32 field; + }; + "#, + "crate_name", + )?; + + let func1 = ci.get_function_definition("minimal").unwrap(); + assert_eq!(func1.name(), "minimal"); + assert!(func1.return_type().is_none()); + assert!(func1.throws_type().is_none()); + assert_eq!(func1.arguments().len(), 0); + + let func2 = ci.get_function_definition("rich").unwrap(); + assert_eq!(func2.name(), "rich"); + assert_eq!( + func2.return_type().unwrap(), + &Type::Sequence { + inner_type: Box::new(Type::Optional { + inner_type: Box::new(Type::String) + }) + } + ); + assert!( + matches!(func2.throws_type(), Some(Type::Enum { name, .. }) if name == "TestError" && ci.is_name_used_as_error(name)) + ); + assert_eq!(func2.arguments().len(), 2); + assert_eq!(func2.arguments()[0].name(), "arg1"); + assert_eq!(func2.arguments()[0].as_type(), Type::UInt32); + assert_eq!(func2.arguments()[1].name(), "arg2"); + assert!( + matches!(func2.arguments()[1].as_type(), Type::Record { name, .. } if name == "TestDict") + ); + Ok(()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/mod.rs b/third_party/rust/uniffi_bindgen/src/interface/mod.rs new file mode 100644 index 0000000000..8e4df2149b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/mod.rs @@ -0,0 +1,1234 @@ +/* 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/. */ + +//! # Component Interface Definition. +//! +//! This module provides an abstract representation of the interface provided by a UniFFI Rust Component, +//! in high-level terms suitable for translation into target consumer languages such as Kotlin +//! and Swift. It also provides facilities for parsing a WebIDL interface definition file into such a +//! representation. +//! +//! The entrypoint to this crate is the `ComponentInterface` struct, which holds a complete definition +//! of the interface provided by a component, in two parts: +//! +//! * The high-level consumer API, in terms of objects and records and methods and so-on +//! * The low-level FFI contract through which the foreign language code can call into Rust. +//! +//! That's really the key concept of this crate so it's worth repeating: a `ComponentInterface` completely +//! defines the shape and semantics of an interface between the Rust-based implementation of a component +//! and its foreign language consumers, including details like: +//! +//! * The names of all symbols in the compiled object file +//! * The type and arity of all exported functions +//! * The layout and conventions used for all arguments and return types +//! +//! If you have a dynamic library compiled from a Rust Component using this crate, and a foreign +//! language binding generated from the same `ComponentInterface` using the same version of this +//! module, then there should be no opportunities for them to disagree on how the two sides should +//! interact. +//! +//! General and incomplete TODO list for this thing: +//! +//! * It should prevent user error and the possibility of generating bad code by doing (at least) +//! the following checks: +//! * No duplicate names (types, methods, args, etc) +//! * No shadowing of builtin names, or names we use in code generation +//! We expect that if the user actually does one of these things, then they *should* get a compile +//! error when trying to build the component, because the codegen will be invalid. But we can't +//! guarantee that there's not some edge-case where it produces valid-but-incorrect code. +//! +//! * There is a *lot* of cloning going on, in the spirit of "first make it work". There's probably +//! a good opportunity here for e.g. interned strings, but we're nowhere near the point were we need +//! that kind of optimization just yet. +//! +//! * Error messages and general developer experience leave a lot to be desired. + +use std::{ + collections::{btree_map::Entry, BTreeMap, BTreeSet, HashSet}, + iter, +}; + +use anyhow::{anyhow, bail, ensure, Result}; + +pub mod universe; +pub use uniffi_meta::{AsType, ExternalKind, ObjectImpl, Type}; +use universe::{TypeIterator, TypeUniverse}; + +mod callbacks; +pub use callbacks::CallbackInterface; +mod enum_; +pub use enum_::{Enum, Variant}; +mod function; +pub use function::{Argument, Callable, Function, ResultType}; +mod object; +pub use object::{Constructor, Method, Object, UniffiTrait}; +mod record; +pub use record::{Field, Record}; + +pub mod ffi; +pub use ffi::{FfiArgument, FfiFunction, FfiType}; +pub use uniffi_meta::Radix; +use uniffi_meta::{ + ConstructorMetadata, LiteralMetadata, NamespaceMetadata, ObjectMetadata, TraitMethodMetadata, + UniffiTraitMetadata, UNIFFI_CONTRACT_VERSION, +}; +pub type Literal = LiteralMetadata; + +/// The main public interface for this module, representing the complete details of an interface exposed +/// by a rust component and the details of consuming it via an extern-C FFI layer. +#[derive(Debug, Default)] +pub struct ComponentInterface { + /// All of the types used in the interface. + // We can't checksum `self.types`, but its contents are implied by the other fields + // anyway, so it's safe to ignore it. + pub(super) types: TypeUniverse, + /// The high-level API provided by the component. + enums: BTreeMap<String, Enum>, + records: BTreeMap<String, Record>, + functions: Vec<Function>, + objects: Vec<Object>, + callback_interfaces: Vec<CallbackInterface>, + // Type names which were seen used as an error. + errors: HashSet<String>, + // Types which were seen used as callback interface error. + callback_interface_throws_types: BTreeSet<Type>, +} + +impl ComponentInterface { + pub fn new(crate_name: &str) -> Self { + assert!(!crate_name.is_empty()); + Self { + types: TypeUniverse::new(NamespaceMetadata { + crate_name: crate_name.to_string(), + ..Default::default() + }), + ..Default::default() + } + } + + /// Parse a `ComponentInterface` from a string containing a WebIDL definition. + pub fn from_webidl(idl: &str, module_path: &str) -> Result<Self> { + ensure!( + !module_path.is_empty(), + "you must specify a valid crate name" + ); + let group = uniffi_udl::parse_udl(idl, module_path)?; + Self::from_metadata(group) + } + + /// Create a `ComponentInterface` from a `MetadataGroup` + /// Public so that external binding generators can use it. + pub fn from_metadata(group: uniffi_meta::MetadataGroup) -> Result<Self> { + let mut ci = Self { + types: TypeUniverse::new(group.namespace.clone()), + ..Default::default() + }; + ci.add_metadata(group)?; + Ok(ci) + } + + /// Add a metadata group to a `ComponentInterface`. + pub fn add_metadata(&mut self, group: uniffi_meta::MetadataGroup) -> Result<()> { + if self.types.namespace.name.is_empty() { + self.types.namespace = group.namespace.clone(); + } else if self.types.namespace != group.namespace { + bail!( + "Namespace mismatch: {:?} - {:?}", + group.namespace, + self.types.namespace + ); + } + // Unconditionally add the String type, which is used by the panic handling + self.types.add_known_type(&uniffi_meta::Type::String)?; + crate::macro_metadata::add_group_to_ci(self, group)?; + Ok(()) + } + + /// The string namespace within which this API should be presented to the caller. + /// + /// This string would typically be used to prefix function names in the FFI, to build + /// a package or module name for the foreign language, etc. + pub fn namespace(&self) -> &str { + &self.types.namespace.name + } + + pub fn uniffi_contract_version(&self) -> u32 { + // This is set by the scripts in the version-mismatch fixture + let force_version = std::env::var("UNIFFI_FORCE_CONTRACT_VERSION"); + match force_version { + Ok(v) if !v.is_empty() => v.parse().unwrap(), + _ => UNIFFI_CONTRACT_VERSION, + } + } + + /// Get the definitions for every Enum type in the interface. + pub fn enum_definitions(&self) -> impl Iterator<Item = &Enum> { + self.enums.values() + } + + /// Get an Enum definition by name, or None if no such Enum is defined. + pub fn get_enum_definition(&self, name: &str) -> Option<&Enum> { + self.enums.get(name) + } + + /// Get the definitions for every Record type in the interface. + pub fn record_definitions(&self) -> impl Iterator<Item = &Record> { + self.records.values() + } + + /// Get a Record definition by name, or None if no such Record is defined. + pub fn get_record_definition(&self, name: &str) -> Option<&Record> { + self.records.get(name) + } + + /// Get the definitions for every Function in the interface. + pub fn function_definitions(&self) -> &[Function] { + &self.functions + } + + /// Get a Function definition by name, or None if no such Function is defined. + pub fn get_function_definition(&self, name: &str) -> Option<&Function> { + // TODO: probably we could store these internally in a HashMap to make this easier? + self.functions.iter().find(|f| f.name == name) + } + + /// Get the definitions for every Object type in the interface. + pub fn object_definitions(&self) -> &[Object] { + &self.objects + } + + /// Get an Object definition by name, or None if no such Object is defined. + pub fn get_object_definition(&self, name: &str) -> Option<&Object> { + // TODO: probably we could store these internally in a HashMap to make this easier? + self.objects.iter().find(|o| o.name == name) + } + + /// Get the definitions for every Callback Interface type in the interface. + pub fn callback_interface_definitions(&self) -> &[CallbackInterface] { + &self.callback_interfaces + } + + /// Get a Callback interface definition by name, or None if no such interface is defined. + pub fn get_callback_interface_definition(&self, name: &str) -> Option<&CallbackInterface> { + // TODO: probably we could store these internally in a HashMap to make this easier? + self.callback_interfaces.iter().find(|o| o.name == name) + } + + /// Get the definitions for every Method type in the interface. + pub fn iter_callables(&self) -> impl Iterator<Item = &dyn Callable> { + // Each of the `as &dyn Callable` casts is a trivial cast, but it seems like the clearest + // way to express the logic in the current Rust + #[allow(trivial_casts)] + self.function_definitions() + .iter() + .map(|f| f as &dyn Callable) + .chain(self.objects.iter().flat_map(|o| { + o.constructors() + .into_iter() + .map(|c| c as &dyn Callable) + .chain(o.methods().into_iter().map(|m| m as &dyn Callable)) + })) + } + + /// Should we generate read (and lift) functions for errors? + /// + /// This is a workaround for the fact that lower/write can't be generated for some errors, + /// specifically errors that are defined as flat in the UDL, but actually have fields in the + /// Rust source. + pub fn should_generate_error_read(&self, e: &Enum) -> bool { + // We can and should always generate read() methods for fielded errors + let fielded = !e.is_flat(); + // For flat errors, we should only generate read() methods if we need them to support + // callback interface errors + let used_in_callback_interface = self + .callback_interface_definitions() + .iter() + .flat_map(|cb| cb.methods()) + .any(|m| m.throws_type() == Some(&e.as_type())); + + self.is_name_used_as_error(&e.name) && (fielded || used_in_callback_interface) + } + + /// Get details about all `Type::External` types. + /// Returns an iterator of (name, crate_name, kind) + pub fn iter_external_types( + &self, + ) -> impl Iterator<Item = (&String, String, ExternalKind, bool)> { + self.types.iter_known_types().filter_map(|t| match t { + Type::External { + name, + module_path, + kind, + tagged, + .. + } => Some(( + name, + module_path.split("::").next().unwrap().to_string(), + *kind, + *tagged, + )), + _ => None, + }) + } + + /// Get details about all `Type::Custom` types + pub fn iter_custom_types(&self) -> impl Iterator<Item = (&String, &Type)> { + self.types.iter_known_types().filter_map(|t| match t { + Type::Custom { name, builtin, .. } => Some((name, &**builtin)), + _ => None, + }) + } + + /// Iterate over all known types in the interface. + pub fn iter_types(&self) -> impl Iterator<Item = &Type> { + self.types.iter_known_types() + } + + /// Get a specific type + pub fn get_type(&self, name: &str) -> Option<Type> { + self.types.get_type_definition(name) + } + + /// Iterate over all types contained in the given item. + /// + /// This method uses `iter_types` to iterate over the types contained within the given type, + /// but additionally recurses into the definition of user-defined types like records and enums + /// to yield the types that *they* contain. + fn iter_types_in_item<'a>(&'a self, item: &'a Type) -> impl Iterator<Item = &'a Type> + 'a { + RecursiveTypeIterator::new(self, item) + } + + /// Check whether the given item contains any (possibly nested) Type::Object references. + /// + /// This is important to know in language bindings that cannot integrate object types + /// tightly with the host GC, and hence need to perform manual destruction of objects. + pub fn item_contains_object_references(&self, item: &Type) -> bool { + self.iter_types_in_item(item) + .any(|t| matches!(t, Type::Object { .. })) + } + + /// Check whether the given item contains any (possibly nested) unsigned types + pub fn item_contains_unsigned_types(&self, item: &Type) -> bool { + self.iter_types_in_item(item) + .any(|t| matches!(t, Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64)) + } + + /// Check whether the interface contains any optional types + pub fn contains_optional_types(&self) -> bool { + self.types + .iter_known_types() + .any(|t| matches!(t, Type::Optional { .. })) + } + + /// Check whether the interface contains any sequence types + pub fn contains_sequence_types(&self) -> bool { + self.types + .iter_known_types() + .any(|t| matches!(t, Type::Sequence { .. })) + } + + /// Check whether the interface contains any map types + pub fn contains_map_types(&self) -> bool { + self.types + .iter_known_types() + .any(|t| matches!(t, Type::Map { .. })) + } + + // The namespace to use in crate-level FFI function definitions. Not used as the ffi + // namespace for types - each type has its own `module_path` which is used for them. + fn ffi_namespace(&self) -> &str { + &self.types.namespace.crate_name + } + + /// Builtin FFI function to get the current contract version + /// This is needed so that the foreign language bindings can check that they are using the same + /// ABI as the scaffolding + pub fn ffi_uniffi_contract_version(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_uniffi_contract_version", self.ffi_namespace()), + is_async: false, + arguments: vec![], + return_type: Some(FfiType::UInt32), + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + /// Builtin FFI function for allocating a new `RustBuffer`. + /// This is needed so that the foreign language bindings can create buffers in which to pass + /// complex data types across the FFI. + pub fn ffi_rustbuffer_alloc(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_rustbuffer_alloc", self.ffi_namespace()), + is_async: false, + arguments: vec![FfiArgument { + name: "size".to_string(), + type_: FfiType::Int32, + }], + return_type: Some(FfiType::RustBuffer(None)), + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function for copying foreign-owned bytes + /// This is needed so that the foreign language bindings can create buffers in which to pass + /// complex data types across the FFI. + pub fn ffi_rustbuffer_from_bytes(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_rustbuffer_from_bytes", self.ffi_namespace()), + is_async: false, + arguments: vec![FfiArgument { + name: "bytes".to_string(), + type_: FfiType::ForeignBytes, + }], + return_type: Some(FfiType::RustBuffer(None)), + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function for freeing a `RustBuffer`. + /// This is needed so that the foreign language bindings can free buffers in which they received + /// complex data types returned across the FFI. + pub fn ffi_rustbuffer_free(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_rustbuffer_free", self.ffi_namespace()), + is_async: false, + arguments: vec![FfiArgument { + name: "buf".to_string(), + type_: FfiType::RustBuffer(None), + }], + return_type: None, + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function for reserving extra space in a `RustBuffer`. + /// This is needed so that the foreign language bindings can grow buffers used for passing + /// complex data types across the FFI. + pub fn ffi_rustbuffer_reserve(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_rustbuffer_reserve", self.ffi_namespace()), + is_async: false, + arguments: vec![ + FfiArgument { + name: "buf".to_string(), + type_: FfiType::RustBuffer(None), + }, + FfiArgument { + name: "additional".to_string(), + type_: FfiType::Int32, + }, + ], + return_type: Some(FfiType::RustBuffer(None)), + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function to set the Rust Future continuation callback + pub fn ffi_rust_future_continuation_callback_set(&self) -> FfiFunction { + FfiFunction { + name: format!( + "ffi_{}_rust_future_continuation_callback_set", + self.ffi_namespace() + ), + arguments: vec![FfiArgument { + name: "callback".to_owned(), + type_: FfiType::RustFutureContinuationCallback, + }], + return_type: None, + is_async: false, + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + /// Builtin FFI function to poll a Rust future. + pub fn ffi_rust_future_poll(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { + FfiFunction { + name: self.rust_future_ffi_fn_name("rust_future_poll", return_ffi_type), + is_async: false, + arguments: vec![ + FfiArgument { + name: "handle".to_owned(), + type_: FfiType::RustFutureHandle, + }, + // Data to pass to the continuation + FfiArgument { + name: "uniffi_callback".to_owned(), + type_: FfiType::RustFutureContinuationData, + }, + ], + return_type: None, + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + /// Builtin FFI function to complete a Rust future and get it's result. + /// + /// We generate one of these for each FFI return type. + pub fn ffi_rust_future_complete(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { + FfiFunction { + name: self.rust_future_ffi_fn_name("rust_future_complete", return_ffi_type.clone()), + is_async: false, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::RustFutureHandle, + }], + return_type: return_ffi_type, + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function for cancelling a Rust Future + pub fn ffi_rust_future_cancel(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { + FfiFunction { + name: self.rust_future_ffi_fn_name("rust_future_cancel", return_ffi_type), + is_async: false, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::RustFutureHandle, + }], + return_type: None, + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + /// Builtin FFI function for freeing a Rust Future + pub fn ffi_rust_future_free(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { + FfiFunction { + name: self.rust_future_ffi_fn_name("rust_future_free", return_ffi_type), + is_async: false, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::RustFutureHandle, + }], + return_type: None, + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + fn rust_future_ffi_fn_name(&self, base_name: &str, return_ffi_type: Option<FfiType>) -> String { + let namespace = self.ffi_namespace(); + match return_ffi_type { + Some(t) => match t { + FfiType::UInt8 => format!("ffi_{namespace}_{base_name}_u8"), + FfiType::Int8 => format!("ffi_{namespace}_{base_name}_i8"), + FfiType::UInt16 => format!("ffi_{namespace}_{base_name}_u16"), + FfiType::Int16 => format!("ffi_{namespace}_{base_name}_i16"), + FfiType::UInt32 => format!("ffi_{namespace}_{base_name}_u32"), + FfiType::Int32 => format!("ffi_{namespace}_{base_name}_i32"), + FfiType::UInt64 => format!("ffi_{namespace}_{base_name}_u64"), + FfiType::Int64 => format!("ffi_{namespace}_{base_name}_i64"), + FfiType::Float32 => format!("ffi_{namespace}_{base_name}_f32"), + FfiType::Float64 => format!("ffi_{namespace}_{base_name}_f64"), + FfiType::RustArcPtr(_) => format!("ffi_{namespace}_{base_name}_pointer"), + FfiType::RustBuffer(_) => format!("ffi_{namespace}_{base_name}_rust_buffer"), + _ => unimplemented!("FFI return type: {t:?}"), + }, + None => format!("ffi_{namespace}_{base_name}_void"), + } + } + + /// Does this interface contain async functions? + pub fn has_async_fns(&self) -> bool { + self.iter_ffi_function_definitions().any(|f| f.is_async()) + } + + /// Iterate over `T` parameters of the `FutureCallback<T>` callbacks in this interface + pub fn iter_future_callback_params(&self) -> impl Iterator<Item = FfiType> { + let unique_results = self + .iter_callables() + .map(|c| c.result_type().future_callback_param()) + .collect::<BTreeSet<_>>(); + unique_results.into_iter() + } + + /// Iterate over return/throws types for async functions + pub fn iter_async_result_types(&self) -> impl Iterator<Item = ResultType> { + let unique_results = self + .iter_callables() + .map(|c| c.result_type()) + .collect::<BTreeSet<_>>(); + unique_results.into_iter() + } + + /// List the definitions of all FFI functions in the interface. + /// + /// The set of FFI functions is derived automatically from the set of higher-level types + /// along with the builtin FFI helper functions. + pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> + '_ { + self.iter_user_ffi_function_definitions() + .cloned() + .chain(self.iter_rust_buffer_ffi_function_definitions()) + .chain(self.iter_futures_ffi_function_definitons()) + .chain(self.iter_checksum_ffi_functions()) + .chain(self.ffi_foreign_executor_callback_set()) + .chain([self.ffi_uniffi_contract_version()]) + } + + /// Alternate version of iter_ffi_function_definitions for languages that don't support async + pub fn iter_ffi_function_definitions_non_async( + &self, + ) -> impl Iterator<Item = FfiFunction> + '_ { + self.iter_user_ffi_function_definitions() + .cloned() + .chain(self.iter_rust_buffer_ffi_function_definitions()) + .chain(self.iter_checksum_ffi_functions()) + .chain([self.ffi_uniffi_contract_version()]) + } + + /// List all FFI functions definitions for user-defined interfaces + /// + /// This includes FFI functions for: + /// - Top-level functions + /// - Object methods + /// - Callback interfaces + pub fn iter_user_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> + '_ { + iter::empty() + .chain( + self.objects + .iter() + .flat_map(|obj| obj.iter_ffi_function_definitions()), + ) + .chain( + self.callback_interfaces + .iter() + .map(|cb| cb.ffi_init_callback()), + ) + .chain(self.functions.iter().map(|f| &f.ffi_func)) + } + + /// List all FFI functions definitions for RustBuffer functionality. + pub fn iter_rust_buffer_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> { + [ + self.ffi_rustbuffer_alloc(), + self.ffi_rustbuffer_from_bytes(), + self.ffi_rustbuffer_free(), + self.ffi_rustbuffer_reserve(), + ] + .into_iter() + } + + /// List all FFI functions definitions for async functionality. + pub fn iter_futures_ffi_function_definitons(&self) -> impl Iterator<Item = FfiFunction> + '_ { + let all_possible_return_ffi_types = [ + Some(FfiType::UInt8), + Some(FfiType::Int8), + Some(FfiType::UInt16), + Some(FfiType::Int16), + Some(FfiType::UInt32), + Some(FfiType::Int32), + Some(FfiType::UInt64), + Some(FfiType::Int64), + Some(FfiType::Float32), + Some(FfiType::Float64), + // RustBuffer and RustArcPtr have an inner field which doesn't affect the rust future + // complete scaffolding function, so we just use a placeholder value here. + Some(FfiType::RustArcPtr("".to_owned())), + Some(FfiType::RustBuffer(None)), + None, + ]; + + iter::once(self.ffi_rust_future_continuation_callback_set()).chain( + all_possible_return_ffi_types + .into_iter() + .flat_map(|return_type| { + [ + self.ffi_rust_future_poll(return_type.clone()), + self.ffi_rust_future_cancel(return_type.clone()), + self.ffi_rust_future_free(return_type.clone()), + self.ffi_rust_future_complete(return_type), + ] + }), + ) + } + + /// The ffi_foreign_executor_callback_set FFI function + /// + /// We only include this in the FFI if the `ForeignExecutor` type is actually used + pub fn ffi_foreign_executor_callback_set(&self) -> Option<FfiFunction> { + if self.types.contains(&Type::ForeignExecutor) { + Some(FfiFunction { + name: format!("ffi_{}_foreign_executor_callback_set", self.ffi_namespace()), + arguments: vec![FfiArgument { + name: "callback".into(), + type_: FfiType::ForeignExecutorCallback, + }], + return_type: None, + is_async: false, + has_rust_call_status_arg: false, + is_object_free_function: false, + }) + } else { + None + } + } + + /// List all API checksums to check + /// + /// Returns a list of (export_symbol_name, checksum) items + pub fn iter_checksums(&self) -> impl Iterator<Item = (String, u16)> + '_ { + let func_checksums = self + .functions + .iter() + .map(|f| (f.checksum_fn_name(), f.checksum())); + let method_checksums = self.objects.iter().flat_map(|o| { + o.methods() + .into_iter() + .map(|m| (m.checksum_fn_name(), m.checksum())) + }); + let constructor_checksums = self.objects.iter().flat_map(|o| { + o.constructors() + .into_iter() + .map(|c| (c.checksum_fn_name(), c.checksum())) + }); + let callback_method_checksums = self.callback_interfaces.iter().flat_map(|cbi| { + cbi.methods().into_iter().filter_map(|m| { + if m.checksum_fn_name().is_empty() { + // UDL-based callbacks don't have checksum functions, skip these + None + } else { + Some((m.checksum_fn_name(), m.checksum())) + } + }) + }); + func_checksums + .chain(method_checksums) + .chain(constructor_checksums) + .chain(callback_method_checksums) + .map(|(fn_name, checksum)| (fn_name.to_string(), checksum)) + } + + pub fn iter_checksum_ffi_functions(&self) -> impl Iterator<Item = FfiFunction> + '_ { + self.iter_checksums().map(|(name, _)| FfiFunction { + name, + is_async: false, + arguments: vec![], + return_type: Some(FfiType::UInt16), + has_rust_call_status_arg: false, + is_object_free_function: false, + }) + } + + // Private methods for building a ComponentInterface. + // + /// Called by `APIBuilder` impls to add a newly-parsed enum definition to the `ComponentInterface`. + pub(super) fn add_enum_definition(&mut self, defn: Enum) -> Result<()> { + match self.enums.entry(defn.name().to_owned()) { + Entry::Vacant(v) => { + self.types.add_known_types(defn.iter_types())?; + v.insert(defn); + } + Entry::Occupied(o) => { + let existing_def = o.get(); + if defn != *existing_def { + bail!( + "Mismatching definition for enum `{}`!\n\ + existing definition: {existing_def:#?},\n\ + new definition: {defn:#?}", + defn.name(), + ); + } + } + } + + Ok(()) + } + + /// Adds a newly-parsed record definition to the `ComponentInterface`. + pub(super) fn add_record_definition(&mut self, defn: Record) -> Result<()> { + match self.records.entry(defn.name().to_owned()) { + Entry::Vacant(v) => { + self.types.add_known_types(defn.iter_types())?; + v.insert(defn); + } + Entry::Occupied(o) => { + let existing_def = o.get(); + if defn != *existing_def { + bail!( + "Mismatching definition for record `{}`!\n\ + existing definition: {existing_def:#?},\n\ + new definition: {defn:#?}", + defn.name(), + ); + } + } + } + + Ok(()) + } + + /// Called by `APIBuilder` impls to add a newly-parsed function definition to the `ComponentInterface`. + pub(super) fn add_function_definition(&mut self, defn: Function) -> Result<()> { + // Since functions are not a first-class type, we have to check for duplicates here + // rather than relying on the type-finding pass to catch them. + if self.functions.iter().any(|f| f.name == defn.name) { + bail!("duplicate function definition: \"{}\"", defn.name); + } + if self.types.get_type_definition(defn.name()).is_some() { + bail!("Conflicting type definition for \"{}\"", defn.name()); + } + self.types.add_known_types(defn.iter_types())?; + self.functions.push(defn); + + Ok(()) + } + + pub(super) fn add_constructor_meta(&mut self, meta: ConstructorMetadata) -> Result<()> { + let object = get_object(&mut self.objects, &meta.self_name) + .ok_or_else(|| anyhow!("add_constructor_meta: object {} not found", &meta.self_name))?; + let defn: Constructor = meta.into(); + + self.types.add_known_types(defn.iter_types())?; + object.constructors.push(defn); + + Ok(()) + } + + pub(super) fn add_method_meta(&mut self, meta: impl Into<Method>) -> Result<()> { + let mut method: Method = meta.into(); + let object = get_object(&mut self.objects, &method.object_name) + .ok_or_else(|| anyhow!("add_method_meta: object {} not found", &method.object_name))?; + + self.types.add_known_types(method.iter_types())?; + method.object_impl = object.imp; + object.methods.push(method); + Ok(()) + } + + pub(super) fn add_uniffitrait_meta(&mut self, meta: UniffiTraitMetadata) -> Result<()> { + let object = get_object(&mut self.objects, meta.self_name()) + .ok_or_else(|| anyhow!("add_uniffitrait_meta: object not found"))?; + let ut: UniffiTrait = meta.into(); + self.types.add_known_types(ut.iter_types())?; + object.uniffi_traits.push(ut); + Ok(()) + } + + pub(super) fn add_object_meta(&mut self, meta: ObjectMetadata) -> Result<()> { + self.add_object_definition(meta.into()) + } + + /// Called by `APIBuilder` impls to add a newly-parsed object definition to the `ComponentInterface`. + fn add_object_definition(&mut self, defn: Object) -> Result<()> { + self.types.add_known_types(defn.iter_types())?; + self.objects.push(defn); + Ok(()) + } + + pub(super) fn note_name_used_as_error(&mut self, name: &str) { + self.errors.insert(name.to_string()); + } + + pub fn is_name_used_as_error(&self, name: &str) -> bool { + self.errors.contains(name) + } + + /// Called by `APIBuilder` impls to add a newly-parsed callback interface definition to the `ComponentInterface`. + pub(super) fn add_callback_interface_definition(&mut self, defn: CallbackInterface) { + self.callback_interfaces.push(defn); + } + + pub(super) fn add_trait_method_meta(&mut self, meta: TraitMethodMetadata) -> Result<()> { + if let Some(cbi) = get_callback_interface(&mut self.callback_interfaces, &meta.trait_name) { + // uniffi_meta should ensure that we process callback interface methods in order, double + // check that here + if cbi.methods.len() != meta.index as usize { + bail!( + "UniFFI internal error: callback interface method index mismatch for {}::{} (expected {}, saw {})", + meta.trait_name, + meta.name, + cbi.methods.len(), + meta.index, + ); + } + let method: Method = meta.into(); + if let Some(error) = method.throws_type() { + self.callback_interface_throws_types.insert(error.clone()); + } + self.types.add_known_types(method.iter_types())?; + cbi.methods.push(method); + } else { + self.add_method_meta(meta)?; + } + Ok(()) + } + + /// Perform global consistency checks on the declared interface. + /// + /// This method checks for consistency problems in the declared interface + /// as a whole, and which can only be detected after we've finished defining + /// the entire interface. + pub fn check_consistency(&self) -> Result<()> { + if self.namespace().is_empty() { + bail!("missing namespace definition"); + } + + // Because functions aren't first class types, we need to check here that + // a function name hasn't already been used as a type name. + for f in self.functions.iter() { + if self.types.get_type_definition(f.name()).is_some() { + bail!("Conflicting type definition for \"{}\"", f.name()); + } + } + + for ty in self.iter_types() { + match ty { + Type::Object { name, .. } => { + ensure!( + self.objects.iter().any(|o| o.name == *name), + "Object `{name}` has no definition" + ); + } + Type::Record { name, .. } => { + ensure!( + self.records.contains_key(name), + "Record `{name}` has no definition", + ); + } + Type::Enum { name, .. } => { + ensure!( + self.enums.contains_key(name), + "Enum `{name}` has no definition", + ); + } + _ => {} + } + } + + Ok(()) + } + + /// Automatically derive the low-level FFI functions from the high-level types in the interface. + /// + /// This should only be called after the high-level types have been completed defined, otherwise + /// the resulting set will be missing some entries. + pub fn derive_ffi_funcs(&mut self) -> Result<()> { + for func in self.functions.iter_mut() { + func.derive_ffi_func()?; + } + for obj in self.objects.iter_mut() { + obj.derive_ffi_funcs()?; + } + for callback in self.callback_interfaces.iter_mut() { + callback.derive_ffi_funcs(); + } + Ok(()) + } +} + +fn get_object<'a>(objects: &'a mut [Object], name: &str) -> Option<&'a mut Object> { + objects.iter_mut().find(|o| o.name == name) +} + +fn get_callback_interface<'a>( + callback_interfaces: &'a mut [CallbackInterface], + name: &str, +) -> Option<&'a mut CallbackInterface> { + callback_interfaces.iter_mut().find(|o| o.name == name) +} + +/// Stateful iterator for yielding all types contained in a given type. +/// +/// This struct is the implementation of [`ComponentInterface::iter_types_in_item`] and should be +/// considered an opaque implementation detail. It's a separate struct because I couldn't +/// figure out a way to implement it using iterators and closures that would make the lifetimes +/// work out correctly. +/// +/// The idea here is that we want to yield all the types from `iter_types` on a given type, and +/// additionally we want to recurse into the definition of any user-provided types like records, +/// enums, etc so we can also yield the types contained therein. +/// +/// To guard against infinite recursion, we maintain a list of previously-seen user-defined +/// types, ensuring that we recurse into the definition of those types only once. To simplify +/// the implementation, we maintain a queue of pending user-defined types that we have seen +/// but not yet recursed into. (Ironically, the use of an explicit queue means our implementation +/// is not actually recursive...) +struct RecursiveTypeIterator<'a> { + /// The [`ComponentInterface`] from which this iterator was created. + ci: &'a ComponentInterface, + /// The currently-active iterator from which we're yielding. + current: TypeIterator<'a>, + /// A set of names of user-defined types that we have already seen. + seen: HashSet<&'a str>, + /// A queue of user-defined types that we need to recurse into. + pending: Vec<&'a Type>, +} + +impl<'a> RecursiveTypeIterator<'a> { + /// Allocate a new `RecursiveTypeIterator` over the given item. + fn new(ci: &'a ComponentInterface, item: &'a Type) -> RecursiveTypeIterator<'a> { + RecursiveTypeIterator { + ci, + // We begin by iterating over the types from the item itself. + current: item.iter_types(), + seen: Default::default(), + pending: Default::default(), + } + } + + /// Add a new type to the queue of pending types, if not previously seen. + fn add_pending_type(&mut self, type_: &'a Type) { + match type_ { + Type::Record { name, .. } + | Type::Enum { name, .. } + | Type::Object { name, .. } + | Type::CallbackInterface { name, .. } => { + if !self.seen.contains(name.as_str()) { + self.pending.push(type_); + self.seen.insert(name.as_str()); + } + } + _ => (), + } + } + + /// Advance the iterator to recurse into the next pending type, if any. + /// + /// This method is called when the current iterator is empty, and it will select + /// the next pending type from the queue and start iterating over its contained types. + /// The return value will be the first item from the new iterator. + fn advance_to_next_type(&mut self) -> Option<&'a Type> { + if let Some(next_type) = self.pending.pop() { + // This is a little awkward because the various definition lookup methods return an `Option<T>`. + // In the unlikely event that one of them returns `None` then, rather than trying to advance + // to a non-existent type, we just leave the existing iterator in place and allow the recursive + // call to `next()` to try again with the next pending type. + let next_iter = match next_type { + Type::Record { name, .. } => { + self.ci.get_record_definition(name).map(Record::iter_types) + } + Type::Enum { name, .. } => self.ci.get_enum_definition(name).map(Enum::iter_types), + Type::Object { name, .. } => { + self.ci.get_object_definition(name).map(Object::iter_types) + } + Type::CallbackInterface { name, .. } => self + .ci + .get_callback_interface_definition(name) + .map(CallbackInterface::iter_types), + _ => None, + }; + if let Some(next_iter) = next_iter { + self.current = next_iter; + } + // Advance the new iterator to its first item. If the new iterator happens to be empty, + // this will recurse back in to `advance_to_next_type` until we find one that isn't. + self.next() + } else { + // We've completely finished the iteration over all pending types. + None + } + } +} + +impl<'a> Iterator for RecursiveTypeIterator<'a> { + type Item = &'a Type; + fn next(&mut self) -> Option<Self::Item> { + if let Some(type_) = self.current.next() { + self.add_pending_type(type_); + Some(type_) + } else { + self.advance_to_next_type() + } + } +} + +// Helpers for functions/methods/constructors which all have the same "throws" semantics. +fn throws_name(throws: &Option<Type>) -> Option<&str> { + // Type has no `name()` method, just `canonical_name()` which isn't what we want. + match throws { + None => None, + Some(Type::Enum { name, .. }) => Some(name), + _ => panic!("unknown throw type: {throws:?}"), + } +} + +#[cfg(test)] +mod test { + use super::*; + + // Note that much of the functionality of `ComponentInterface` is tested via its interactions + // with specific member types, in the sub-modules defining those member types. + + #[test] + fn test_duplicate_type_names_are_an_error() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + }; + dictionary Testing { + u32 field; + }; + "#; + let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err(); + assert_eq!( + err.to_string(), + "Conflicting type definition for `Testing`! \ + existing definition: Object { module_path: \"crate_name\", name: \"Testing\", imp: Struct }, \ + new definition: Record { module_path: \"crate_name\", name: \"Testing\" }" + ); + + const UDL2: &str = r#" + namespace test{}; + enum Testing { + "one", "two" + }; + [Error] + enum Testing { "three", "four" }; + "#; + let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err(); + assert_eq!( + err.to_string(), + "Mismatching definition for enum `Testing`!\nexisting definition: Enum { + name: \"Testing\", + module_path: \"crate_name\", + variants: [ + Variant { + name: \"one\", + fields: [], + }, + Variant { + name: \"two\", + fields: [], + }, + ], + flat: true, +}, +new definition: Enum { + name: \"Testing\", + module_path: \"crate_name\", + variants: [ + Variant { + name: \"three\", + fields: [], + }, + Variant { + name: \"four\", + fields: [], + }, + ], + flat: true, +}", + ); + + const UDL3: &str = r#" + namespace test{ + u32 Testing(); + }; + enum Testing { + "one", "two" + }; + "#; + let err = ComponentInterface::from_webidl(UDL3, "crate_name").unwrap_err(); + assert!(format!("{err:#}").contains("Conflicting type definition for \"Testing\"")); + } + + #[test] + fn test_contains_optional_types() { + let mut ci = ComponentInterface { + ..Default::default() + }; + + // check that `contains_optional_types` returns false when there is no Optional type in the interface + assert!(!ci.contains_optional_types()); + + // check that `contains_optional_types` returns true when there is an Optional type in the interface + assert!(ci + .types + .add_known_type(&Type::Optional { + inner_type: Box::new(Type::String) + }) + .is_ok()); + assert!(ci.contains_optional_types()); + } + + #[test] + fn test_contains_sequence_types() { + let mut ci = ComponentInterface { + ..Default::default() + }; + + // check that `contains_sequence_types` returns false when there is no Sequence type in the interface + assert!(!ci.contains_sequence_types()); + + // check that `contains_sequence_types` returns true when there is a Sequence type in the interface + assert!(ci + .types + .add_known_type(&Type::Sequence { + inner_type: Box::new(Type::UInt64) + }) + .is_ok()); + assert!(ci.contains_sequence_types()); + assert!(ci.types.contains(&Type::UInt64)); + } + + #[test] + fn test_contains_map_types() { + let mut ci = ComponentInterface { + ..Default::default() + }; + + // check that `contains_map_types` returns false when there is no Map type in the interface + assert!(!ci.contains_map_types()); + + // check that `contains_map_types` returns true when there is a Map type in the interface + assert!(ci + .types + .add_known_type(&Type::Map { + key_type: Box::new(Type::String), + value_type: Box::new(Type::Boolean) + }) + .is_ok()); + assert!(ci.contains_map_types()); + assert!(ci.types.contains(&Type::String)); + assert!(ci.types.contains(&Type::Boolean)); + } + + #[test] + fn test_no_infinite_recursion_when_walking_types() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + void tester(Testing foo); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert!(!ci.item_contains_unsigned_types(&Type::Object { + name: "Testing".into(), + module_path: "".into(), + imp: ObjectImpl::Struct, + })); + } + + #[test] + fn test_correct_recursion_when_walking_types() { + const UDL: &str = r#" + namespace test{}; + interface TestObj { + void tester(TestRecord foo); + }; + dictionary TestRecord { + NestedRecord bar; + }; + dictionary NestedRecord { + u64 baz; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert!(ci.item_contains_unsigned_types(&Type::Object { + name: "TestObj".into(), + module_path: "".into(), + imp: ObjectImpl::Struct, + })); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/object.rs b/third_party/rust/uniffi_bindgen/src/interface/object.rs new file mode 100644 index 0000000000..942032b3c6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/object.rs @@ -0,0 +1,773 @@ +/* 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/. */ + +//! # Object definitions for a `ComponentInterface`. +//! +//! This module converts "interface" definitions from UDL into [`Object`] structures +//! that can be added to a `ComponentInterface`, which are the main way we define stateful +//! objects with behaviour for a UniFFI Rust Component. An [`Object`] is an opaque handle +//! to some state on which methods can be invoked. +//! +//! (The terminology mismatch between "interface" and "object" is a historical artifact of +//! this tool prior to committing to WebIDL syntax). +//! +//! A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! interface Example { +//! constructor(string? name); +//! string my_name(); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Object`] member with one [`Constructor`] and one [`Method`] being added +//! to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # interface Example { +//! # constructor(string? name); +//! # string my_name(); +//! # }; +//! # "##, "crate_name")?; +//! let obj = ci.get_object_definition("Example").unwrap(); +//! assert_eq!(obj.name(), "Example"); +//! assert_eq!(obj.constructors().len(), 1); +//! assert_eq!(obj.constructors()[0].arguments()[0].name(), "name"); +//! assert_eq!(obj.methods().len(),1 ); +//! assert_eq!(obj.methods()[0].name(), "my_name"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! It's not necessary for all interfaces to have constructors. +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # interface Example {}; +//! # "##, "crate_name")?; +//! let obj = ci.get_object_definition("Example").unwrap(); +//! assert_eq!(obj.name(), "Example"); +//! assert_eq!(obj.constructors().len(), 0); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use std::iter; + +use anyhow::Result; +use uniffi_meta::Checksum; + +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::function::{Argument, Callable}; +use super::{AsType, ObjectImpl, Type, TypeIterator}; + +/// An "object" is an opaque type that is passed around by reference, can +/// have methods called on it, and so on - basically your classic Object Oriented Programming +/// type of deal, except without elaborate inheritance hierarchies. Some can be instantiated. +/// +/// In UDL these correspond to the `interface` keyword. +/// +/// At the FFI layer, objects are represented by an opaque integer handle and a set of functions +/// a common prefix. The object's constructors are functions that return new objects by handle, +/// and its methods are functions that take a handle as first argument. The foreign language +/// binding code is expected to stitch these functions back together into an appropriate class +/// definition (or that language's equivalent thereof). +/// +/// TODO: +/// - maybe "Class" would be a better name than "Object" here? +#[derive(Debug, Clone, Checksum)] +pub struct Object { + pub(super) name: String, + /// How this object is implemented in Rust + pub(super) imp: ObjectImpl, + pub(super) module_path: String, + pub(super) constructors: Vec<Constructor>, + pub(super) methods: Vec<Method>, + // The "trait" methods - they have a (presumably "well known") name, and + // a regular method (albeit with a generated name) + // XXX - this should really be a HashSet, but not enough transient types support hash to make it worthwhile now. + pub(super) uniffi_traits: Vec<UniffiTrait>, + // We don't include the FfiFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_func_free: FfiFunction, +} + +impl Object { + pub fn name(&self) -> &str { + &self.name + } + + /// Returns the fully qualified name that should be used by Rust code for this object. + /// Includes `r#`, traits get a leading `dyn`. If we ever supported associated types, then + /// this would also include them. + pub fn rust_name(&self) -> String { + self.imp.rust_name_for(&self.name) + } + + pub fn imp(&self) -> &ObjectImpl { + &self.imp + } + + pub fn constructors(&self) -> Vec<&Constructor> { + self.constructors.iter().collect() + } + + pub fn primary_constructor(&self) -> Option<&Constructor> { + self.constructors + .iter() + .find(|cons| cons.is_primary_constructor()) + } + + pub fn alternate_constructors(&self) -> Vec<&Constructor> { + self.constructors + .iter() + .filter(|cons| !cons.is_primary_constructor()) + .collect() + } + + pub fn methods(&self) -> Vec<&Method> { + self.methods.iter().collect() + } + + pub fn get_method(&self, name: &str) -> Method { + let matches: Vec<_> = self.methods.iter().filter(|m| m.name() == name).collect(); + match matches.len() { + 1 => matches[0].clone(), + n => panic!("{n} methods named {name}"), + } + } + + pub fn uniffi_traits(&self) -> Vec<&UniffiTrait> { + self.uniffi_traits.iter().collect() + } + + pub fn ffi_object_free(&self) -> &FfiFunction { + &self.ffi_func_free + } + + pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> { + iter::once(&self.ffi_func_free) + .chain(self.constructors.iter().map(|f| &f.ffi_func)) + .chain(self.methods.iter().map(|f| &f.ffi_func)) + .chain( + self.uniffi_traits + .iter() + .flat_map(|ut| match ut { + UniffiTrait::Display { fmt: m } + | UniffiTrait::Debug { fmt: m } + | UniffiTrait::Hash { hash: m } => vec![m], + UniffiTrait::Eq { eq, ne } => vec![eq, ne], + }) + .map(|m| &m.ffi_func), + ) + } + + pub fn derive_ffi_funcs(&mut self) -> Result<()> { + assert!(!self.ffi_func_free.name().is_empty()); + self.ffi_func_free.arguments = vec![FfiArgument { + name: "ptr".to_string(), + type_: FfiType::RustArcPtr(self.name.to_string()), + }]; + self.ffi_func_free.return_type = None; + self.ffi_func_free.is_object_free_function = true; + + for cons in self.constructors.iter_mut() { + cons.derive_ffi_func(); + } + for meth in self.methods.iter_mut() { + meth.derive_ffi_func()?; + } + for ut in self.uniffi_traits.iter_mut() { + ut.derive_ffi_func()?; + } + + Ok(()) + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + self.methods + .iter() + .map(Method::iter_types) + .chain(self.uniffi_traits.iter().map(UniffiTrait::iter_types)) + .chain(self.constructors.iter().map(Constructor::iter_types)) + .flatten(), + ) + } +} + +impl AsType for Object { + fn as_type(&self) -> Type { + Type::Object { + name: self.name.clone(), + module_path: self.module_path.clone(), + imp: self.imp, + } + } +} + +impl From<uniffi_meta::ObjectMetadata> for Object { + fn from(meta: uniffi_meta::ObjectMetadata) -> Self { + let ffi_free_name = meta.free_ffi_symbol_name(); + Object { + module_path: meta.module_path, + name: meta.name, + imp: meta.imp, + constructors: Default::default(), + methods: Default::default(), + uniffi_traits: Default::default(), + ffi_func_free: FfiFunction { + name: ffi_free_name, + ..Default::default() + }, + } + } +} + +impl From<uniffi_meta::UniffiTraitMetadata> for UniffiTrait { + fn from(meta: uniffi_meta::UniffiTraitMetadata) -> Self { + match meta { + uniffi_meta::UniffiTraitMetadata::Debug { fmt } => { + UniffiTrait::Debug { fmt: fmt.into() } + } + uniffi_meta::UniffiTraitMetadata::Display { fmt } => { + UniffiTrait::Display { fmt: fmt.into() } + } + uniffi_meta::UniffiTraitMetadata::Eq { eq, ne } => UniffiTrait::Eq { + eq: eq.into(), + ne: ne.into(), + }, + uniffi_meta::UniffiTraitMetadata::Hash { hash } => { + UniffiTrait::Hash { hash: hash.into() } + } + } + } +} + +// Represents a constructor for an object type. +// +// In the FFI, this will be a function that returns a pointer to an instance +// of the corresponding object type. +#[derive(Debug, Clone, Checksum)] +pub struct Constructor { + pub(super) name: String, + pub(super) object_name: String, + pub(super) object_module_path: String, + pub(super) arguments: Vec<Argument>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_func: FfiFunction, + pub(super) throws: Option<Type>, + pub(super) checksum_fn_name: String, + // Force a checksum value, or we'll fallback to the trait. + #[checksum_ignore] + pub(super) checksum: Option<u16>, +} + +impl Constructor { + pub fn name(&self) -> &str { + &self.name + } + + pub fn arguments(&self) -> Vec<&Argument> { + self.arguments.iter().collect() + } + + pub fn full_arguments(&self) -> Vec<Argument> { + self.arguments.to_vec() + } + + pub fn ffi_func(&self) -> &FfiFunction { + &self.ffi_func + } + + pub fn checksum_fn_name(&self) -> &str { + &self.checksum_fn_name + } + + pub fn checksum(&self) -> u16 { + self.checksum.unwrap_or_else(|| uniffi_meta::checksum(self)) + } + + pub fn throws(&self) -> bool { + self.throws.is_some() + } + + pub fn throws_name(&self) -> Option<&str> { + super::throws_name(&self.throws) + } + + pub fn throws_type(&self) -> Option<&Type> { + self.throws.as_ref() + } + + pub fn is_primary_constructor(&self) -> bool { + self.name == "new" + } + + fn derive_ffi_func(&mut self) { + assert!(!self.ffi_func.name().is_empty()); + self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect(); + self.ffi_func.return_type = Some(FfiType::RustArcPtr(self.object_name.clone())); + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.arguments.iter().flat_map(Argument::iter_types)) + } +} + +impl From<uniffi_meta::ConstructorMetadata> for Constructor { + fn from(meta: uniffi_meta::ConstructorMetadata) -> Self { + let ffi_name = meta.ffi_symbol_name(); + let checksum_fn_name = meta.checksum_symbol_name(); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + + let ffi_func = FfiFunction { + name: ffi_name, + ..FfiFunction::default() + }; + Self { + name: meta.name, + object_name: meta.self_name, + object_module_path: meta.module_path, + arguments, + ffi_func, + throws: meta.throws.map(Into::into), + checksum_fn_name, + checksum: meta.checksum, + } + } +} + +// Represents an instance method for an object type. +// +// The FFI will represent this as a function whose first/self argument is a +// `FfiType::RustArcPtr` to the instance. +#[derive(Debug, Clone, Checksum)] +pub struct Method { + pub(super) name: String, + pub(super) object_name: String, + pub(super) object_module_path: String, + pub(super) is_async: bool, + pub(super) object_impl: ObjectImpl, + pub(super) arguments: Vec<Argument>, + pub(super) return_type: Option<Type>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_func: FfiFunction, + pub(super) throws: Option<Type>, + pub(super) takes_self_by_arc: bool, + pub(super) checksum_fn_name: String, + // Force a checksum value, or we'll fallback to the trait. + #[checksum_ignore] + pub(super) checksum: Option<u16>, +} + +impl Method { + pub fn name(&self) -> &str { + &self.name + } + + pub fn is_async(&self) -> bool { + self.is_async + } + + pub fn arguments(&self) -> Vec<&Argument> { + self.arguments.iter().collect() + } + + // Methods have a special implicit first argument for the object instance, + // hence `arguments` and `full_arguments` are different. + pub fn full_arguments(&self) -> Vec<Argument> { + vec![Argument { + name: "ptr".to_string(), + // TODO: ideally we'd get this via `ci.resolve_type_expression` so that it + // is contained in the proper `TypeUniverse`, but this works for now. + type_: Type::Object { + name: self.object_name.clone(), + module_path: self.object_module_path.clone(), + imp: self.object_impl, + }, + by_ref: !self.takes_self_by_arc, + optional: false, + default: None, + }] + .into_iter() + .chain(self.arguments.iter().cloned()) + .collect() + } + + pub fn return_type(&self) -> Option<&Type> { + self.return_type.as_ref() + } + + pub fn ffi_func(&self) -> &FfiFunction { + &self.ffi_func + } + + pub fn checksum_fn_name(&self) -> &str { + &self.checksum_fn_name + } + + pub fn checksum(&self) -> u16 { + self.checksum.unwrap_or_else(|| uniffi_meta::checksum(self)) + } + + pub fn throws(&self) -> bool { + self.throws.is_some() + } + + pub fn throws_name(&self) -> Option<&str> { + super::throws_name(&self.throws) + } + + pub fn throws_type(&self) -> Option<&Type> { + self.throws.as_ref() + } + + pub fn takes_self_by_arc(&self) -> bool { + self.takes_self_by_arc + } + + pub fn derive_ffi_func(&mut self) -> Result<()> { + assert!(!self.ffi_func.name().is_empty()); + self.ffi_func.init( + self.return_type.as_ref().map(Into::into), + self.full_arguments().iter().map(Into::into), + ); + Ok(()) + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + self.arguments + .iter() + .flat_map(Argument::iter_types) + .chain(self.return_type.iter().flat_map(Type::iter_types)), + ) + } +} + +impl From<uniffi_meta::MethodMetadata> for Method { + fn from(meta: uniffi_meta::MethodMetadata) -> Self { + let ffi_name = meta.ffi_symbol_name(); + let checksum_fn_name = meta.checksum_symbol_name(); + let is_async = meta.is_async; + let return_type = meta.return_type.map(Into::into); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + + let ffi_func = FfiFunction { + name: ffi_name, + is_async, + ..FfiFunction::default() + }; + + Self { + name: meta.name, + object_name: meta.self_name, + object_module_path: meta.module_path, + is_async, + object_impl: ObjectImpl::Struct, // will be filled in later + arguments, + return_type, + ffi_func, + throws: meta.throws.map(Into::into), + takes_self_by_arc: meta.takes_self_by_arc, + checksum_fn_name, + checksum: meta.checksum, + } + } +} + +impl From<uniffi_meta::TraitMethodMetadata> for Method { + fn from(meta: uniffi_meta::TraitMethodMetadata) -> Self { + let ffi_name = meta.ffi_symbol_name(); + let checksum_fn_name = meta.checksum_symbol_name(); + let return_type = meta.return_type.map(Into::into); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + let ffi_func = FfiFunction { + name: ffi_name, + ..FfiFunction::default() + }; + Self { + name: meta.name, + object_name: meta.trait_name, + object_module_path: meta.module_path, + is_async: false, + arguments, + return_type, + throws: meta.throws.map(Into::into), + takes_self_by_arc: meta.takes_self_by_arc, + checksum_fn_name, + checksum: meta.checksum, + ffi_func, + object_impl: ObjectImpl::Struct, + } + } +} + +/// The list of traits we support generating helper methods for. +#[derive(Clone, Debug, Checksum)] +pub enum UniffiTrait { + Debug { fmt: Method }, + Display { fmt: Method }, + Eq { eq: Method, ne: Method }, + Hash { hash: Method }, +} + +impl UniffiTrait { + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + match self { + UniffiTrait::Display { fmt: m } + | UniffiTrait::Debug { fmt: m } + | UniffiTrait::Hash { hash: m } => vec![m.iter_types()], + UniffiTrait::Eq { eq, ne } => vec![eq.iter_types(), ne.iter_types()], + } + .into_iter() + .flatten(), + ) + } + + pub fn derive_ffi_func(&mut self) -> Result<()> { + match self { + UniffiTrait::Display { fmt: m } + | UniffiTrait::Debug { fmt: m } + | UniffiTrait::Hash { hash: m } => { + m.derive_ffi_func()?; + } + UniffiTrait::Eq { eq, ne } => { + eq.derive_ffi_func()?; + ne.derive_ffi_func()?; + } + } + Ok(()) + } +} + +impl Callable for Constructor { + fn arguments(&self) -> Vec<&Argument> { + self.arguments() + } + + fn return_type(&self) -> Option<Type> { + Some(Type::Object { + name: self.object_name.clone(), + module_path: self.object_module_path.clone(), + imp: ObjectImpl::Struct, + }) + } + + fn throws_type(&self) -> Option<Type> { + self.throws_type().cloned() + } + + fn is_async(&self) -> bool { + false + } +} + +impl Callable for Method { + fn arguments(&self) -> Vec<&Argument> { + self.arguments() + } + + fn return_type(&self) -> Option<Type> { + self.return_type().cloned() + } + + fn throws_type(&self) -> Option<Type> { + self.throws_type().cloned() + } + + fn is_async(&self) -> bool { + self.is_async + } +} + +#[cfg(test)] +mod test { + use super::super::ComponentInterface; + use super::*; + + #[test] + fn test_that_all_argument_and_return_types_become_known() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(string? name, u16 age); + sequence<u32> code_points_of_name(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.object_definitions().len(), 1); + ci.get_object_definition("Testing").unwrap(); + + assert_eq!(ci.iter_types().count(), 6); + assert!(ci.iter_types().any(|t| t == &Type::UInt16)); + assert!(ci.iter_types().any(|t| t == &Type::UInt32)); + assert!(ci.iter_types().any(|t| t + == &Type::Sequence { + inner_type: Box::new(Type::UInt32) + })); + assert!(ci.iter_types().any(|t| t == &Type::String)); + assert!(ci.iter_types().any(|t| t + == &Type::Optional { + inner_type: Box::new(Type::String) + })); + assert!(ci + .iter_types() + .any(|t| matches!(t, Type::Object { name, ..} if name == "Testing"))); + } + + #[test] + fn test_alternate_constructors() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + [Name=new_with_u32] + constructor(u32 v); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.object_definitions().len(), 1); + + let obj = ci.get_object_definition("Testing").unwrap(); + assert!(obj.primary_constructor().is_some()); + assert_eq!(obj.alternate_constructors().len(), 1); + assert_eq!(obj.methods().len(), 0); + + let cons = obj.primary_constructor().unwrap(); + assert_eq!(cons.name(), "new"); + assert_eq!(cons.arguments.len(), 0); + assert_eq!(cons.ffi_func.arguments.len(), 0); + + let cons = obj.alternate_constructors()[0]; + assert_eq!(cons.name(), "new_with_u32"); + assert_eq!(cons.arguments.len(), 1); + assert_eq!(cons.ffi_func.arguments.len(), 1); + } + + #[test] + fn test_the_name_new_identifies_the_primary_constructor() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + [Name=newish] + constructor(); + [Name=new] + constructor(u32 v); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.object_definitions().len(), 1); + + let obj = ci.get_object_definition("Testing").unwrap(); + assert!(obj.primary_constructor().is_some()); + assert_eq!(obj.alternate_constructors().len(), 1); + assert_eq!(obj.methods().len(), 0); + + let cons = obj.primary_constructor().unwrap(); + assert_eq!(cons.name(), "new"); + assert_eq!(cons.arguments.len(), 1); + + let cons = obj.alternate_constructors()[0]; + assert_eq!(cons.name(), "newish"); + assert_eq!(cons.arguments.len(), 0); + assert_eq!(cons.ffi_func.arguments.len(), 0); + } + + #[test] + fn test_the_name_new_is_reserved_for_constructors() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + void new(u32 v); + }; + "#; + let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err(); + assert_eq!( + err.to_string(), + "the method name \"new\" is reserved for the default constructor" + ); + } + + #[test] + fn test_duplicate_primary_constructors_not_allowed() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + constructor(u32 v); + }; + "#; + let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err(); + assert_eq!(err.to_string(), "Duplicate interface member name: \"new\""); + + const UDL2: &str = r#" + namespace test{}; + interface Testing { + constructor(); + [Name=new] + constructor(u32 v); + }; + "#; + let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err(); + assert_eq!(err.to_string(), "Duplicate interface member name: \"new\""); + } + + #[test] + fn test_trait_attribute() { + const UDL: &str = r#" + namespace test{}; + interface NotATrait { + }; + [Trait] + interface ATrait { + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + let obj = ci.get_object_definition("NotATrait").unwrap(); + assert_eq!(obj.imp.rust_name_for(&obj.name), "r#NotATrait"); + let obj = ci.get_object_definition("ATrait").unwrap(); + assert_eq!(obj.imp.rust_name_for(&obj.name), "dyn r#ATrait"); + } + + #[test] + fn test_trait_constructors_not_allowed() { + const UDL: &str = r#" + namespace test{}; + [Trait] + interface Testing { + constructor(); + }; + "#; + let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err(); + assert_eq!( + err.to_string(), + "Trait interfaces can not have constructors: \"new\"" + ); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/record.rs b/third_party/rust/uniffi_bindgen/src/interface/record.rs new file mode 100644 index 0000000000..17d3774a49 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/record.rs @@ -0,0 +1,230 @@ +/* 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/. */ + +//! # Record definitions for a `ComponentInterface`. +//! +//! This module converts "dictionary" definitions from UDL into [`Record`] structures +//! that can be added to a `ComponentInterface`, which are the main way we define structured +//! data types for a UniFFI Rust Component. A [`Record`] has a fixed set of named fields, +//! each of a specific type. +//! +//! (The terminology mismatch between "dictionary" and "record" is a historical artifact +//! due to this tool being loosely inspired by WebAssembly Interface Types, which used +//! the term "record" for this sort of data). +//! +//! A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! dictionary Example { +//! string name; +//! u32 value; +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Record`] member with two [`Field`]s being added to the resulting +//! [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # dictionary Example { +//! # string name; +//! # u32 value; +//! # }; +//! # "##, "crate_name")?; +//! let record = ci.get_record_definition("Example").unwrap(); +//! assert_eq!(record.name(), "Example"); +//! assert_eq!(record.fields()[0].name(), "name"); +//! assert_eq!(record.fields()[1].name(), "value"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::Result; +use uniffi_meta::Checksum; + +use super::Literal; +use super::{AsType, Type, TypeIterator}; + +/// Represents a "data class" style object, for passing around complex values. +/// +/// In the FFI these are represented as a byte buffer, which one side explicitly +/// serializes the data into and the other serializes it out of. So I guess they're +/// kind of like "pass by clone" values. +#[derive(Debug, Clone, PartialEq, Eq, Checksum)] +pub struct Record { + pub(super) name: String, + pub(super) module_path: String, + pub(super) fields: Vec<Field>, +} + +impl Record { + pub fn name(&self) -> &str { + &self.name + } + + pub fn fields(&self) -> &[Field] { + &self.fields + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.fields.iter().flat_map(Field::iter_types)) + } +} + +impl AsType for Record { + fn as_type(&self) -> Type { + Type::Record { + name: self.name.clone(), + module_path: self.module_path.clone(), + } + } +} + +impl TryFrom<uniffi_meta::RecordMetadata> for Record { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::RecordMetadata) -> Result<Self> { + Ok(Self { + name: meta.name, + module_path: meta.module_path, + fields: meta + .fields + .into_iter() + .map(TryInto::try_into) + .collect::<Result<_>>()?, + }) + } +} + +// Represents an individual field on a Record. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Checksum)] +pub struct Field { + pub(super) name: String, + pub(super) type_: Type, + pub(super) default: Option<Literal>, +} + +impl Field { + pub fn name(&self) -> &str { + &self.name + } + + pub fn default_value(&self) -> Option<&Literal> { + self.default.as_ref() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + self.type_.iter_types() + } +} + +impl AsType for Field { + fn as_type(&self) -> Type { + self.type_.clone() + } +} + +impl TryFrom<uniffi_meta::FieldMetadata> for Field { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::FieldMetadata) -> Result<Self> { + let name = meta.name; + let type_ = meta.ty; + let default = meta.default; + Ok(Self { + name, + type_, + default, + }) + } +} + +#[cfg(test)] +mod test { + use super::super::ComponentInterface; + use super::*; + use uniffi_meta::Radix; + + #[test] + fn test_multiple_record_types() { + const UDL: &str = r#" + namespace test{}; + dictionary Empty {}; + dictionary Simple { + u32 field; + }; + dictionary Complex { + string? key; + u32 value = 0; + required boolean spin; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.record_definitions().count(), 3); + + let record = ci.get_record_definition("Empty").unwrap(); + assert_eq!(record.name(), "Empty"); + assert_eq!(record.fields().len(), 0); + + let record = ci.get_record_definition("Simple").unwrap(); + assert_eq!(record.name(), "Simple"); + assert_eq!(record.fields().len(), 1); + assert_eq!(record.fields()[0].name(), "field"); + assert_eq!(record.fields()[0].as_type(), Type::UInt32); + assert!(record.fields()[0].default_value().is_none()); + + let record = ci.get_record_definition("Complex").unwrap(); + assert_eq!(record.name(), "Complex"); + assert_eq!(record.fields().len(), 3); + assert_eq!(record.fields()[0].name(), "key"); + assert_eq!( + record.fields()[0].as_type(), + Type::Optional { + inner_type: Box::new(Type::String) + }, + ); + assert!(record.fields()[0].default_value().is_none()); + assert_eq!(record.fields()[1].name(), "value"); + assert_eq!(record.fields()[1].as_type(), Type::UInt32); + assert!(matches!( + record.fields()[1].default_value(), + Some(Literal::UInt(0, Radix::Decimal, Type::UInt32)) + )); + assert_eq!(record.fields()[2].name(), "spin"); + assert_eq!(record.fields()[2].as_type(), Type::Boolean); + assert!(record.fields()[2].default_value().is_none()); + } + + #[test] + fn test_that_all_field_types_become_known() { + const UDL: &str = r#" + namespace test{}; + dictionary Testing { + string? maybe_name; + u32 value; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.record_definitions().count(), 1); + let record = ci.get_record_definition("Testing").unwrap(); + assert_eq!(record.fields().len(), 2); + assert_eq!(record.fields()[0].name(), "maybe_name"); + assert_eq!(record.fields()[1].name(), "value"); + + assert_eq!(ci.iter_types().count(), 4); + assert!(ci.iter_types().any(|t| t == &Type::UInt32)); + assert!(ci.iter_types().any(|t| t == &Type::String)); + assert!(ci.iter_types().any(|t| t + == &Type::Optional { + inner_type: Box::new(Type::String) + })); + assert!(ci + .iter_types() + .any(|t| matches!(t, Type::Record { name, .. } if name == "Testing"))); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/universe.rs b/third_party/rust/uniffi_bindgen/src/interface/universe.rs new file mode 100644 index 0000000000..e69d86e44f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/universe.rs @@ -0,0 +1,136 @@ +/* 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/. */ + +//! The set of all [`Type`]s used in a component interface is represented by a `TypeUniverse`, +//! which can be used by the bindings generator code to determine what type-related helper +//! functions to emit for a given component. +//! +use anyhow::Result; +use std::{collections::hash_map::Entry, collections::BTreeSet, collections::HashMap}; + +pub use uniffi_meta::{AsType, ExternalKind, NamespaceMetadata, ObjectImpl, Type, TypeIterator}; + +/// The set of all possible types used in a particular component interface. +/// +/// Every component API uses a finite number of types, including primitive types, API-defined +/// types like records and enums, and recursive types such as sequences of the above. Our +/// component API doesn't support fancy generics so this is a finitely-enumerable set, which +/// is useful to be able to operate on explicitly. +/// +/// You could imagine this struct doing some clever interning of names and so-on in future, +/// to reduce the overhead of passing around [Type] instances. For now we just do a whole +/// lot of cloning. +#[derive(Debug, Default)] +pub(crate) struct TypeUniverse { + /// The unique prefixes that we'll use for namespacing when exposing this component's API. + pub namespace: NamespaceMetadata, + + // Named type definitions (including aliases). + type_definitions: HashMap<String, Type>, + // All the types in the universe, by canonical type name, in a well-defined order. + all_known_types: BTreeSet<Type>, +} + +impl TypeUniverse { + pub fn new(namespace: NamespaceMetadata) -> Self { + Self { + namespace, + ..Default::default() + } + } + + /// Add the definition of a named [Type]. + fn add_type_definition(&mut self, name: &str, type_: &Type) -> Result<()> { + match self.type_definitions.entry(name.to_string()) { + Entry::Occupied(o) => { + // all conflicts have been resolved by now in udl. + // I doubt procmacros could cause this? + assert_eq!(type_, o.get()); + Ok(()) + } + Entry::Vacant(e) => { + e.insert(type_.clone()); + Ok(()) + } + } + } + + /// Get the [Type] corresponding to a given name, if any. + pub(super) fn get_type_definition(&self, name: &str) -> Option<Type> { + self.type_definitions.get(name).cloned() + } + + /// Add a [Type] to the set of all types seen in the component interface. + pub fn add_known_type(&mut self, type_: &Type) -> Result<()> { + // 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()); + } + match type_ { + Type::UInt8 => self.add_type_definition("u8", type_)?, + Type::Int8 => self.add_type_definition("i8", type_)?, + Type::UInt16 => self.add_type_definition("u16", type_)?, + Type::Int16 => self.add_type_definition("i16", type_)?, + Type::UInt32 => self.add_type_definition("i32", type_)?, + Type::Int32 => self.add_type_definition("u32", type_)?, + Type::UInt64 => self.add_type_definition("u64", type_)?, + Type::Int64 => self.add_type_definition("i64", type_)?, + Type::Float32 => self.add_type_definition("f32", type_)?, + Type::Float64 => self.add_type_definition("f64", type_)?, + Type::Boolean => self.add_type_definition("bool", type_)?, + Type::String => self.add_type_definition("string", type_)?, + Type::Bytes => self.add_type_definition("bytes", type_)?, + Type::Timestamp => self.add_type_definition("timestamp", type_)?, + Type::Duration => self.add_type_definition("duration", type_)?, + Type::ForeignExecutor => { + self.add_type_definition("ForeignExecutor", type_)?; + } + Type::Object { name, .. } + | Type::Record { name, .. } + | Type::Enum { name, .. } + | Type::CallbackInterface { name, .. } + | Type::External { name, .. } => self.add_type_definition(name, type_)?, + Type::Custom { name, builtin, .. } => { + self.add_type_definition(name, type_)?; + self.add_known_type(builtin)?; + } + // Structurally recursive types. + Type::Optional { inner_type, .. } | Type::Sequence { inner_type, .. } => { + self.add_known_type(inner_type)?; + } + Type::Map { + key_type, + value_type, + } => { + self.add_known_type(key_type)?; + self.add_known_type(value_type)?; + } + } + Ok(()) + } + + /// Add many [`Type`]s... + pub fn add_known_types(&mut self, types: TypeIterator<'_>) -> Result<()> { + for t in types { + self.add_known_type(t)? + } + Ok(()) + } + + /// Check if a [Type] is present + pub fn contains(&self, type_: &Type) -> bool { + self.all_known_types.contains(type_) + } + + /// Iterator over all the known types in this universe. + pub fn iter_known_types(&self) -> impl Iterator<Item = &Type> { + self.all_known_types.iter() + } +} + +#[cfg(test)] +mod test_type_universe { + // All the useful functionality of the `TypeUniverse` struct + // is tested as part of the `TypeFinder` and `TypeResolver` test suites. +} |