diff options
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/interface')
14 files changed, 5320 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/interface/attributes.rs b/third_party/rust/uniffi_bindgen/src/interface/attributes.rs new file mode 100644 index 0000000000..4df6042564 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/attributes.rs @@ -0,0 +1,759 @@ +/* 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/. */ + +//! # Attribute definitions for a `ComponentInterface`. +//! +//! This module provides some conveniences for working with attribute definitions +//! from WebIDL. When encountering a weedle `ExtendedAttribute` node, use `TryFrom` +//! to convert it into an [`Attribute`] representing one of the attributes that we +//! support. You can also use the [`parse_attributes`] function to parse an +//! `ExtendedAttributeList` into a vec of same. +//! +//! We only support a small number of attributes, so it's manageable to have them +//! all handled by a single abstraction. This might need to be refactored in future +//! if we grow significantly more complicated attribute handling. + +use anyhow::{bail, Result}; +use uniffi_meta::Checksum; + +/// Represents an attribute parsed from UDL, like `[ByRef]` or `[Throws]`. +/// +/// This is a convenience enum for parsing UDL attributes and erroring out if we encounter +/// any unsupported ones. These don't convert directly into parts of a `ComponentInterface`, but +/// may influence the properties of things like functions and arguments. +#[derive(Debug, Clone, Checksum)] +pub(super) enum Attribute { + ByRef, + Enum, + Error, + Name(String), + SelfType(SelfType), + Threadsafe, // N.B. the `[Threadsafe]` attribute is deprecated and will be removed + Throws(String), + // `[External="crate_name"]` - We can `use crate_name::...` for the type. + External(String), + // Custom type on the scaffolding side + Custom, +} + +impl Attribute { + pub fn is_error(&self) -> bool { + matches!(self, Attribute::Error) + } + pub fn is_enum(&self) -> bool { + matches!(self, Attribute::Enum) + } +} + +/// Convert a weedle `ExtendedAttribute` into an `Attribute` for a `ComponentInterface` member, +/// or error out if the attribute is not supported. +impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute { + type Error = anyhow::Error; + fn try_from( + weedle_attribute: &weedle::attribute::ExtendedAttribute<'_>, + ) -> Result<Self, anyhow::Error> { + match weedle_attribute { + // Matches plain named attributes like "[ByRef"]. + weedle::attribute::ExtendedAttribute::NoArgs(attr) => match (attr.0).0 { + "ByRef" => Ok(Attribute::ByRef), + "Enum" => Ok(Attribute::Enum), + "Error" => Ok(Attribute::Error), + "Threadsafe" => Ok(Attribute::Threadsafe), + "Custom" => Ok(Attribute::Custom), + _ => anyhow::bail!("ExtendedAttributeNoArgs not supported: {:?}", (attr.0).0), + }, + // Matches assignment-style attributes like ["Throws=Error"] + weedle::attribute::ExtendedAttribute::Ident(identity) => { + match identity.lhs_identifier.0 { + "Name" => Ok(Attribute::Name(name_from_id_or_string(&identity.rhs))), + "Throws" => Ok(Attribute::Throws(name_from_id_or_string(&identity.rhs))), + "Self" => Ok(Attribute::SelfType(SelfType::try_from(&identity.rhs)?)), + "External" => Ok(Attribute::External(name_from_id_or_string(&identity.rhs))), + _ => anyhow::bail!( + "Attribute identity Identifier not supported: {:?}", + identity.lhs_identifier.0 + ), + } + } + _ => anyhow::bail!("Attribute not supported: {:?}", weedle_attribute), + } + } +} + +fn name_from_id_or_string(nm: &weedle::attribute::IdentifierOrString<'_>) -> String { + match nm { + weedle::attribute::IdentifierOrString::Identifier(identifier) => identifier.0.to_string(), + weedle::attribute::IdentifierOrString::String(str_lit) => str_lit.0.to_string(), + } +} + +/// Parse a weedle `ExtendedAttributeList` into a list of `Attribute`s, +/// erroring out on duplicates. +fn parse_attributes<F>( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + validator: F, +) -> Result<Vec<Attribute>> +where + F: Fn(&Attribute) -> Result<()>, +{ + let attrs = &weedle_attributes.body.list; + + let mut hash_set = std::collections::HashSet::new(); + for attr in attrs { + if !hash_set.insert(attr) { + anyhow::bail!("Duplicated ExtendedAttribute: {:?}", attr); + } + } + + let attrs = attrs + .iter() + .map(Attribute::try_from) + .collect::<Result<Vec<_>, _>>()?; + + for attr in &attrs { + validator(attr)?; + } + + Ok(attrs) +} + +/// Attributes that can be attached to an `enum` definition in the UDL. +/// There's only one case here: using `[Error]` to mark an enum as an error class. +#[derive(Debug, Clone, Checksum, Default)] +pub(super) struct EnumAttributes(Vec<Attribute>); + +impl EnumAttributes { + pub fn contains_error_attr(&self) -> bool { + self.0.iter().any(|attr| attr.is_error()) + } +} + +impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes { + type Error = anyhow::Error; + fn try_from( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + ) -> Result<Self, Self::Error> { + let attrs = parse_attributes(weedle_attributes, |attr| match attr { + Attribute::Error => Ok(()), + _ => bail!(format!("{attr:?} not supported for enums")), + })?; + Ok(Self(attrs)) + } +} + +impl<T: TryInto<EnumAttributes, Error = anyhow::Error>> TryFrom<Option<T>> for EnumAttributes { + type Error = anyhow::Error; + fn try_from(value: Option<T>) -> Result<Self, Self::Error> { + match value { + None => Ok(Default::default()), + Some(v) => v.try_into(), + } + } +} + +/// Represents UDL attributes that might appear on a function. +/// +/// This supports the `[Throws=ErrorName]` attribute for functions that +/// can produce an error. +#[derive(Debug, Clone, Checksum, Default)] +pub(super) struct FunctionAttributes(Vec<Attribute>); + +impl FunctionAttributes { + pub(super) fn get_throws_err(&self) -> Option<&str> { + self.0.iter().find_map(|attr| match attr { + // This will hopefully return a helpful compilation error + // if the error is not defined. + Attribute::Throws(inner) => Some(inner.as_ref()), + _ => None, + }) + } +} + +impl FromIterator<Attribute> for FunctionAttributes { + fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + +impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttributes { + type Error = anyhow::Error; + fn try_from( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + ) -> Result<Self, Self::Error> { + let attrs = parse_attributes(weedle_attributes, |attr| match attr { + Attribute::Throws(_) => Ok(()), + _ => bail!(format!("{attr:?} not supported for functions")), + })?; + Ok(Self(attrs)) + } +} + +impl<T: TryInto<FunctionAttributes, Error = anyhow::Error>> TryFrom<Option<T>> + for FunctionAttributes +{ + type Error = anyhow::Error; + fn try_from(value: Option<T>) -> Result<Self, Self::Error> { + match value { + None => Ok(Default::default()), + Some(v) => v.try_into(), + } + } +} + +/// Represents UDL attributes that might appear on a function argument. +/// +/// This supports the `[ByRef]` attribute for arguments that should be passed +/// by reference in the generated Rust scaffolding. +#[derive(Debug, Clone, Checksum, Default)] +pub(super) struct ArgumentAttributes(Vec<Attribute>); + +impl ArgumentAttributes { + pub fn by_ref(&self) -> bool { + self.0.iter().any(|attr| matches!(attr, Attribute::ByRef)) + } +} + +impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ArgumentAttributes { + type Error = anyhow::Error; + fn try_from( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + ) -> Result<Self, Self::Error> { + let attrs = parse_attributes(weedle_attributes, |attr| match attr { + Attribute::ByRef => Ok(()), + _ => bail!(format!("{attr:?} not supported for arguments")), + })?; + Ok(Self(attrs)) + } +} + +impl<T: TryInto<ArgumentAttributes, Error = anyhow::Error>> TryFrom<Option<T>> + for ArgumentAttributes +{ + type Error = anyhow::Error; + fn try_from(value: Option<T>) -> Result<Self, Self::Error> { + match value { + None => Ok(Default::default()), + Some(v) => v.try_into(), + } + } +} + +/// Represents UDL attributes that might appear on an `interface` definition. +#[derive(Debug, Clone, Checksum, Default)] +pub(super) struct InterfaceAttributes(Vec<Attribute>); + +impl InterfaceAttributes { + pub fn contains_enum_attr(&self) -> bool { + self.0.iter().any(|attr| attr.is_enum()) + } + + pub fn contains_error_attr(&self) -> bool { + self.0.iter().any(|attr| attr.is_error()) + } + + pub fn threadsafe(&self) -> bool { + self.0 + .iter() + .any(|attr| matches!(attr, Attribute::Threadsafe)) + } +} + +impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for InterfaceAttributes { + type Error = anyhow::Error; + fn try_from( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + ) -> Result<Self, Self::Error> { + let attrs = parse_attributes(weedle_attributes, |attr| match attr { + Attribute::Enum => Ok(()), + Attribute::Error => Ok(()), + Attribute::Threadsafe => Ok(()), + _ => bail!(format!("{attr:?} not supported for interface definition")), + })?; + // Can't be both `[Threadsafe]` and an `[Enum]`. + if attrs.len() > 1 { + bail!("conflicting attributes on interface definition"); + } + Ok(Self(attrs)) + } +} + +impl<T: TryInto<InterfaceAttributes, Error = anyhow::Error>> TryFrom<Option<T>> + for InterfaceAttributes +{ + type Error = anyhow::Error; + fn try_from(value: Option<T>) -> Result<Self, Self::Error> { + match value { + None => Ok(Default::default()), + Some(v) => v.try_into(), + } + } +} + +/// Represents UDL attributes that might appear on a constructor. +/// +/// This supports the `[Throws=ErrorName]` attribute for constructors that can produce +/// an error, and the `[Name=MethodName]` for non-default constructors. +#[derive(Debug, Clone, Checksum, Default)] +pub(super) struct ConstructorAttributes(Vec<Attribute>); + +impl ConstructorAttributes { + pub(super) fn get_throws_err(&self) -> Option<&str> { + self.0.iter().find_map(|attr| match attr { + // This will hopefully return a helpful compilation error + // if the error is not defined. + Attribute::Throws(inner) => Some(inner.as_ref()), + _ => None, + }) + } + + pub(super) fn get_name(&self) -> Option<&str> { + self.0.iter().find_map(|attr| match attr { + Attribute::Name(inner) => Some(inner.as_ref()), + _ => None, + }) + } +} + +impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ConstructorAttributes { + type Error = anyhow::Error; + fn try_from( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + ) -> Result<Self, Self::Error> { + let attrs = parse_attributes(weedle_attributes, |attr| match attr { + Attribute::Throws(_) => Ok(()), + Attribute::Name(_) => Ok(()), + _ => bail!(format!("{attr:?} not supported for constructors")), + })?; + Ok(Self(attrs)) + } +} + +/// Represents UDL attributes that might appear on a method. +/// +/// This supports the `[Throws=ErrorName]` attribute for methods that can produce +/// an error, and the `[Self=ByArc]` attribute for methods that take `Arc<Self>` as receiver. +#[derive(Debug, Clone, Checksum, Default)] +pub(super) struct MethodAttributes(Vec<Attribute>); + +impl MethodAttributes { + pub(super) fn get_throws_err(&self) -> Option<&str> { + self.0.iter().find_map(|attr| match attr { + // This will hopefully return a helpful compilation error + // if the error is not defined. + Attribute::Throws(inner) => Some(inner.as_ref()), + _ => None, + }) + } + + pub(super) fn get_self_by_arc(&self) -> bool { + self.0 + .iter() + .any(|attr| matches!(attr, Attribute::SelfType(SelfType::ByArc))) + } +} + +impl FromIterator<Attribute> for MethodAttributes { + fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + +impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for MethodAttributes { + type Error = anyhow::Error; + fn try_from( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + ) -> Result<Self, Self::Error> { + let attrs = parse_attributes(weedle_attributes, |attr| match attr { + Attribute::SelfType(_) => Ok(()), + Attribute::Throws(_) => Ok(()), + _ => bail!(format!("{attr:?} not supported for methods")), + })?; + Ok(Self(attrs)) + } +} + +impl<T: TryInto<MethodAttributes, Error = anyhow::Error>> TryFrom<Option<T>> for MethodAttributes { + type Error = anyhow::Error; + fn try_from(value: Option<T>) -> Result<Self, Self::Error> { + match value { + None => Ok(Default::default()), + Some(v) => v.try_into(), + } + } +} + +/// Represents the different possible types of method call receiver. +/// +/// Actually we only support one of these right now, `[Self=ByArc]`. +/// We might add more in future, e.g. a `[Self=ByRef]` if there are cases +/// where we need to force the receiver to be taken by reference. +#[derive(Debug, Clone, Checksum)] +pub(super) enum SelfType { + ByArc, // Method receiver is `Arc<Self>`. +} + +impl TryFrom<&weedle::attribute::IdentifierOrString<'_>> for SelfType { + type Error = anyhow::Error; + fn try_from(nm: &weedle::attribute::IdentifierOrString<'_>) -> Result<Self, Self::Error> { + Ok(match nm { + weedle::attribute::IdentifierOrString::Identifier(identifier) => match identifier.0 { + "ByArc" => SelfType::ByArc, + _ => bail!("Unsupported Self Type: {:?}", identifier.0), + }, + weedle::attribute::IdentifierOrString::String(_) => { + bail!("Unsupported Self Type: {:?}", nm) + } + }) + } +} + +/// Represents UDL attributes that might appear on a typedef +/// +/// This supports the `[External="crate_name"]` and `[Custom]` attributes for types. +#[derive(Debug, Clone, Checksum, Default)] +pub(super) struct TypedefAttributes(Vec<Attribute>); + +impl TypedefAttributes { + pub(super) fn get_crate_name(&self) -> String { + self.0 + .iter() + .find_map(|attr| match attr { + Attribute::External(crate_name) => Some(crate_name.clone()), + _ => None, + }) + .expect("must have a crate name") + } + + pub(super) fn is_custom(&self) -> bool { + self.0 + .iter() + .any(|attr| matches!(attr, Attribute::Custom { .. })) + } +} + +impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for TypedefAttributes { + type Error = anyhow::Error; + fn try_from( + weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, + ) -> Result<Self, Self::Error> { + let attrs = parse_attributes(weedle_attributes, |attr| match attr { + Attribute::External { .. } | Attribute::Custom => Ok(()), + _ => bail!(format!("{attr:?} not supported for typedefs")), + })?; + Ok(Self(attrs)) + } +} + +impl<T: TryInto<TypedefAttributes, Error = anyhow::Error>> TryFrom<Option<T>> + for TypedefAttributes +{ + type Error = anyhow::Error; + fn try_from(value: Option<T>) -> Result<Self, Self::Error> { + match value { + None => Ok(Default::default()), + Some(v) => v.try_into(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use weedle::Parse; + + #[test] + fn test_byref() -> Result<()> { + let (_, node) = weedle::attribute::ExtendedAttribute::parse("ByRef").unwrap(); + let attr = Attribute::try_from(&node)?; + assert!(matches!(attr, Attribute::ByRef)); + Ok(()) + } + + #[test] + fn test_enum() -> Result<()> { + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Enum").unwrap(); + let attr = Attribute::try_from(&node)?; + assert!(matches!(attr, Attribute::Enum)); + assert!(attr.is_enum()); + Ok(()) + } + + #[test] + fn test_error() -> Result<()> { + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Error").unwrap(); + let attr = Attribute::try_from(&node)?; + assert!(matches!(attr, Attribute::Error)); + assert!(attr.is_error()); + Ok(()) + } + + #[test] + fn test_name() -> Result<()> { + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Name=Value").unwrap(); + let attr = Attribute::try_from(&node)?; + assert!(matches!(attr, Attribute::Name(nm) if nm == "Value")); + + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Name").unwrap(); + let err = Attribute::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "ExtendedAttributeNoArgs not supported: \"Name\"" + ); + + Ok(()) + } + + #[test] + fn test_selftype() -> Result<()> { + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Self=ByArc").unwrap(); + let attr = Attribute::try_from(&node)?; + assert!(matches!(attr, Attribute::SelfType(SelfType::ByArc))); + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Self=ByMistake").unwrap(); + let err = Attribute::try_from(&node).unwrap_err(); + assert_eq!(err.to_string(), "Unsupported Self Type: \"ByMistake\""); + Ok(()) + } + + #[test] + fn test_threadsafe() -> Result<()> { + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Threadsafe").unwrap(); + let attr = Attribute::try_from(&node)?; + assert!(matches!(attr, Attribute::Threadsafe)); + Ok(()) + } + + #[test] + fn test_throws() -> Result<()> { + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Throws=Name").unwrap(); + let attr = Attribute::try_from(&node)?; + assert!(matches!(attr, Attribute::Throws(nm) if nm == "Name")); + + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Throws").unwrap(); + let err = Attribute::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "ExtendedAttributeNoArgs not supported: \"Throws\"" + ); + + Ok(()) + } + + #[test] + fn test_unsupported() { + let (_, node) = + weedle::attribute::ExtendedAttribute::parse("UnsupportedAttribute").unwrap(); + let err = Attribute::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "ExtendedAttributeNoArgs not supported: \"UnsupportedAttribute\"" + ); + + let (_, node) = + weedle::attribute::ExtendedAttribute::parse("Unsupported=Attribute").unwrap(); + let err = Attribute::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "Attribute identity Identifier not supported: \"Unsupported\"" + ); + } + + #[test] + fn test_other_attributes_not_supported_for_enums() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Error, ByRef]").unwrap(); + let err = EnumAttributes::try_from(&node).unwrap_err(); + assert_eq!(err.to_string(), "ByRef not supported for enums"); + } + + #[test] + fn test_throws_attribute() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap(); + let attrs = FunctionAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.get_throws_err(), Some("Error"))); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); + let attrs = FunctionAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.get_throws_err(), None)); + } + + #[test] + fn test_other_attributes_not_supported_for_functions() { + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, ByRef]").unwrap(); + let err = FunctionAttributes::try_from(&node).unwrap_err(); + assert_eq!(err.to_string(), "ByRef not supported for functions"); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Self=ByArc]").unwrap(); + let err = FunctionAttributes::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "SelfType(ByArc) not supported for functions" + ); + } + + #[test] + fn test_method_attributes() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap(); + let attrs = MethodAttributes::try_from(&node).unwrap(); + assert!(!attrs.get_self_by_arc()); + assert!(matches!(attrs.get_throws_err(), Some("Error"))); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); + let attrs = MethodAttributes::try_from(&node).unwrap(); + assert!(!attrs.get_self_by_arc()); + assert!(attrs.get_throws_err().is_none()); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error]").unwrap(); + let attrs = MethodAttributes::try_from(&node).unwrap(); + assert!(attrs.get_self_by_arc()); + assert!(attrs.get_throws_err().is_some()); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc]").unwrap(); + let attrs = MethodAttributes::try_from(&node).unwrap(); + assert!(attrs.get_self_by_arc()); + assert!(attrs.get_throws_err().is_none()); + } + + #[test] + fn test_constructor_attributes() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap(); + let attrs = ConstructorAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.get_throws_err(), Some("Error"))); + assert!(matches!(attrs.get_name(), None)); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Name=MyFactory]").unwrap(); + let attrs = ConstructorAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.get_throws_err(), None)); + assert!(matches!(attrs.get_name(), Some("MyFactory"))); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Name=MyFactory]") + .unwrap(); + let attrs = ConstructorAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.get_throws_err(), Some("Error"))); + assert!(matches!(attrs.get_name(), Some("MyFactory"))); + } + + #[test] + fn test_other_attributes_not_supported_for_constructors() { + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, ByRef]").unwrap(); + let err = ConstructorAttributes::try_from(&node).unwrap_err(); + assert_eq!(err.to_string(), "ByRef not supported for constructors"); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Self=ByArc]").unwrap(); + let err = ConstructorAttributes::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "SelfType(ByArc) not supported for constructors" + ); + } + + #[test] + fn test_byref_attribute() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[ByRef]").unwrap(); + let attrs = ArgumentAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.by_ref(), true)); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); + let attrs = ArgumentAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.by_ref(), false)); + } + + #[test] + fn test_other_attributes_not_supported_for_arguments() { + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, ByRef]").unwrap(); + let err = ArgumentAttributes::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "Throws(\"Error\") not supported for arguments" + ); + } + + #[test] + fn test_threadsafe_attribute() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Threadsafe]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.threadsafe(), true)); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.threadsafe(), false)); + } + + #[test] + fn test_enum_attribute() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.contains_enum_attr(), true)); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.contains_enum_attr(), false)); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Threadsafe]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.contains_enum_attr(), false)); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Threadsafe, Enum]").unwrap(); + let err = InterfaceAttributes::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "conflicting attributes on interface definition" + ); + } + + #[test] + fn test_other_attributes_not_supported_for_interfaces() { + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Threadsafe, ByRef]").unwrap(); + let err = InterfaceAttributes::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "ByRef not supported for interface definition" + ); + } + + #[test] + fn test_typedef_attribute() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Custom]").unwrap(); + let attrs = TypedefAttributes::try_from(&node).unwrap(); + assert!(attrs.is_custom()); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[External=crate_name]").unwrap(); + let attrs = TypedefAttributes::try_from(&node).unwrap(); + assert!(!attrs.is_custom()); + assert_eq!(attrs.get_crate_name(), "crate_name"); + } + + #[test] + fn test_typedef_attributes_malformed() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Custom=foo]").unwrap(); + let err = TypedefAttributes::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "Attribute identity Identifier not supported: \"Custom\"" + ); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[External]").unwrap(); + let err = TypedefAttributes::try_from(&node).unwrap_err(); + assert_eq!( + err.to_string(), + "ExtendedAttributeNoArgs not supported: \"External\"" + ); + } + + #[test] + fn test_other_attributes_not_supported_for_typedef() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[ByRef]").unwrap(); + let err = TypedefAttributes::try_from(&node).unwrap_err(); + assert_eq!(err.to_string(), "ByRef not supported for typedefs"); + } +} 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..a7164cec5b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs @@ -0,0 +1,169 @@ +/* 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(); +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`CallbackInterface`] member being added to the resulting +//! [`ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # callback interface Example { +//! # string hello(); +//! # }; +//! # "##)?; +//! 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 anyhow::{bail, Result}; +use uniffi_meta::Checksum; + +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::object::Method; +use super::types::{Type, TypeIterator}; +use super::{APIConverter, ComponentInterface}; + +#[derive(Debug, Clone, Checksum)] +pub struct CallbackInterface { + pub(super) name: 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 { + fn new(name: String) -> CallbackInterface { + CallbackInterface { + name, + methods: Default::default(), + ffi_init_callback: Default::default(), + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn type_(&self) -> Type { + Type::CallbackInterface(self.name.clone()) + } + + 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, ci_prefix: &str) { + self.ffi_init_callback.name = format!("ffi_{ci_prefix}_{}_init_callback", 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 APIConverter<CallbackInterface> for weedle::CallbackInterfaceDefinition<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<CallbackInterface> { + if self.attributes.is_some() { + bail!("callback interface attributes are not supported yet"); + } + if self.inheritance.is_some() { + bail!("callback interface inheritance is not supported"); + } + let mut object = CallbackInterface::new(self.identifier.0.to_string()); + for member in &self.members.body { + match member { + weedle::interface::InterfaceMember::Operation(t) => { + let mut method: Method = t.convert(ci)?; + method.object_name = object.name.clone(); + object.methods.push(method); + } + _ => bail!( + "no support for callback interface member type {:?} yet", + member + ), + } + } + Ok(object) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_empty_interface() { + const UDL: &str = r#" + namespace test{}; + // Weird, but allowed. + callback interface Testing {}; + "#; + let ci = ComponentInterface::from_webidl(UDL).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).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..c85e6ecc46 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs @@ -0,0 +1,441 @@ +/* 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" +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Enum`] member being added to the resulting [`ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # enum Example { +//! # "one", +//! # "two" +//! # }; +//! # "##)?; +//! 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); +//! }; +//! # "##)?; +//! # 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); +//! # }; +//! # "##)?; +//! 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>(()) +//! ``` + +use anyhow::{bail, Result}; +use uniffi_meta::Checksum; + +use super::record::Field; +use super::types::{Type, TypeIterator}; +use super::{APIConverter, ComponentInterface}; + +/// 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) variants: Vec<Variant>, + // "Flat" enums do not have variants with associated data. + pub(super) flat: bool, +} + +impl Enum { + pub fn name(&self) -> &str { + &self.name + } + + pub fn type_(&self) -> Type { + // *sigh* at the clone here, the relationship between a ComponentInterface + // and its contained types could use a bit of a cleanup. + Type::Enum(self.name.clone()) + } + + 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)) + } +} + +impl From<uniffi_meta::EnumMetadata> for Enum { + fn from(meta: uniffi_meta::EnumMetadata) -> Self { + let flat = meta.variants.iter().all(|v| v.fields.is_empty()); + Self { + name: meta.name, + variants: meta.variants.into_iter().map(Into::into).collect(), + flat, + } + } +} + +// Note that we have two `APIConverter` impls here - one for the `enum` case +// and one for the `[Enum] interface` case. + +impl APIConverter<Enum> for weedle::EnumDefinition<'_> { + fn convert(&self, _ci: &mut ComponentInterface) -> Result<Enum> { + Ok(Enum { + name: self.identifier.0.to_string(), + variants: self + .values + .body + .list + .iter() + .map::<Result<_>, _>(|v| { + Ok(Variant { + name: v.0.to_string(), + ..Default::default() + }) + }) + .collect::<Result<Vec<_>>>()?, + // Enums declared using the `enum` syntax can never have variants with fields. + flat: true, + }) + } +} + +impl APIConverter<Enum> for weedle::InterfaceDefinition<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Enum> { + if self.inheritance.is_some() { + bail!("interface inheritance is not supported for enum interfaces"); + } + // We don't need to check `self.attributes` here; if calling code has dispatched + // to this impl then we already know there was an `[Enum]` attribute. + Ok(Enum { + name: self.identifier.0.to_string(), + variants: self + .members + .body + .iter() + .map::<Result<Variant>, _>(|member| match member { + weedle::interface::InterfaceMember::Operation(t) => Ok(t.convert(ci)?), + _ => bail!( + "interface member type {:?} not supported in enum interface", + member + ), + }) + .collect::<Result<Vec<_>>>()?, + // Enums declared using the `[Enum] interface` syntax might have variants with fields. + flat: false, + }) + } +} + +/// 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 From<uniffi_meta::VariantMetadata> for Variant { + fn from(meta: uniffi_meta::VariantMetadata) -> Self { + Self { + name: meta.name, + fields: meta.fields.into_iter().map(Into::into).collect(), + } + } +} + +impl APIConverter<Variant> for weedle::interface::OperationInterfaceMember<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Variant> { + if self.special.is_some() { + bail!("special operations not supported"); + } + if let Some(weedle::interface::StringifierOrStatic::Stringifier(_)) = self.modifier { + bail!("stringifiers are not supported"); + } + // OK, so this is a little weird. + // The syntax we use for enum interface members is `Name(type arg, ...);`, which parses + // as an anonymous operation where `Name` is the return type. We re-interpret it to + // use `Name` as the name of the variant. + if self.identifier.is_some() { + bail!("enum interface members must not have a method name"); + } + let name: String = { + use weedle::types::{ + NonAnyType::Identifier, ReturnType, SingleType::NonAny, Type::Single, + }; + match &self.return_type { + ReturnType::Type(Single(NonAny(Identifier(id)))) => id.type_.0.to_owned(), + _ => bail!("enum interface members must have plain identifiers as names"), + } + }; + Ok(Variant { + name, + fields: self + .args + .body + .list + .iter() + .map(|arg| arg.convert(ci)) + .collect::<Result<Vec<_>>>()?, + }) + } +} + +impl APIConverter<Field> for weedle::argument::Argument<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Field> { + match self { + weedle::argument::Argument::Single(t) => t.convert(ci), + weedle::argument::Argument::Variadic(_) => bail!("variadic arguments not supported"), + } + } +} + +impl APIConverter<Field> for weedle::argument::SingleArgument<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Field> { + let type_ = ci.resolve_type_expression(&self.type_)?; + if let Type::Object(_) = type_ { + bail!("Objects cannot currently be used in enum variant data"); + } + if self.default.is_some() { + bail!("enum interface variant fields must not have default values"); + } + if self.attributes.is_some() { + bail!("enum interface variant fields must not have attributes"); + } + // TODO: maybe we should use our own `Field` type here with just name and type, + // rather than appropriating record::Field..? + Ok(Field { + name: self.identifier.0.to_string(), + type_, + default: None, + }) + } +} + +#[cfg(test)] +mod test { + use super::super::ffi::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).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).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.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.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!(!ewd.is_flat()); + 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].type_(), Type::Enum("TestEnum".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(nm)) if nm == "TestEnum")); + 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].type_(), + Type::Enum("TestEnumWithData".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(nm)) if nm == "TestEnumWithData")); + assert!(matches!( + fret.ffi_func().return_type(), + Some(FfiType::RustBuffer(None)) + )); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/error.rs b/third_party/rust/uniffi_bindgen/src/interface/error.rs new file mode 100644 index 0000000000..9aa57255e8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/error.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/. */ + +//! # Error definitions for a `ComponentInterface`. +//! +//! This module converts error 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 {}; +//! [Error] +//! enum Example { +//! "one", +//! "two" +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Error`] member with fieldless variants being added to the resulting [`ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Error] +//! # enum Example { +//! # "one", +//! # "two" +//! # }; +//! # "##)?; +//! let err = ci.get_error_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); +//! # 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); +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Error`] member with variants that have fields being added to the resulting [`ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Error] +//! # interface Example { +//! # one(); +//! # two(string reason); +//! # three(i32 x, i32 y); +//! # }; +//! # "##)?; +//! let err = ci.get_error_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); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::Result; +use uniffi_meta::Checksum; + +use super::enum_::{Enum, Variant}; +use super::types::{Type, TypeIterator}; +use super::{APIConverter, ComponentInterface}; + +/// Represents an Error that might be thrown by functions/methods in the component interface. +/// +/// Errors are represented in the UDL as enums with the special `[Error]` attribute, but +/// they're handled in the FFI very differently. We create them in `uniffi::call_with_result()` if +/// the wrapped function returns an `Err` value +/// struct and assign an integer error code to each variant. +#[derive(Debug, Clone, PartialEq, Eq, Checksum)] +pub struct Error { + pub name: String, + enum_: Enum, +} + +impl Error { + pub fn from_enum(enum_: Enum) -> Self { + Self { + name: enum_.name.clone(), + enum_, + } + } + + pub fn type_(&self) -> Type { + // *sigh* at the clone here, the relationship between a ComponentInterface + // and its contained types could use a bit of a cleanup. + Type::Error(self.name.clone()) + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn wrapped_enum(&self) -> &Enum { + &self.enum_ + } + + pub fn variants(&self) -> &[Variant] { + self.enum_.variants() + } + + pub fn is_flat(&self) -> bool { + self.enum_.is_flat() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + self.wrapped_enum().iter_types() + } +} + +impl From<uniffi_meta::ErrorMetadata> for Error { + fn from(meta: uniffi_meta::ErrorMetadata) -> Self { + Self { + name: meta.name.clone(), + enum_: Enum { + name: meta.name, + variants: meta.variants.into_iter().map(Into::into).collect(), + flat: meta.flat, + }, + } + } +} + +impl APIConverter<Error> for weedle::EnumDefinition<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Error> { + Ok(Error::from_enum(APIConverter::<Enum>::convert(self, ci)?)) + } +} + +impl APIConverter<Error> for weedle::InterfaceDefinition<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Error> { + Ok(Error::from_enum(APIConverter::<Enum>::convert(self, ci)?)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_variants() { + const UDL: &str = r#" + namespace test{}; + [Error] + enum Testing { "one", "two", "three" }; + "#; + let ci = ComponentInterface::from_webidl(UDL).unwrap(); + assert_eq!(ci.error_definitions().count(), 1); + let error = ci.get_error_definition("Testing").unwrap(); + assert_eq!( + error + .variants() + .iter() + .map(|v| v.name()) + .collect::<Vec<&str>>(), + vec!("one", "two", "three") + ); + assert!(error.is_flat()); + } + + #[test] + fn test_duplicate_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).unwrap(); + assert_eq!(ci.error_definitions().count(), 1); + assert_eq!( + ci.get_error_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).unwrap(); + assert_eq!(ci.error_definitions().count(), 1); + let error: &Error = ci.get_error_definition("Testing").unwrap(); + assert_eq!( + error + .variants() + .iter() + .map(|v| v.name()) + .collect::<Vec<&str>>(), + vec!("One", "Two") + ); + assert!(!error.is_flat()); + } +} 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..e1f9fe9737 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs @@ -0,0 +1,102 @@ +/* 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 [`super::types::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). +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +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, + /// A pointer to a single function in to the foreign language. + /// This function contains all the machinery to make callbacks work on the foreign language side. + ForeignCallback, + // 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. +} + +/// 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, Default, Clone)] +pub struct FfiFunction { + pub(super) name: String, + pub(super) arguments: Vec<FfiArgument>, + pub(super) return_type: Option<FfiType>, +} + +impl FfiFunction { + pub fn name(&self) -> &str { + &self.name + } + pub fn arguments(&self) -> Vec<&FfiArgument> { + self.arguments.iter().collect() + } + pub fn return_type(&self) -> Option<&FfiType> { + self.return_type.as_ref() + } +} + +/// 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..61724816cb --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/function.rs @@ -0,0 +1,288 @@ +/* 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(); +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Function`] member being added to the resulting [`ComponentInterface`]: +//! +//! ``` +//! # use uniffi_bindgen::interface::Type; +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example { +//! # string hello(); +//! # }; +//! # "##)?; +//! 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 std::convert::TryFrom; + +use anyhow::{bail, Result}; +use uniffi_meta::Checksum; + +use super::attributes::{ArgumentAttributes, Attribute, FunctionAttributes}; +use super::ffi::{FfiArgument, FfiFunction}; +use super::literal::{convert_default_value, Literal}; +use super::types::{Type, TypeIterator}; +use super::{convert_type, APIConverter, ComponentInterface}; + +/// 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) 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: FunctionAttributes, +} + +impl Function { + 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 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 derive_ffi_func(&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.name.is_empty() { + self.ffi_func.name = format!("{ci_prefix}_{}", self.name); + } + + self.ffi_func.arguments = self.arguments.iter().map(|arg| arg.into()).collect(); + self.ffi_func.return_type = self.return_type.as_ref().map(|rt| rt.into()); + Ok(()) + } +} + +impl From<uniffi_meta::FnParamMetadata> for Argument { + fn from(meta: uniffi_meta::FnParamMetadata) -> Self { + Argument { + name: meta.name, + type_: convert_type(&meta.ty), + by_ref: false, + optional: false, + default: None, + } + } +} + +impl From<uniffi_meta::FnMetadata> for Function { + fn from(meta: uniffi_meta::FnMetadata) -> 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, + arguments, + return_type, + ffi_func, + attributes: meta.throws.map(Attribute::Throws).into_iter().collect(), + } + } +} + +impl APIConverter<Function> for weedle::namespace::NamespaceMember<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Function> { + match self { + weedle::namespace::NamespaceMember::Operation(f) => f.convert(ci), + _ => bail!("no support for namespace member type {:?} yet", self), + } + } +} + +impl APIConverter<Function> for weedle::namespace::OperationNamespaceMember<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Function> { + let return_type = ci.resolve_return_type_expression(&self.return_type)?; + Ok(Function { + name: match self.identifier { + None => bail!("anonymous functions are not supported {:?}", self), + Some(id) => id.0.to_string(), + }, + return_type, + arguments: self.args.body.list.convert(ci)?, + ffi_func: Default::default(), + attributes: FunctionAttributes::try_from(self.attributes.as_ref())?, + }) + } +} + +/// 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 type_(&self) -> &Type { + &self.type_ + } + + pub fn by_ref(&self) -> bool { + self.by_ref + } + + pub fn default_value(&self) -> Option<&Literal> { + self.default.as_ref() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + self.type_.iter_types() + } +} + +impl From<&Argument> for FfiArgument { + fn from(a: &Argument) -> FfiArgument { + FfiArgument { + name: a.name.clone(), + type_: (&a.type_).into(), + } + } +} + +impl APIConverter<Argument> for weedle::argument::Argument<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Argument> { + match self { + weedle::argument::Argument::Single(t) => t.convert(ci), + weedle::argument::Argument::Variadic(_) => bail!("variadic arguments not supported"), + } + } +} + +impl APIConverter<Argument> for weedle::argument::SingleArgument<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Argument> { + let type_ = ci.resolve_type_expression(&self.type_)?; + let default = match self.default { + None => None, + Some(v) => Some(convert_default_value(&v.value, &type_)?), + }; + let by_ref = ArgumentAttributes::try_from(self.attributes.as_ref())?.by_ref(); + Ok(Argument { + name: self.identifier.0.to_string(), + type_, + by_ref, + optional: self.optional.is_some(), + default, + }) + } +} + +#[cfg(test)] +mod test { + 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; + }; + "##, + )?; + + 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().canonical_name(), + "SequenceOptionalstring" + ); + assert!(matches!(func2.throws_type(), Some(Type::Error(s)) if s == "TestError")); + assert_eq!(func2.arguments().len(), 2); + assert_eq!(func2.arguments()[0].name(), "arg1"); + assert_eq!(func2.arguments()[0].type_().canonical_name(), "u32"); + assert_eq!(func2.arguments()[1].name(), "arg2"); + assert_eq!( + func2.arguments()[1].type_().canonical_name(), + "TypeTestDict" + ); + Ok(()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/literal.rs b/third_party/rust/uniffi_bindgen/src/interface/literal.rs new file mode 100644 index 0000000000..da02613684 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/literal.rs @@ -0,0 +1,186 @@ +/* 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/. */ + +//! # Support for literal values. +//! +//! This module provides support for interpreting literal values from the UDL, +//! which appear in places such as default arguments. + +use anyhow::{bail, Result}; +use uniffi_meta::Checksum; + +use super::types::Type; + +// Represents a literal value. +// Used for e.g. default argument values. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Checksum)] +pub enum Literal { + Boolean(bool), + String(String), + // Integers are represented as the widest representation we can. + // Number formatting vary with language and radix, so we avoid a lot of parsing and + // formatting duplication by using only signed and unsigned variants. + UInt(u64, Radix, Type), + Int(i64, Radix, Type), + // Pass the string representation through as typed in the UDL. + // This avoids a lot of uncertainty around precision and accuracy, + // though bindings for languages less sophisticated number parsing than WebIDL + // will have to do extra work. + Float(String, Type), + Enum(String, Type), + EmptySequence, + EmptyMap, + Null, +} + +// Represent the radix of integer literal values. +// We preserve the radix into the generated bindings for readability reasons. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Checksum)] +pub enum Radix { + Decimal = 10, + Octal = 8, + Hexadecimal = 16, +} + +pub(super) fn convert_default_value( + default_value: &weedle::literal::DefaultValue<'_>, + type_: &Type, +) -> Result<Literal> { + fn convert_integer(literal: &weedle::literal::IntegerLit<'_>, type_: &Type) -> Result<Literal> { + let (string, radix) = match literal { + weedle::literal::IntegerLit::Dec(v) => (v.0, Radix::Decimal), + weedle::literal::IntegerLit::Hex(v) => (v.0, Radix::Hexadecimal), + weedle::literal::IntegerLit::Oct(v) => (v.0, Radix::Octal), + }; + // This is the radix of the parsed number, passed to `from_str_radix`. + let src_radix = radix as u32; + // This radix tells the backends how to represent the number in the output languages. + let dest_radix = if string == "0" || string.starts_with('-') { + // 1. weedle parses "0" as an octal literal, but we most likely want to treat this as a decimal. + // 2. Explicitly negatively signed hex numbers won't convert via i64 very well if they're not 64 bit. + // For ease of implementation, output will use decimal. + Radix::Decimal + } else { + radix + }; + + // Clippy seems to think we should be using `strip_prefix` here, but + // it seems confused as to what this is actually doing. + #[allow(clippy::manual_strip)] + let string = if string.starts_with('-') { + ("-".to_string() + string[1..].trim_start_matches("0x")).to_lowercase() + } else { + string.trim_start_matches("0x").to_lowercase() + }; + + Ok(match type_ { + Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => Literal::Int( + i64::from_str_radix(&string, src_radix)?, + dest_radix, + type_.clone(), + ), + Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => Literal::UInt( + u64::from_str_radix(&string, src_radix)?, + dest_radix, + type_.clone(), + ), + + _ => bail!("Cannot coerce literal {} into a non-integer type", string), + }) + } + + fn convert_float(literal: &weedle::literal::FloatLit<'_>, type_: &Type) -> Result<Literal> { + let string = match literal { + weedle::literal::FloatLit::Value(v) => v.0, + + _ => bail!("Infinity and NaN is not currently supported"), + }; + + Ok(match type_ { + Type::Float32 | Type::Float64 => Literal::Float(string.to_string(), type_.clone()), + _ => bail!("Cannot coerce literal {} into a non-float type", string), + }) + } + + Ok(match (default_value, type_) { + (weedle::literal::DefaultValue::Boolean(b), Type::Boolean) => Literal::Boolean(b.0), + (weedle::literal::DefaultValue::String(s), Type::String) => { + // Note that weedle doesn't parse escaped double quotes. + // Keeping backends using double quotes (possible for all to date) + // means we don't need to escape single quotes. But we haven't spent a lot of time + // trying to break default values with weird escapes and quotes. + Literal::String(s.0.to_string()) + } + (weedle::literal::DefaultValue::EmptyArray(_), Type::Sequence(_)) => Literal::EmptySequence, + (weedle::literal::DefaultValue::String(s), Type::Enum(_)) => { + Literal::Enum(s.0.to_string(), type_.clone()) + } + (weedle::literal::DefaultValue::Null(_), Type::Optional(_)) => Literal::Null, + (_, Type::Optional(inner_type)) => convert_default_value(default_value, inner_type)?, + + // We'll ensure the type safety in the convert_* number methods. + (weedle::literal::DefaultValue::Integer(i), _) => convert_integer(i, type_)?, + (weedle::literal::DefaultValue::Float(i), _) => convert_float(i, type_)?, + + _ => bail!("No support for {:?} literal yet", default_value), + }) +} + +#[cfg(test)] +mod test { + use super::*; + use weedle::Parse; + + fn parse_and_convert(expr: &str, t: Type) -> Result<Literal> { + let (_, node) = weedle::literal::DefaultValue::parse(expr).unwrap(); + convert_default_value(&node, &t) + } + + #[test] + fn test_default_value_conversion() -> Result<()> { + assert!(matches!( + parse_and_convert("0", Type::UInt8)?, + Literal::UInt(0, Radix::Decimal, Type::UInt8) + )); + assert!(matches!( + parse_and_convert("-12", Type::Int32)?, + Literal::Int(-12, Radix::Decimal, Type::Int32) + )); + assert!( + matches!(parse_and_convert("3.14", Type::Float32)?, Literal::Float(v, Type::Float32) if v == "3.14") + ); + assert!(matches!( + parse_and_convert("false", Type::Boolean)?, + Literal::Boolean(false) + )); + assert!( + matches!(parse_and_convert("\"TEST\"", Type::String)?, Literal::String(v) if v == "TEST") + ); + assert!( + matches!(parse_and_convert("\"one\"", Type::Enum("E".into()))?, Literal::Enum(v, Type::Enum(e)) if v == "one" && e == "E") + ); + assert!(matches!( + parse_and_convert("[]", Type::Sequence(Box::new(Type::String)))?, + Literal::EmptySequence + )); + assert!(matches!( + parse_and_convert("null", Type::Optional(Box::new(Type::String)))?, + Literal::Null + )); + Ok(()) + } + #[test] + fn test_error_on_type_mismatch() { + assert_eq!( + parse_and_convert("0", Type::Boolean) + .unwrap_err() + .to_string(), + "Cannot coerce literal 0 into a non-integer type" + ); + assert!(parse_and_convert("{}", Type::Boolean) + .unwrap_err() + .to_string() + .starts_with("No support for")); + } +} 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..3daf50ef4a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/mod.rs @@ -0,0 +1,1218 @@ +/* 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, HashSet}, + convert::TryFrom, + iter, +}; + +use anyhow::{bail, ensure, Result}; + +pub mod types; +pub use types::Type; +use types::{TypeIterator, TypeUniverse}; + +mod attributes; +mod callbacks; +pub use callbacks::CallbackInterface; +mod enum_; +pub use enum_::Enum; +mod error; +pub use error::Error; +mod function; +pub use function::{Argument, Function}; +mod literal; +pub use literal::{Literal, Radix}; +mod namespace; +pub use namespace::Namespace; +mod object; +pub use object::{Constructor, Method, Object}; +mod record; +pub use record::{Field, Record}; + +pub mod ffi; +pub use ffi::{FfiArgument, FfiFunction, FfiType}; +use uniffi_meta::{Checksum, FnMetadata, MethodMetadata, ObjectMetadata}; + +/// This needs to match the major/minor version of the `uniffi` crate. See +/// `docs/uniffi-versioning.md` for details. +/// +/// Once we get to 1.0, then we should reformat this to only include the major version number. +const UNIFFI_CONTRACT_VERSION: &str = "0.22"; + +/// 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, Checksum)] +pub struct ComponentInterface { + /// This always points to `UNIFFI_CONTRACT_VERSION`. By including it in the checksum, we + /// prevent consumers from combining scaffolding and bindings that were created with different + /// `uniffi` versions. + uniffi_version: &'static str, + /// 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. + #[checksum_ignore] + pub(super) types: TypeUniverse, + /// The unique prefix that we'll use for namespacing when exposing this component's API. + namespace: String, + /// The internal unique prefix used to namespace FFI symbols + #[checksum_ignore] + ffi_namespace: String, + /// 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>, + errors: BTreeMap<String, Error>, +} + +impl ComponentInterface { + /// Parse a `ComponentInterface` from a string containing a WebIDL definition. + pub fn from_webidl(idl: &str) -> Result<Self> { + let mut ci = Self { + uniffi_version: UNIFFI_CONTRACT_VERSION, + ..Default::default() + }; + // There's some lifetime thing with the errors returned from weedle::Definitions::parse + // that my own lifetime is too short to worry about figuring out; unwrap and move on. + + // Note we use `weedle::Definitions::parse` instead of `weedle::parse` so + // on parse errors we can see how far weedle got, which helps locate the problem. + use weedle::Parse; // this trait must be in scope for parse to work. + let (remaining, defns) = weedle::Definitions::parse(idl.trim()).unwrap(); + if !remaining.is_empty() { + println!("Error parsing the IDL. Text remaining to be parsed is:"); + println!("{remaining}"); + bail!("parse error"); + } + // Unconditionally add the String type, which is used by the panic handling + ci.types.add_known_type(&Type::String)?; + // We process the WebIDL definitions in two passes. + // First, go through and look for all the named types. + ci.types.add_type_definitions_from(defns.as_slice())?; + // With those names resolved, we can build a complete representation of the API. + APIBuilder::process(&defns, &mut ci)?; + + // The FFI namespace must not be computed on the fly because it could otherwise be + // influenced by things added later from proc-macro metadata. Those have their own + // namespacing mechanism. + assert!(!ci.namespace.is_empty()); + ci.ffi_namespace = format!("{}_{:x}", ci.namespace, ci.checksum()); + + // The following two methods will be called later anyways, but we call them here because + // it's convenient for UDL-only tests. + ci.check_consistency()?; + // Now that the high-level API is settled, we can derive the low-level FFI. + ci.derive_ffi_funcs()?; + + Ok(ci) + } + + /// 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.namespace.as_str() + } + + /// 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 Error type in the interface. + pub fn error_definitions(&self) -> impl Iterator<Item = &Error> { + self.errors.values() + } + + /// Get an Error definition by name, or None if no such Error is defined. + pub fn get_error_definition(&self, name: &str) -> Option<&Error> { + self.errors.get(name) + } + + /// 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, error: &Error) -> bool { + // We can and should always generate read() methods for fielded errors + let fielded = !error.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(error.type_())); + + fielded || used_in_callback_interface + } + + /// Get details about all `Type::External` types + pub fn iter_external_types(&self) -> impl Iterator<Item = (&String, &String)> { + self.types.iter_known_types().filter_map(|t| match t { + Type::External { name, crate_name } => Some((name, crate_name)), + _ => 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(_, _))) + } + + /// Calculate a numeric checksum for this ComponentInterface. + /// + /// The checksum can be used to guard against accidentally using foreign-language bindings + /// generated from one version of an interface with the compiled Rust code from a different + /// version of that interface. It offers the following properties: + /// + /// - Two ComponentIntefaces generated from the same WebIDL file, using the same version of uniffi + /// and the same version of Rust, will always have the same checksum value. + /// - Two ComponentInterfaces will, with high probability, have different checksum values if: + /// - They were generated from two different WebIDL files. + /// - They were generated by two different versions of uniffi + /// + /// Note that this is designed to prevent accidents, not attacks, so there is no need for the + /// checksum to be cryptographically secure. + pub fn checksum(&self) -> u16 { + uniffi_meta::checksum(self) + } + + /// The namespace to use in FFI-level function definitions. + /// + /// The value returned by this method is used as a prefix to namespace all UDL-defined FFI + /// functions used in this ComponentInterface. + /// + /// Since these names are an internal implementation detail that is not typically visible to + /// consumers, we take the opportunity to add an additional safety guard by including a 4-hex-char + /// checksum in each name. If foreign-language bindings attempt to load and use a version of the + /// Rust code compiled from a different UDL definition than the one used for the bindings themselves, + /// then there is a high probability of checksum mismatch and they will fail to link against the + /// compiled Rust code. The result will be an ugly inscrutable link-time error, but that is a lot + /// better than triggering potentially arbitrary memory unsafety! + pub fn ffi_namespace(&self) -> &str { + assert!(!self.ffi_namespace.is_empty()); + &self.ffi_namespace + } + + /// 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()), + arguments: vec![FfiArgument { + name: "size".to_string(), + type_: FfiType::Int32, + }], + return_type: Some(FfiType::RustBuffer(None)), + } + } + + /// 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()), + arguments: vec![FfiArgument { + name: "bytes".to_string(), + type_: FfiType::ForeignBytes, + }], + return_type: Some(FfiType::RustBuffer(None)), + } + } + + /// 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()), + arguments: vec![FfiArgument { + name: "buf".to_string(), + type_: FfiType::RustBuffer(None), + }], + return_type: None, + } + } + + /// 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()), + 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)), + } + } + + /// 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()) + } + + /// 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() + } + + // + // Private methods for building a ComponentInterface. + // + + /// Resolve a weedle type expression into a `Type`. + /// + /// This method uses the current state of our `TypeUniverse` to turn a weedle type expression + /// into a concrete `Type` (or error if the type expression is not well defined). It abstracts + /// away the complexity of walking weedle's type struct hierarchy by dispatching to the `TypeResolver` + /// trait. + fn resolve_type_expression<T: types::TypeResolver>(&mut self, expr: T) -> Result<Type> { + self.types.resolve_type_expression(expr) + } + + /// Resolve a weedle `ReturnType` expression into an optional `Type`. + /// + /// This method is similar to `resolve_type_expression`, but tailored specifically for return types. + /// It can return `None` to represent a non-existent return value. + fn resolve_return_type_expression( + &mut self, + expr: &weedle::types::ReturnType<'_>, + ) -> Result<Option<Type>> { + Ok(match expr { + weedle::types::ReturnType::Undefined(_) => None, + weedle::types::ReturnType::Type(t) => { + // Older versions of WebIDL used `void` for functions that don't return a value, + // while newer versions have replaced it with `undefined`. Special-case this for + // backwards compatibility for our consumers. + use weedle::types::{NonAnyType::Identifier, SingleType::NonAny, Type::Single}; + match t { + Single(NonAny(Identifier(id))) if id.type_.0 == "void" => None, + _ => Some(self.resolve_type_expression(t)?), + } + } + }) + } + + /// Called by `APIBuilder` impls to add a newly-parsed namespace definition to the `ComponentInterface`. + fn add_namespace_definition(&mut self, defn: Namespace) -> Result<()> { + if !self.namespace.is_empty() { + bail!("duplicate namespace definition"); + } + self.namespace = defn.name; + Ok(()) + } + + /// 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) => { + 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(()) + } + + /// Called by `APIBuilder` impls to add 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) => { + 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(()) + } + + fn add_function_impl(&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 !matches!(self.types.get_type_definition(defn.name()), None) { + bail!("Conflicting type definition for \"{}\"", defn.name()); + } + self.functions.push(defn); + + Ok(()) + } + + /// Called by `APIBuilder` impls to add a newly-parsed function definition to the `ComponentInterface`. + fn add_function_definition(&mut self, defn: Function) -> Result<()> { + for arg in &defn.arguments { + self.types.add_known_type(&arg.type_)?; + } + if let Some(ty) = &defn.return_type { + self.types.add_known_type(ty)?; + } + + self.add_function_impl(defn) + } + + pub(super) fn add_fn_meta(&mut self, meta: FnMetadata) -> Result<()> { + self.add_function_impl(meta.into()) + } + + pub(super) fn add_method_meta(&mut self, meta: MethodMetadata) { + let object = get_or_insert_object(&mut self.objects, &meta.self_name); + let defn: Method = meta.into(); + object.methods.push(defn); + } + + pub(super) fn add_object_free_fn(&mut self, meta: ObjectMetadata) { + let object = get_or_insert_object(&mut self.objects, &meta.name); + object.ffi_func_free.name = meta.free_ffi_symbol_name(); + } + + /// Called by `APIBuilder` impls to add a newly-parsed object definition to the `ComponentInterface`. + fn add_object_definition(&mut self, defn: Object) { + // Note that there will be no duplicates thanks to the previous type-finding pass. + self.objects.push(defn); + } + + /// Called by `APIBuilder` impls to add a newly-parsed callback interface definition to the `ComponentInterface`. + fn add_callback_interface_definition(&mut self, defn: CallbackInterface) { + // Note that there will be no duplicates thanks to the previous type-finding pass. + self.callback_interfaces.push(defn); + } + + /// Called by `APIBuilder` impls to add a newly-parsed error definition to the `ComponentInterface`. + pub(super) fn add_error_definition(&mut self, defn: Error) -> Result<()> { + match self.errors.entry(defn.name().to_owned()) { + Entry::Vacant(v) => { + v.insert(defn); + } + Entry::Occupied(o) => { + let existing_def = o.get(); + if defn != *existing_def { + bail!( + "Mismatching definition for error `{}`!\n\ + existing definition: {existing_def:#?},\n\ + new definition: {defn:#?}", + defn.name(), + ); + } + } + } + + Ok(()) + } + + /// Resolve unresolved types within proc-macro function / method signatures. + pub fn resolve_types(&mut self) -> Result<()> { + fn handle_unresolved_in( + ty: &mut Type, + f: impl Fn(&str) -> Result<Type> + Clone, + ) -> Result<()> { + match ty { + Type::Unresolved { name } => { + *ty = f(name)?; + } + Type::Optional(inner) => { + handle_unresolved_in(inner, f)?; + } + Type::Sequence(inner) => { + handle_unresolved_in(inner, f)?; + } + Type::Map(k, v) => { + handle_unresolved_in(k, f.clone())?; + handle_unresolved_in(v, f)?; + } + _ => {} + } + + Ok(()) + } + + let fn_sig_types = self.functions.iter_mut().flat_map(|fun| { + fun.arguments + .iter_mut() + .map(|arg| &mut arg.type_) + .chain(&mut fun.return_type) + }); + let method_sig_types = self.objects.iter_mut().flat_map(|obj| { + obj.methods.iter_mut().flat_map(|m| { + m.arguments + .iter_mut() + .map(|arg| &mut arg.type_) + .chain(&mut m.return_type) + }) + }); + + let record_fields_types = self + .records + .values_mut() + .flat_map(|r| r.fields.iter_mut().map(|f| &mut f.type_)); + let enum_fields_types = self.enums.values_mut().flat_map(|e| { + e.variants + .iter_mut() + .flat_map(|r| r.fields.iter_mut().map(|f| &mut f.type_)) + }); + + let possibly_unresolved_types = fn_sig_types + .chain(method_sig_types) + .chain(record_fields_types) + .chain(enum_fields_types); + + for ty in possibly_unresolved_types { + handle_unresolved_in(ty, |unresolved_ty_name| { + match self.types.get_type_definition(unresolved_ty_name) { + Some(def) => { + assert!( + !matches!(&def, Type::Unresolved { .. }), + "unresolved types must not be part of TypeUniverse" + ); + Ok(def) + } + None => bail!("Failed to resolve type `{unresolved_ty_name}`"), + } + })?; + + // The proc-macro scanning metadata code doesn't add known types + // when they could contain unresolved types, so we have to do it + // here after replacing unresolveds. + self.types.add_known_type(ty)?; + } + + 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"); + } + + // To keep codegen tractable, enum variant names must not shadow type names. + for e in self.enums.values() { + for variant in &e.variants { + if self.types.get_type_definition(variant.name()).is_some() { + bail!( + "Enum variant names must not shadow type names: \"{}\"", + variant.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", + ); + } + Type::Unresolved { name } => { + bail!("Type `{name}` should be resolved at this point"); + } + _ => {} + } + } + + 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<()> { + let ci_prefix = self.ffi_namespace().to_owned(); + for func in self.functions.iter_mut() { + func.derive_ffi_func(&ci_prefix)?; + } + for obj in self.objects.iter_mut() { + obj.derive_ffi_funcs(&ci_prefix)?; + } + for callback in self.callback_interfaces.iter_mut() { + callback.derive_ffi_funcs(&ci_prefix); + } + Ok(()) + } +} + +fn get_or_insert_object<'a>(objects: &'a mut Vec<Object>, name: &str) -> &'a mut Object { + // The find-based way of writing this currently runs into a borrow checker + // error, so we use position + match objects.iter_mut().position(|o| o.name == name) { + Some(idx) => &mut objects[idx], + None => { + objects.push(Object::new(name.to_owned())); + objects.last_mut().unwrap() + } + } +} + +/// 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(nm) + | Type::Enum(nm) + | Type::Error(nm) + | Type::Object(nm) + | Type::CallbackInterface(nm) => { + if !self.seen.contains(nm.as_str()) { + self.pending.push(type_); + self.seen.insert(nm.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(nm) => self.ci.get_record_definition(nm).map(Record::iter_types), + Type::Enum(nm) => self.ci.get_enum_definition(nm).map(Enum::iter_types), + Type::Error(nm) => self.ci.get_error_definition(nm).map(Error::iter_types), + Type::Object(nm) => self.ci.get_object_definition(nm).map(Object::iter_types), + Type::CallbackInterface(nm) => self + .ci + .get_callback_interface_definition(nm) + .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() + } + } +} + +/// Trait to help build a `ComponentInterface` from WedIDL syntax nodes. +/// +/// This trait does structural matching on the various weedle AST nodes and +/// uses them to build up the records, enums, objects etc in the provided +/// `ComponentInterface`. +trait APIBuilder { + fn process(&self, ci: &mut ComponentInterface) -> Result<()>; +} + +/// Add to a `ComponentInterface` from a list of weedle definitions, +/// by processing each in turn. +impl<T: APIBuilder> APIBuilder for Vec<T> { + fn process(&self, ci: &mut ComponentInterface) -> Result<()> { + for item in self { + item.process(ci)?; + } + Ok(()) + } +} + +/// Add to a `ComponentInterface` from a weedle definition. +/// This is conceptually the root of the parser, and dispatches to implementations +/// for the various specific WebIDL types that we support. +impl APIBuilder for weedle::Definition<'_> { + fn process(&self, ci: &mut ComponentInterface) -> Result<()> { + match self { + weedle::Definition::Namespace(d) => d.process(ci)?, + weedle::Definition::Enum(d) => { + // We check if the enum represents an error... + let attrs = attributes::EnumAttributes::try_from(d.attributes.as_ref())?; + if attrs.contains_error_attr() { + let err = d.convert(ci)?; + ci.add_error_definition(err)?; + } else { + let e = d.convert(ci)?; + ci.add_enum_definition(e)?; + } + } + weedle::Definition::Dictionary(d) => { + let rec = d.convert(ci)?; + ci.add_record_definition(rec)?; + } + weedle::Definition::Interface(d) => { + let attrs = attributes::InterfaceAttributes::try_from(d.attributes.as_ref())?; + if attrs.contains_enum_attr() { + let e = d.convert(ci)?; + ci.add_enum_definition(e)?; + } else if attrs.contains_error_attr() { + let e = d.convert(ci)?; + ci.add_error_definition(e)?; + } else { + let obj = d.convert(ci)?; + ci.add_object_definition(obj); + } + } + weedle::Definition::CallbackInterface(d) => { + let obj = d.convert(ci)?; + ci.add_callback_interface_definition(obj); + } + // everything needed for typedefs is done in finder.rs. + weedle::Definition::Typedef(_) => {} + _ => bail!("don't know how to deal with {:?}", self), + } + Ok(()) + } +} + +/// Trait to help convert WedIDL syntax nodes into `ComponentInterface` objects. +/// +/// This trait does structural matching on the various weedle AST nodes and converts +/// them into appropriate structs that we can use to build up the contents of a +/// `ComponentInterface`. It is basically the `TryFrom` trait except that the conversion +/// always happens in the context of a given `ComponentInterface`, which is used for +/// resolving e.g. type definitions. +/// +/// The difference between this trait and `APIBuilder` is that `APIConverter` treats the +/// `ComponentInterface` as a read-only data source for resolving types, while `APIBuilder` +/// actually mutates the `ComponentInterface` to add new definitions. +trait APIConverter<T> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<T>; +} + +/// Convert a list of weedle items into a list of `ComponentInterface` items, +/// by doing a direct item-by-item mapping. +impl<U, T: APIConverter<U>> APIConverter<Vec<U>> for Vec<T> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Vec<U>> { + self.iter().map(|v| v.convert(ci)).collect::<Result<_>>() + } +} + +fn convert_type(s: &uniffi_meta::Type) -> Type { + use uniffi_meta::Type as Ty; + + match s { + Ty::U8 => Type::UInt8, + Ty::U16 => Type::UInt16, + Ty::U32 => Type::UInt32, + Ty::U64 => Type::UInt64, + Ty::I8 => Type::Int8, + Ty::I16 => Type::Int16, + Ty::I32 => Type::Int32, + Ty::I64 => Type::Int64, + Ty::F32 => Type::Float32, + Ty::F64 => Type::Float64, + Ty::Bool => Type::Boolean, + Ty::String => Type::String, + Ty::Option { inner_type } => Type::Optional(convert_type(inner_type).into()), + Ty::Vec { inner_type } => Type::Sequence(convert_type(inner_type).into()), + Ty::HashMap { + key_type, + value_type, + } => Type::Map( + convert_type(key_type).into(), + convert_type(value_type).into(), + ), + Ty::ArcObject { object_name } => Type::Object(object_name.clone()), + Ty::Unresolved { name } => Type::Unresolved { name: name.clone() }, + } +} + +#[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. + + const UDL1: &str = r#" + namespace foobar{}; + enum Test { + "test_me", + }; + "#; + + const UDL2: &str = r#" + namespace hello { + u64 world(); + }; + dictionary Test { + boolean me; + }; + "#; + + #[test] + fn test_checksum_always_matches_for_same_webidl() { + for udl in &[UDL1, UDL2] { + let ci1 = ComponentInterface::from_webidl(udl).unwrap(); + let ci2 = ComponentInterface::from_webidl(udl).unwrap(); + assert_eq!(ci1.checksum(), ci2.checksum()); + } + } + + #[test] + fn test_checksum_differs_for_different_webidl() { + // There is a small probability of this test spuriously failing due to hash collision. + // If it happens often enough to be a problem, probably this whole "checksum" thing + // is not working out as intended. + let ci1 = ComponentInterface::from_webidl(UDL1).unwrap(); + let ci2 = ComponentInterface::from_webidl(UDL2).unwrap(); + assert_ne!(ci1.checksum(), ci2.checksum()); + } + + #[test] + fn test_checksum_differs_for_different_uniffi_version() { + // There is a small probability of this test spuriously failing due to hash collision. + // If it happens often enough to be a problem, probably this whole "checksum" thing + // is not working out as intended. + for udl in &[UDL1, UDL2] { + let ci1 = ComponentInterface::from_webidl(udl).unwrap(); + let mut ci2 = ComponentInterface::from_webidl(udl).unwrap(); + ci2.uniffi_version = "99.99"; + assert_ne!(ci1.checksum(), ci2.checksum()); + } + } + + #[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).unwrap_err(); + assert_eq!( + err.to_string(), + "Conflicting type definition for `Testing`! \ + existing definition: Object(\"Testing\"), \ + new definition: Record(\"Testing\")" + ); + + const UDL2: &str = r#" + namespace test{}; + enum Testing { + "one", "two" + }; + [Error] + enum Testing { "three", "four" }; + "#; + let err = ComponentInterface::from_webidl(UDL2).unwrap_err(); + assert_eq!( + err.to_string(), + "Conflicting type definition for `Testing`! \ + existing definition: Enum(\"Testing\"), \ + new definition: Error(\"Testing\")" + ); + + const UDL3: &str = r#" + namespace test{ + u32 Testing(); + }; + enum Testing { + "one", "two" + }; + "#; + let err = ComponentInterface::from_webidl(UDL3).unwrap_err(); + assert_eq!( + err.to_string(), + "Conflicting type definition for \"Testing\"" + ); + } + + #[test] + fn test_enum_variant_names_dont_shadow_types() { + // There are some edge-cases during codegen where we don't know how to disambiguate + // between an enum variant reference and a top-level type reference, so we + // disallow it in order to give a more scrutable error to the consumer. + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + }; + [Enum] + interface HardToCodegenFor { + Testing(); + OtherVariant(u32 field); + }; + "#; + let err = ComponentInterface::from_webidl(UDL).unwrap_err(); + assert_eq!( + err.to_string(), + "Enum variant names must not shadow type names: \"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_type_definition("TestOptional{}", Type::Optional(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_type_definition("TestSequence{}", Type::Sequence(Box::new(Type::UInt64))) + .is_ok()); + assert!(ci.contains_sequence_types()); + } + + #[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_type_definition( + "Map{}", + Type::Map(Box::new(Type::String), Box::new(Type::Boolean)) + ) + .is_ok()); + assert!(ci.contains_map_types()); + } + + #[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).unwrap(); + assert!(!ci.item_contains_unsigned_types(&Type::Object("Testing".into()))); + } + + #[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).unwrap(); + assert!(ci.item_contains_unsigned_types(&Type::Object("TestObj".into()))); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/namespace.rs b/third_party/rust/uniffi_bindgen/src/interface/namespace.rs new file mode 100644 index 0000000000..4a57d0ff41 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/namespace.rs @@ -0,0 +1,132 @@ +/* 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/. */ + +//! # Namespace definition for a `ComponentInterface`. +//! +//! This module converts a namespace definition from UDL into structures that +//! can be added to a `ComponentInterface`. +//! +//! In WebIDL proper, each `namespace` declares a set of functions and attributes that +//! are exposed as a global object of that name, and there can be any number of such +//! namespace definitions. +//! +//! For our purposes with UDL, we expect just a single `namespace` declaration, which +//! defines properties of the component as a whole (currently just the name). It also +//! contains the functions that will be exposed as individual plain functions exported by +//! the component, if any. So something like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! namespace example { +//! string hello(); +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Declares a component named "example" with a single exported function named "hello": +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example { +//! # string hello(); +//! # }; +//! # "##)?; +//! assert_eq!(ci.namespace(), "example"); +//! assert_eq!(ci.get_function_definition("hello").unwrap().name(), "hello"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! While this awkward-looking syntax: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! namespace example {}; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Declares a component named "example" with no exported functions: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # "##)?; +//! assert_eq!(ci.namespace(), "example"); +//! assert_eq!(ci.function_definitions().len(), 0); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Yeah, it's a bit of an awkward fit syntactically, but it's enough +//! to get us up and running for a first version of this tool. + +use anyhow::{bail, Result}; + +use super::{APIBuilder, APIConverter, ComponentInterface}; + +/// A namespace is currently just a name, but might hold more metadata about +/// the component in future. +/// +#[derive(Debug, Clone, Hash)] +pub struct Namespace { + pub(super) name: String, +} + +impl APIBuilder for weedle::NamespaceDefinition<'_> { + fn process(&self, ci: &mut ComponentInterface) -> Result<()> { + if self.attributes.is_some() { + bail!("namespace attributes are not supported yet"); + } + ci.add_namespace_definition(Namespace { + name: self.identifier.0.to_string(), + })?; + for func in self.members.body.convert(ci)? { + ci.add_function_definition(func)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_empty_namespace() { + const UDL: &str = r#" + namespace foobar{}; + "#; + let ci = ComponentInterface::from_webidl(UDL).unwrap(); + assert_eq!(ci.namespace(), "foobar"); + } + + #[test] + fn test_namespace_with_functions() { + const UDL: &str = r#" + namespace foobar{ + boolean hello(); + void world(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL).unwrap(); + assert_eq!(ci.namespace(), "foobar"); + assert_eq!(ci.function_definitions().len(), 2); + assert!(ci.get_function_definition("hello").is_some()); + assert!(ci.get_function_definition("world").is_some()); + assert!(ci.get_function_definition("potato").is_none()); + } + + #[test] + fn test_rejects_duplicate_namespaces() { + const UDL: &str = r#" + namespace foobar{ + boolean hello(); + void world(); + }; + namespace something_else{}; + "#; + let err = ComponentInterface::from_webidl(UDL).unwrap_err(); + assert_eq!(err.to_string(), "duplicate namespace definition"); + } +} 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\""); + } +} 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..b000510884 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/record.rs @@ -0,0 +1,243 @@ +/* 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; +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Record`] member with two [`Field`]s being added to the resulting +//! [`ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # dictionary Example { +//! # string name; +//! # u32 value; +//! # }; +//! # "##)?; +//! 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::{bail, Result}; +use uniffi_meta::Checksum; + +use super::types::{Type, TypeIterator}; +use super::{ + convert_type, + literal::{convert_default_value, Literal}, +}; +use super::{APIConverter, ComponentInterface}; + +/// 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) fields: Vec<Field>, +} + +impl Record { + pub fn name(&self) -> &str { + &self.name + } + + pub fn type_(&self) -> Type { + // *sigh* at the clone here, the relationship between a ComponentInterface + // and its contained types could use a bit of a cleanup. + Type::Record(self.name.clone()) + } + + pub fn fields(&self) -> &[Field] { + &self.fields + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.fields.iter().flat_map(Field::iter_types)) + } +} + +impl From<uniffi_meta::RecordMetadata> for Record { + fn from(meta: uniffi_meta::RecordMetadata) -> Self { + Self { + name: meta.name, + fields: meta.fields.into_iter().map(Into::into).collect(), + } + } +} + +impl APIConverter<Record> for weedle::DictionaryDefinition<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Record> { + if self.attributes.is_some() { + bail!("dictionary attributes are not supported yet"); + } + if self.inheritance.is_some() { + bail!("dictionary inheritance is not supported"); + } + Ok(Record { + name: self.identifier.0.to_string(), + fields: self.members.body.convert(ci)?, + }) + } +} + +// 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 type_(&self) -> &Type { + &self.type_ + } + + pub fn default_value(&self) -> Option<&Literal> { + self.default.as_ref() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + self.type_.iter_types() + } +} + +impl From<uniffi_meta::FieldMetadata> for Field { + fn from(meta: uniffi_meta::FieldMetadata) -> Self { + Self { + name: meta.name, + type_: convert_type(&meta.ty), + default: None, + } + } +} + +impl APIConverter<Field> for weedle::dictionary::DictionaryMember<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<Field> { + if self.attributes.is_some() { + bail!("dictionary member attributes are not supported yet"); + } + let type_ = ci.resolve_type_expression(&self.type_)?; + let default = match self.default { + None => None, + Some(v) => Some(convert_default_value(&v.value, &type_)?), + }; + Ok(Field { + name: self.identifier.0.to_string(), + type_, + default, + }) + } +} + +#[cfg(test)] +mod test { + use super::super::literal::Radix; + use super::*; + + #[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).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].type_().canonical_name(), "u32"); + 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].type_().canonical_name(), + "Optionalstring" + ); + assert!(record.fields()[0].default_value().is_none()); + assert_eq!(record.fields()[1].name(), "value"); + assert_eq!(record.fields()[1].type_().canonical_name(), "u32"); + 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].type_().canonical_name(), "bool"); + 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).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.canonical_name() == "u32")); + 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")); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/types/finder.rs b/third_party/rust/uniffi_bindgen/src/interface/types/finder.rs new file mode 100644 index 0000000000..991f910a4d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/types/finder.rs @@ -0,0 +1,244 @@ +/* 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/. */ + +//! # Helpers for finding the named types defined in a UDL interface. +//! +//! This module provides the [`TypeFinder`] trait, an abstraction for walking +//! the weedle parse tree, looking for type definitions, and accumulating them +//! in a [`TypeUniverse`]. +//! +//! The type-finding process only discovers very basic information about names +//! and their corresponding types. For example, it can discover that "Foobar" +//! names a Record, but it won't discover anything about the fields of that +//! record. +//! +//! Factoring this functionality out into a separate phase makes the subsequent +//! work of more *detailed* parsing of the UDL a lot simpler, we know how to resolve +//! names to types when building up the full interface definition. + +use std::convert::TryFrom; + +use anyhow::{bail, Result}; + +use super::super::attributes::{EnumAttributes, InterfaceAttributes, TypedefAttributes}; +use super::{Type, TypeUniverse}; + +/// Trait to help with an early "type discovery" phase when processing the UDL. +/// +/// This trait does structural matching against weedle AST nodes from a parsed +/// UDL file, looking for all the newly-defined types in the file and accumulating +/// them in the given `TypeUniverse`. +pub(in super::super) trait TypeFinder { + fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()>; +} + +impl<T: TypeFinder> TypeFinder for &[T] { + fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> { + for item in *self { + item.add_type_definitions_to(types)?; + } + Ok(()) + } +} + +impl TypeFinder for weedle::Definition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> { + match self { + weedle::Definition::Interface(d) => d.add_type_definitions_to(types), + weedle::Definition::Dictionary(d) => d.add_type_definitions_to(types), + weedle::Definition::Enum(d) => d.add_type_definitions_to(types), + weedle::Definition::Typedef(d) => d.add_type_definitions_to(types), + weedle::Definition::CallbackInterface(d) => d.add_type_definitions_to(types), + _ => Ok(()), + } + } +} + +impl TypeFinder for weedle::InterfaceDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> { + let name = self.identifier.0.to_string(); + // Some enum types are defined using an `interface` with a special attribute. + if InterfaceAttributes::try_from(self.attributes.as_ref())?.contains_enum_attr() { + types.add_type_definition(self.identifier.0, Type::Enum(name)) + } else if InterfaceAttributes::try_from(self.attributes.as_ref())?.contains_error_attr() { + types.add_type_definition(self.identifier.0, Type::Error(name)) + } else { + types.add_type_definition(self.identifier.0, Type::Object(name)) + } + } +} + +impl TypeFinder for weedle::DictionaryDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> { + let name = self.identifier.0.to_string(); + types.add_type_definition(self.identifier.0, Type::Record(name)) + } +} + +impl TypeFinder for weedle::EnumDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> { + let name = self.identifier.0.to_string(); + // Our error types are defined using an `enum` with a special attribute. + if EnumAttributes::try_from(self.attributes.as_ref())?.contains_error_attr() { + types.add_type_definition(self.identifier.0, Type::Error(name)) + } else { + types.add_type_definition(self.identifier.0, Type::Enum(name)) + } + } +} + +impl TypeFinder for weedle::TypedefDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> { + let name = self.identifier.0; + let attrs = TypedefAttributes::try_from(self.attributes.as_ref())?; + // If we wanted simple `typedef`s, it would be as easy as: + // > let t = types.resolve_type_expression(&self.type_)?; + // > types.add_type_definition(name, t) + // But we don't - `typedef`s are reserved for external types. + if attrs.is_custom() { + // A local type which wraps a builtin and for which we will generate an + // `FfiConverter` implementation. + let builtin = types.resolve_type_expression(&self.type_)?; + types.add_type_definition( + name, + Type::Custom { + name: name.to_string(), + builtin: builtin.into(), + }, + ) + } else { + // A crate which can supply an `FfiConverter`. + // We don't reference `self._type`, so ideally we could insist on it being + // the literal 'extern' but that's tricky + types.add_type_definition( + name, + Type::External { + name: name.to_string(), + crate_name: attrs.get_crate_name(), + }, + ) + } + } +} + +impl TypeFinder for weedle::CallbackInterfaceDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> { + if self.attributes.is_some() { + bail!("no typedef attributes are currently supported"); + } + let name = self.identifier.0.to_string(); + types.add_type_definition(self.identifier.0, Type::CallbackInterface(name)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + // A helper to take valid UDL and a closure to check what's in it. + fn test_a_finding<F>(udl: &str, tester: F) + where + F: FnOnce(TypeUniverse), + { + let idl = weedle::parse(udl).unwrap(); + let mut types = TypeUniverse::default(); + types.add_type_definitions_from(idl.as_ref()).unwrap(); + tester(types); + } + + #[test] + fn test_type_finding() { + test_a_finding( + r#" + callback interface TestCallbacks { + string hello(u32 count); + }; + "#, + |types| { + assert!( + matches!(types.get_type_definition("TestCallbacks").unwrap(), Type::CallbackInterface(nm) if nm == "TestCallbacks") + ); + }, + ); + + test_a_finding( + r#" + dictionary TestRecord { + u32 field; + }; + "#, + |types| { + assert!( + matches!(types.get_type_definition("TestRecord").unwrap(), Type::Record(nm) if nm == "TestRecord") + ); + }, + ); + + test_a_finding( + r#" + enum TestItems { "one", "two" }; + + [Error] + enum TestError { "ErrorOne", "ErrorTwo" }; + "#, + |types| { + assert!( + matches!(types.get_type_definition("TestItems").unwrap(), Type::Enum(nm) if nm == "TestItems") + ); + assert!( + matches!(types.get_type_definition("TestError").unwrap(), Type::Error(nm) if nm == "TestError") + ); + }, + ); + + test_a_finding( + r#" + interface TestObject { + constructor(); + }; + "#, + |types| { + assert!( + matches!(types.get_type_definition("TestObject").unwrap(), Type::Object(nm) if nm == "TestObject") + ); + }, + ); + + test_a_finding( + r#" + [External="crate-name"] + typedef extern ExternalType; + + [Custom] + typedef string CustomType; + "#, + |types| { + assert!( + matches!(types.get_type_definition("ExternalType").unwrap(), Type::External { name, crate_name } + if name == "ExternalType" && crate_name == "crate-name") + ); + assert!( + matches!(types.get_type_definition("CustomType").unwrap(), Type::Custom { name, builtin } + if name == "CustomType" && builtin == Box::new(Type::String)) + ); + }, + ); + } + + fn get_err(udl: &str) -> String { + let parsed = weedle::parse(udl).unwrap(); + let mut types = TypeUniverse::default(); + let err = types + .add_type_definitions_from(parsed.as_ref()) + .unwrap_err(); + err.to_string() + } + + #[test] + #[should_panic] + fn test_typedef_error_on_no_attr() { + // Sorry, still working out what we want for non-imported typedefs.. + get_err("typedef string Custom;"); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/types/mod.rs b/third_party/rust/uniffi_bindgen/src/interface/types/mod.rs new file mode 100644 index 0000000000..c31c9b3ebd --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/types/mod.rs @@ -0,0 +1,342 @@ +/* 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/. */ + +//! # Basic typesystem for defining a component interface. +//! +//! This module provides the "API-level" typesystem of a UniFFI Rust Component, that is, +//! the types provided by the Rust implementation and consumed callers of the foreign language +//! bindings. Think "objects" and "enums" and "records". +//! +//! The [`Type`] enum represents high-level types that would appear in the public API of +//! a component, such as enums and records as well as primitives like ints and strings. +//! The Rust code that implements a component, and the foreign language bindings that consume it, +//! will both typically deal with such types as their core concern. +//! +//! 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. +//! +//! As a developer working on UniFFI itself, you're likely to spend a fair bit of time thinking +//! about how these API-level types map into the lower-level types of the FFI layer as represented +//! by the [`ffi::FfiType`](super::ffi::FfiType) enum, but that's a detail that is invisible to end users. + +use std::{collections::hash_map::Entry, collections::BTreeSet, collections::HashMap, iter}; + +use anyhow::{bail, Result}; +use heck::ToUpperCamelCase; +use uniffi_meta::Checksum; + +use super::ffi::FfiType; + +mod finder; +pub(super) use finder::TypeFinder; +mod resolver; +pub(super) use resolver::{resolve_builtin_type, TypeResolver}; + +/// Represents all the different high-level types that can be used in a component interface. +/// At this level we identify user-defined types by name, without knowing any details +/// of their internal structure apart from what type of thing they are (record, enum, etc). +#[derive(Debug, Clone, Eq, PartialEq, Checksum, Ord, PartialOrd)] +pub enum Type { + // Primitive types. + UInt8, + Int8, + UInt16, + Int16, + UInt32, + Int32, + UInt64, + Int64, + Float32, + Float64, + Boolean, + String, + Timestamp, + Duration, + // Types defined in the component API, each of which has a string name. + Object(String), + Record(String), + Enum(String), + Error(String), + CallbackInterface(String), + // Structurally recursive types. + Optional(Box<Type>), + Sequence(Box<Type>), + Map(Box<Type>, Box<Type>), + // An FfiConverter we `use` from an external crate + External { name: String, crate_name: String }, + // Custom type on the scaffolding side + Custom { name: String, builtin: Box<Type> }, + // An unresolved user-defined type inside a proc-macro exported function + // signature. Must be replaced by another type before bindings generation. + Unresolved { name: String }, +} + +impl Type { + /// Get the canonical, unique-within-this-component name for a type. + /// + /// When generating helper code for foreign language bindings, it's sometimes useful to be + /// able to name a particular type in order to e.g. call a helper function that is specific + /// to that type. We support this by defining a naming convention where each type gets a + /// unique canonical name, constructed recursively from the names of its component types (if any). + pub fn canonical_name(&self) -> String { + match self { + // Builtin primitive types, with plain old names. + Type::Int8 => "i8".into(), + Type::UInt8 => "u8".into(), + Type::Int16 => "i16".into(), + Type::UInt16 => "u16".into(), + Type::Int32 => "i32".into(), + Type::UInt32 => "u32".into(), + Type::Int64 => "i64".into(), + Type::UInt64 => "u64".into(), + Type::Float32 => "f32".into(), + Type::Float64 => "f64".into(), + Type::String => "string".into(), + Type::Boolean => "bool".into(), + // API defined types. + // Note that these all get unique names, and the parser ensures that the names do not + // conflict with a builtin type. We add a prefix to the name to guard against pathological + // cases like a record named `SequenceRecord` interfering with `sequence<Record>`. + // However, types that support importing all end up with the same prefix of "Type", so + // that the import handling code knows how to find the remote reference. + Type::Object(nm) => format!("Type{nm}"), + Type::Error(nm) => format!("Type{nm}"), + Type::Enum(nm) => format!("Type{nm}"), + Type::Record(nm) => format!("Type{nm}"), + Type::CallbackInterface(nm) => format!("CallbackInterface{nm}"), + Type::Timestamp => "Timestamp".into(), + Type::Duration => "Duration".into(), + // Recursive types. + // These add a prefix to the name of the underlying type. + // The component API definition cannot give names to recursive types, so as long as the + // prefixes we add here are all unique amongst themselves, then we have no chance of + // acccidentally generating name collisions. + Type::Optional(t) => format!("Optional{}", t.canonical_name()), + Type::Sequence(t) => format!("Sequence{}", t.canonical_name()), + Type::Map(k, v) => format!( + "Map{}{}", + k.canonical_name().to_upper_camel_case(), + v.canonical_name().to_upper_camel_case() + ), + // A type that exists externally. + Type::External { name, .. } | Type::Custom { name, .. } => format!("Type{name}"), + Type::Unresolved { name } => { + unreachable!("Type `{name}` must be resolved before calling canonical_name") + } + } + } + + pub fn ffi_type(&self) -> FfiType { + self.into() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + let nested_types = match self { + Type::Optional(t) | Type::Sequence(t) => t.iter_types(), + Type::Map(k, v) => Box::new(k.iter_types().chain(v.iter_types())), + _ => Box::new(iter::empty()), + }; + Box::new(std::iter::once(self).chain(nested_types)) + } +} + +/// 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), + // 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, + // Other types are serialized into a bytebuffer and deserialized on the other side. + Type::Enum(_) + | Type::Error(_) + | Type::Record(_) + | Type::Optional(_) + | Type::Sequence(_) + | Type::Map(_, _) + | Type::Timestamp + | Type::Duration => FfiType::RustBuffer(None), + Type::External { name, .. } => FfiType::RustBuffer(Some(name.clone())), + Type::Custom { builtin, .. } => FfiType::from(builtin.as_ref()), + Type::Unresolved { name } => { + unreachable!("Type `{name}` must be resolved before lowering to FfiType") + } + } + } +} + +// Needed for rust scaffolding askama template +impl From<&&Type> for FfiType { + fn from(ty: &&Type) -> Self { + (*ty).into() + } +} + +/// 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 { + // 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 { + /// Add the definitions of all named [Type]s from a given WebIDL definition. + /// + /// This will fail if you try to add a name for which an existing type definition exists. + pub(super) fn add_type_definitions_from<T: TypeFinder>(&mut self, defn: T) -> Result<()> { + defn.add_type_definitions_to(self) + } + + /// Add the definition of a named [Type]. + /// + /// This will fail if you try to add a name for which an existing type definition exists. + pub fn add_type_definition(&mut self, name: &str, type_: Type) -> Result<()> { + if let Type::Unresolved { name: name_ } = &type_ { + assert_eq!(name, name_); + bail!("attempted to add type definition of Unresolved for `{name}`"); + } + + if resolve_builtin_type(name).is_some() { + bail!( + "please don't shadow builtin types ({name}, {})", + type_.canonical_name(), + ); + } + self.add_known_type(&type_)?; + match self.type_definitions.entry(name.to_string()) { + Entry::Occupied(o) => { + let existing_def = o.get(); + if type_ == *existing_def + && matches!(type_, Type::Record(_) | Type::Enum(_) | Type::Error(_)) + { + // UDL and proc-macro metadata are allowed to define the same record, enum and + // error types, if the definitions match (fields and variants are checked in + // add_{record,enum,error}_definition) + Ok(()) + } else { + bail!( + "Conflicting type definition for `{name}`! \ + existing definition: {existing_def:?}, \ + new definition: {type_:?}" + ); + } + } + Entry::Vacant(e) => { + e.insert(type_); + 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() + } + + /// Get the [Type] corresponding to a given WebIDL type node. + /// + /// If the node is a structural type (e.g. a sequence) then this will also add + /// it to the set of all types seen in the component interface. + pub(crate) fn resolve_type_expression<T: TypeResolver>(&mut self, expr: T) -> Result<Type> { + expr.resolve_type_expression(self) + } + + /// Add a [Type] to the set of all types seen in the component interface. + pub fn add_known_type(&mut self, type_: &Type) -> Result<()> { + // Adding potentially-unresolved types is a footgun, make sure we don't do that. + if matches!(type_, Type::Unresolved { .. }) { + bail!("Unresolved types must be resolved before being added to known types"); + } + + // Types are more likely to already be known than not, so avoid unnecessary cloning. + if !self.all_known_types.contains(type_) { + self.all_known_types.insert(type_.to_owned()); + + // Add inner types. For UDL, this is actually pointless extra work (as is calling + // add_known_type from add_function_definition), but for the proc-macro frontend + // this is important if the inner type isn't ever mentioned outside one of these + // generic builtin types. + match type_ { + Type::Optional(t) => self.add_known_type(t)?, + Type::Sequence(t) => self.add_known_type(t)?, + Type::Map(k, v) => { + self.add_known_type(k)?; + self.add_known_type(v)?; + } + _ => {} + } + } + + Ok(()) + } + + /// Iterator over all the known types in this universe. + pub fn iter_known_types(&self) -> impl Iterator<Item = &Type> { + self.all_known_types.iter() + } +} + +/// An abstract type for an iterator over &Type references. +/// +/// Ideally we would not need to name this type explicitly, and could just +/// use an `impl Iterator<Item = &Type>` on any method that yields types. +pub type TypeIterator<'a> = Box<dyn Iterator<Item = &'a Type> + 'a>; + +#[cfg(test)] +mod test_type { + use super::*; + + #[test] + fn test_canonical_names() { + // Non-exhaustive, but gives a bit of a flavour of what we want. + assert_eq!(Type::UInt8.canonical_name(), "u8"); + assert_eq!(Type::String.canonical_name(), "string"); + assert_eq!( + Type::Optional(Box::new(Type::Sequence(Box::new(Type::Object( + "Example".into() + ))))) + .canonical_name(), + "OptionalSequenceTypeExample" + ); + } +} + +#[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. +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/types/resolver.rs b/third_party/rust/uniffi_bindgen/src/interface/types/resolver.rs new file mode 100644 index 0000000000..35ae640254 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/types/resolver.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/. */ + +//! # Helpers for resolving UDL type expressions into concrete types. +//! +//! This module provides the [`TypeResolver`] trait, an abstraction for walking +//! the parse tree of a weedle type expression and using a [`TypeUniverse`] to +//! convert it into a concrete type definition (so it assumes that you're already +//! used a [`TypeFinder`](super::TypeFinder) to populate the universe). +//! +//! Perhaps most importantly, it knows how to error out if the UDL tries to reference +//! an undefined or invalid type. + +use anyhow::{bail, Result}; + +use super::{Type, TypeUniverse}; + +/// Trait to help resolving an UDL type node to a [`Type`]. +/// +/// This trait does structural matching against type-related weedle AST nodes from +/// a parsed UDL file, turning them into a corresponding [`Type`] struct. It uses the +/// known type definitions in a [`TypeUniverse`] to resolve names to types. +/// +/// As a side-effect, resolving a type expression will grow the type universe with +/// references to the types seen during traversal. For example resolving the type +/// expression `sequence<<TestRecord>?` will: +/// +/// * add `Optional<Sequence<TestRecord>` and `Sequence<TestRecord>` to the +/// known types in the universe. +/// * error out if the type name `TestRecord` is not already known. +/// +pub(crate) trait TypeResolver { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type>; +} + +impl TypeResolver for &weedle::types::Type<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + (*self).resolve_type_expression(types) + } +} + +impl TypeResolver for weedle::types::Type<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + match self { + weedle::types::Type::Single(t) => match t { + weedle::types::SingleType::Any(_) => bail!("no support for `any` types"), + weedle::types::SingleType::NonAny(t) => t.resolve_type_expression(types), + }, + weedle::types::Type::Union(_) => bail!("no support for union types yet"), + } + } +} + +impl TypeResolver for weedle::types::NonAnyType<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + match self { + weedle::types::NonAnyType::Boolean(t) => t.resolve_type_expression(types), + weedle::types::NonAnyType::Identifier(t) => t.resolve_type_expression(types), + weedle::types::NonAnyType::Integer(t) => t.resolve_type_expression(types), + weedle::types::NonAnyType::FloatingPoint(t) => t.resolve_type_expression(types), + weedle::types::NonAnyType::Sequence(t) => t.resolve_type_expression(types), + weedle::types::NonAnyType::RecordType(t) => t.resolve_type_expression(types), + _ => bail!("no support for type {:?}", self), + } + } +} + +impl TypeResolver for &weedle::types::AttributedNonAnyType<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + if self.attributes.is_some() { + bail!("type attributes are not supported yet"); + } + self.type_.resolve_type_expression(types) + } +} + +impl TypeResolver for &weedle::types::AttributedType<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + if self.attributes.is_some() { + bail!("type attributes are not supported yet"); + } + self.type_.resolve_type_expression(types) + } +} + +impl<T: TypeResolver> TypeResolver for weedle::types::MayBeNull<T> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + let type_ = self.type_.resolve_type_expression(types)?; + match self.q_mark { + None => Ok(type_), + Some(_) => { + let ty = Type::Optional(Box::new(type_)); + types.add_known_type(&ty)?; + Ok(ty) + } + } + } +} + +impl TypeResolver for weedle::types::IntegerType { + fn resolve_type_expression(&self, _types: &mut TypeUniverse) -> Result<Type> { + bail!( + "WebIDL integer types not implemented ({:?}); consider using u8, u16, u32 or u64", + self + ) + } +} + +impl TypeResolver for weedle::types::FloatingPointType { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + match self { + weedle::types::FloatingPointType::Float(t) => t.resolve_type_expression(types), + weedle::types::FloatingPointType::Double(t) => t.resolve_type_expression(types), + } + } +} + +impl TypeResolver for weedle::types::SequenceType<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + let t = self.generics.body.as_ref().resolve_type_expression(types)?; + let ty = Type::Sequence(Box::new(t)); + types.add_known_type(&ty)?; + Ok(ty) + } +} + +impl TypeResolver for weedle::types::RecordKeyType<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + use weedle::types::RecordKeyType::*; + match self { + Byte(_) | USV(_) => bail!( + "WebIDL Byte or USV string type not implemented ({self:?}); \ + consider using DOMString or string", + ), + DOM(_) => { + types.add_known_type(&Type::String)?; + Ok(Type::String) + } + NonAny(t) => t.resolve_type_expression(types), + } + } +} + +impl TypeResolver for weedle::types::RecordType<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + let key_type = self.generics.body.0.resolve_type_expression(types)?; + let value_type = self.generics.body.2.resolve_type_expression(types)?; + let map = Type::Map(Box::new(key_type), Box::new(value_type)); + types.add_known_type(&map)?; + Ok(map) + } +} + +impl TypeResolver for weedle::common::Identifier<'_> { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + match resolve_builtin_type(self.0) { + Some(type_) => { + types.add_known_type(&type_)?; + Ok(type_) + } + None => match types.get_type_definition(self.0) { + Some(type_) => { + types.add_known_type(&type_)?; + Ok(type_) + } + None => bail!("unknown type reference: {}", self.0), + }, + } + } +} + +impl TypeResolver for weedle::term::Boolean { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + types.add_known_type(&Type::Boolean)?; + Ok(Type::Boolean) + } +} + +impl TypeResolver for weedle::types::FloatType { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + if self.unrestricted.is_some() { + bail!("we don't support `unrestricted float`"); + } + types.add_known_type(&Type::Float32)?; + Ok(Type::Float32) + } +} + +impl TypeResolver for weedle::types::DoubleType { + fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> { + if self.unrestricted.is_some() { + bail!("we don't support `unrestricted double`"); + } + types.add_known_type(&Type::Float64)?; + Ok(Type::Float64) + } +} + +/// Resolve built-in API types by name. +/// +/// Given an identifier from the UDL, this will return `Some(Type)` if it names one of the +/// built-in primitive types or `None` if it names something else. +pub(in super::super) fn resolve_builtin_type(name: &str) -> Option<Type> { + match name { + "string" => Some(Type::String), + "u8" => Some(Type::UInt8), + "i8" => Some(Type::Int8), + "u16" => Some(Type::UInt16), + "i16" => Some(Type::Int16), + "u32" => Some(Type::UInt32), + "i32" => Some(Type::Int32), + "u64" => Some(Type::UInt64), + "i64" => Some(Type::Int64), + "f32" => Some(Type::Float32), + "f64" => Some(Type::Float64), + "timestamp" => Some(Type::Timestamp), + "duration" => Some(Type::Duration), + _ => None, + } +} + +#[cfg(test)] +mod test { + use super::*; + use weedle::Parse; + + #[test] + fn test_named_type_resolution() -> Result<()> { + let mut types = TypeUniverse::default(); + types.add_type_definition("TestRecord", Type::Record("TestRecord".into()))?; + assert_eq!(types.iter_known_types().count(), 1); + + let (_, expr) = weedle::types::Type::parse("TestRecord").unwrap(); + let t = types.resolve_type_expression(expr).unwrap(); + assert!(matches!(t, Type::Record(nm) if nm == "TestRecord")); + assert_eq!(types.iter_known_types().count(), 1); + + let (_, expr) = weedle::types::Type::parse("TestRecord?").unwrap(); + let t = types.resolve_type_expression(expr).unwrap(); + assert!(matches!(t, Type::Optional(_))); + // Matching the Box<T> is hard, use names as a convenient workaround. + assert_eq!(t.canonical_name(), "OptionalTypeTestRecord"); + assert_eq!(types.iter_known_types().count(), 2); + + Ok(()) + } + + #[test] + fn test_resolving_optional_type_adds_inner_type() { + let mut types = TypeUniverse::default(); + assert_eq!(types.iter_known_types().count(), 0); + let (_, expr) = weedle::types::Type::parse("u32?").unwrap(); + let t = types.resolve_type_expression(expr).unwrap(); + assert_eq!(t.canonical_name(), "Optionalu32"); + assert_eq!(types.iter_known_types().count(), 2); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "u32")); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "Optionalu32")); + } + + #[test] + fn test_resolving_sequence_type_adds_inner_type() { + let mut types = TypeUniverse::default(); + assert_eq!(types.iter_known_types().count(), 0); + let (_, expr) = weedle::types::Type::parse("sequence<string>").unwrap(); + let t = types.resolve_type_expression(expr).unwrap(); + assert_eq!(t.canonical_name(), "Sequencestring"); + assert_eq!(types.iter_known_types().count(), 2); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "Sequencestring")); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "string")); + } + + #[test] + fn test_resolving_map_type_adds_string_and_inner_type() { + let mut types = TypeUniverse::default(); + assert_eq!(types.iter_known_types().count(), 0); + let (_, expr) = weedle::types::Type::parse("record<DOMString, float>").unwrap(); + let t = types.resolve_type_expression(expr).unwrap(); + assert_eq!(t.canonical_name(), "MapStringF32"); + assert_eq!(types.iter_known_types().count(), 3); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "MapStringF32")); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "string")); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "f32")); + } + + #[test] + fn test_resolving_map_type_adds_key_type_and_inner_type() { + let mut types = TypeUniverse::default(); + assert_eq!(types.iter_known_types().count(), 0); + let (_, expr) = weedle::types::Type::parse("record<u64, float>").unwrap(); + let t = types.resolve_type_expression(expr).unwrap(); + assert_eq!(t.canonical_name(), "MapU64F32"); + assert_eq!(types.iter_known_types().count(), 3); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "MapU64F32")); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "u64")); + assert!(types + .iter_known_types() + .any(|t| t.canonical_name() == "f32")); + } + + #[test] + fn test_error_on_unknown_type() -> Result<()> { + let mut types = TypeUniverse::default(); + types.add_type_definition("TestRecord", Type::Record("TestRecord".into()))?; + // Oh no, someone made a typo in the type-o... + let (_, expr) = weedle::types::Type::parse("TestRecrd").unwrap(); + let err = types.resolve_type_expression(expr).unwrap_err(); + assert_eq!(err.to_string(), "unknown type reference: TestRecrd"); + Ok(()) + } + + #[test] + fn test_error_on_union_type() -> Result<()> { + let mut types = TypeUniverse::default(); + types.add_type_definition("TestRecord", Type::Record("TestRecord".into()))?; + let (_, expr) = weedle::types::Type::parse("(TestRecord or u32)").unwrap(); + let err = types.resolve_type_expression(expr).unwrap_err(); + assert_eq!(err.to_string(), "no support for union types yet"); + Ok(()) + } + + #[test] + fn test_type_set_is_well_ordered() -> Result<()> { + // The set (universe) of types should have a well-defined order. When + // the data structure does not guarantee the order of its elements, such as + // HashSet, then the resulting generated source code is likely not + // deterministic, and the compiled binary file may not be reproducible. We + // avoid this issue by using an implementation that defines the order of its + // elements. This test verifies that the elements are sorted as expected. + let mut types = TypeUniverse::default(); + types.add_type_definition("TestRecord", Type::Record("TestRecord".into()))?; + assert_eq!(types.iter_known_types().count(), 1); + types.add_type_definition("TestRecord2", Type::Record("TestRecord2".into()))?; + assert_eq!(types.iter_known_types().count(), 2); + types.add_type_definition("TestInt64", Type::Int64)?; + types.add_type_definition("TestInt8", Type::Int8)?; + types.add_type_definition("TestUInt8", Type::UInt8)?; + types.add_type_definition("TestBoolean", Type::Boolean)?; + assert_eq!(types.iter_known_types().count(), 6); + let mut iter = types.iter_known_types(); + assert_eq!(Some(&Type::UInt8), iter.next()); + assert_eq!(Some(&Type::Int8), iter.next()); + assert_eq!(Some(&Type::Int64), iter.next()); + assert_eq!(Some(&Type::Boolean), iter.next()); + assert_eq!(Some(&Type::Record("TestRecord".into())), iter.next()); + assert_eq!(Some(&Type::Record("TestRecord2".into())), iter.next()); + Ok(()) + } +} |