diff options
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/interface/object.rs')
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/object.rs | 599 |
1 files changed, 599 insertions, 0 deletions
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..e8ba089261 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/object.rs @@ -0,0 +1,599 @@ +/* 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(); +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Object`] member with one [`Constructor`] and one [`Method`] being added +//! to the resulting [`ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # interface Example { +//! # constructor(string? name); +//! # string my_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 {}; +//! # "##)?; +//! let obj = ci.get_object_definition("Example").unwrap(); +//! assert_eq!(obj.name(), "Example"); +//! assert_eq!(obj.constructors().len(), 0); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use std::convert::TryFrom; +use std::{collections::HashSet, iter}; + +use anyhow::{bail, Result}; +use uniffi_meta::Checksum; + +use super::attributes::{Attribute, ConstructorAttributes, InterfaceAttributes, MethodAttributes}; +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::function::Argument; +use super::types::{Type, TypeIterator}; +use super::{convert_type, APIConverter, ComponentInterface}; + +/// An "object" is an opaque type that can be instantiated and passed around by reference, +/// have methods called on it, and so on - basically your classic Object Oriented Programming +/// type of deal, except without elaborate inheritance hierarchies. +/// +/// 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, + pub(super) constructors: Vec<Constructor>, + 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_func_free: FfiFunction, + #[checksum_ignore] + pub(super) uses_deprecated_threadsafe_attribute: bool, +} + +impl Object { + pub(super) fn new(name: String) -> Object { + Object { + name, + constructors: Default::default(), + methods: Default::default(), + ffi_func_free: Default::default(), + uses_deprecated_threadsafe_attribute: false, + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn type_(&self) -> Type { + Type::Object(self.name.clone()) + } + + 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 ffi_object_free(&self) -> &FfiFunction { + &self.ffi_func_free + } + + pub fn uses_deprecated_threadsafe_attribute(&self) -> bool { + self.uses_deprecated_threadsafe_attribute + } + + 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)) + } + + pub fn derive_ffi_funcs(&mut self, ci_prefix: &str) -> Result<()> { + // The name is already set if the function is defined through a proc-macro invocation + // rather than in UDL. Don't overwrite it in that case. + if self.ffi_func_free.name().is_empty() { + self.ffi_func_free.name = format!("ffi_{ci_prefix}_{}_object_free", self.name); + } + 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; + + for cons in self.constructors.iter_mut() { + cons.derive_ffi_func(ci_prefix, &self.name); + } + for meth in self.methods.iter_mut() { + meth.derive_ffi_func(ci_prefix, &self.name)?; + } + + Ok(()) + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + self.methods + .iter() + .map(Method::iter_types) + .chain(self.constructors.iter().map(Constructor::iter_types)) + .flatten(), + ) + } +} + +impl APIConverter<Object> for weedle::InterfaceDefinition<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Object> { + if self.inheritance.is_some() { + bail!("interface inheritance is not supported"); + } + let mut object = Object::new(self.identifier.0.to_string()); + let attributes = match &self.attributes { + Some(attrs) => InterfaceAttributes::try_from(attrs)?, + None => Default::default(), + }; + object.uses_deprecated_threadsafe_attribute = attributes.threadsafe(); + // Convert each member into a constructor or method, guarding against duplicate names. + let mut member_names = HashSet::new(); + for member in &self.members.body { + match member { + weedle::interface::InterfaceMember::Constructor(t) => { + let cons: Constructor = t.convert(ci)?; + if !member_names.insert(cons.name.clone()) { + bail!("Duplicate interface member name: \"{}\"", cons.name()) + } + object.constructors.push(cons); + } + weedle::interface::InterfaceMember::Operation(t) => { + let mut method: Method = t.convert(ci)?; + if !member_names.insert(method.name.clone()) { + bail!("Duplicate interface member name: \"{}\"", method.name()) + } + method.object_name = object.name.clone(); + object.methods.push(method); + } + _ => bail!("no support for interface member type {:?} yet", member), + } + } + Ok(object) + } +} + +// 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) 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) attributes: ConstructorAttributes, +} + +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 throws(&self) -> bool { + self.attributes.get_throws_err().is_some() + } + + pub fn throws_name(&self) -> Option<&str> { + self.attributes.get_throws_err() + } + + pub fn throws_type(&self) -> Option<Type> { + self.attributes + .get_throws_err() + .map(|name| Type::Error(name.to_owned())) + } + + pub fn is_primary_constructor(&self) -> bool { + self.name == "new" + } + + fn derive_ffi_func(&mut self, ci_prefix: &str, obj_name: &str) { + self.ffi_func.name = format!("{ci_prefix}_{obj_name}_{}", self.name); + self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect(); + self.ffi_func.return_type = Some(FfiType::RustArcPtr(obj_name.to_string())); + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.arguments.iter().flat_map(Argument::iter_types)) + } +} + +impl Default for Constructor { + fn default() -> Self { + Constructor { + name: String::from("new"), + arguments: Vec::new(), + ffi_func: Default::default(), + attributes: Default::default(), + } + } +} + +impl APIConverter<Constructor> for weedle::interface::ConstructorInterfaceMember<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Constructor> { + let attributes = match &self.attributes { + Some(attr) => ConstructorAttributes::try_from(attr)?, + None => Default::default(), + }; + Ok(Constructor { + name: String::from(attributes.get_name().unwrap_or("new")), + arguments: self.args.body.list.convert(ci)?, + ffi_func: Default::default(), + attributes, + }) + } +} + +// 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) 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) attributes: MethodAttributes, +} + +impl Method { + pub fn name(&self) -> &str { + &self.name + } + + 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(self.object_name.clone()), + by_ref: !self.attributes.get_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 throws(&self) -> bool { + self.attributes.get_throws_err().is_some() + } + + pub fn throws_name(&self) -> Option<&str> { + self.attributes.get_throws_err() + } + + pub fn throws_type(&self) -> Option<Type> { + self.attributes + .get_throws_err() + .map(|name| Type::Error(name.to_owned())) + } + + pub fn takes_self_by_arc(&self) -> bool { + self.attributes.get_self_by_arc() + } + + pub fn derive_ffi_func(&mut self, ci_prefix: &str, obj_prefix: &str) -> Result<()> { + // The name is already set if the function is defined through a proc-macro invocation + // rather than in UDL. Don't overwrite it in that case. + if self.ffi_func.name.is_empty() { + self.ffi_func.name = format!("{ci_prefix}_{obj_prefix}_{}", self.name); + } + + self.ffi_func.arguments = self.full_arguments().iter().map(Into::into).collect(); + self.ffi_func.return_type = self.return_type.as_ref().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 return_type = meta.return_type.map(|out| convert_type(&out)); + 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, + arguments, + return_type, + ffi_func, + attributes: meta.throws.map(Attribute::Throws).into_iter().collect(), + } + } +} + +impl APIConverter<Method> for weedle::interface::OperationInterfaceMember<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Method> { + if self.special.is_some() { + bail!("special operations not supported"); + } + if self.modifier.is_some() { + bail!("method modifiers are not supported") + } + let return_type = ci.resolve_return_type_expression(&self.return_type)?; + Ok(Method { + name: match self.identifier { + None => bail!("anonymous methods are not supported {:?}", self), + Some(id) => { + let name = id.0.to_string(); + if name == "new" { + bail!("the method name \"new\" is reserved for the default constructor"); + } + name + } + }, + // We don't know the name of the containing `Object` at this point, fill it in later. + object_name: Default::default(), + arguments: self.args.body.list.convert(ci)?, + return_type, + ffi_func: Default::default(), + attributes: MethodAttributes::try_from(self.attributes.as_ref())?, + }) + } +} + +#[cfg(test)] +mod test { + 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).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.canonical_name() == "u16")); + assert!(ci.iter_types().any(|t| t.canonical_name() == "u32")); + assert!(ci.iter_types().any(|t| t.canonical_name() == "Sequenceu32")); + assert!(ci.iter_types().any(|t| t.canonical_name() == "string")); + assert!(ci + .iter_types() + .any(|t| t.canonical_name() == "Optionalstring")); + assert!(ci.iter_types().any(|t| t.canonical_name() == "TypeTesting")); + } + + #[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).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).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).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).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).unwrap_err(); + assert_eq!(err.to_string(), "Duplicate interface member name: \"new\""); + } +} |