/* 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, pub(super) methods: Vec, // 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, // 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 { 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 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 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, // 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, pub(super) checksum_fn_name: String, // Force a checksum value, or we'll fallback to the trait. #[checksum_ignore] pub(super) checksum: Option, } 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 { 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 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, pub(super) return_type: Option, // 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, 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, } 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 { 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 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 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 { Some(Type::Object { name: self.object_name.clone(), module_path: self.object_module_path.clone(), imp: ObjectImpl::Struct, }) } fn throws_type(&self) -> Option { 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 { self.return_type().cloned() } fn throws_type(&self) -> Option { 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 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\"" ); } }