diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/uniffi_udl/src | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi_udl/src')
-rw-r--r-- | third_party/rust/uniffi_udl/src/attributes.rs | 839 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/collectors.rs | 293 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/converters/callables.rs | 219 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/converters/enum_.rs | 139 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/converters/interface.rs | 135 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/converters/mod.rs | 222 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/finder.rs | 285 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/lib.rs | 47 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/literal.rs | 164 | ||||
-rw-r--r-- | third_party/rust/uniffi_udl/src/resolver.rs | 284 |
10 files changed, 2627 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_udl/src/attributes.rs b/third_party/rust/uniffi_udl/src/attributes.rs new file mode 100644 index 0000000000..f06b4f29c1 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/attributes.rs @@ -0,0 +1,839 @@ +/* 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 `InterfaceCollector`. +//! +//! 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, ExternalKind, ObjectImpl}; + +/// 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 `InterfaceCollector`, 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), + Throws(String), + Traits(Vec<String>), + // `[External="crate_name"]` - We can `use crate_name::...` for the type. + External { + crate_name: String, + kind: ExternalKind, + export: bool, + }, + // Custom type on the scaffolding side + Custom, + // The interface described is implemented as a trait. + Trait, +} + +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 `InterfaceCollector` 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), + "Custom" => Ok(Attribute::Custom), + "Trait" => Ok(Attribute::Trait), + _ => 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 { + crate_name: name_from_id_or_string(&identity.rhs), + kind: ExternalKind::DataClass, + export: false, + }), + "ExternalExport" => Ok(Attribute::External { + crate_name: name_from_id_or_string(&identity.rhs), + kind: ExternalKind::DataClass, + export: true, + }), + "ExternalInterface" => Ok(Attribute::External { + crate_name: name_from_id_or_string(&identity.rhs), + kind: ExternalKind::Interface, + export: false, + }), + "ExternalInterfaceExport" => Ok(Attribute::External { + crate_name: name_from_id_or_string(&identity.rhs), + kind: ExternalKind::Interface, + export: true, + }), + _ => anyhow::bail!( + "Attribute identity Identifier not supported: {:?}", + identity.lhs_identifier.0 + ), + } + } + weedle::attribute::ExtendedAttribute::IdentList(attr_list) => { + match attr_list.identifier.0 { + "Traits" => Ok(Attribute::Traits( + attr_list + .list + .body + .list + .iter() + .map(|i| i.0.to_string()) + .collect(), + )), + _ => anyhow::bail!( + "Attribute identity list not supported: {:?}", + attr_list.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 object_impl(&self) -> ObjectImpl { + if self.0.iter().any(|attr| matches!(attr, Attribute::Trait)) { + ObjectImpl::Trait + } else { + ObjectImpl::Struct + } + } + pub fn get_traits(&self) -> Vec<String> { + self.0 + .iter() + .find_map(|attr| match attr { + Attribute::Traits(inner) => Some(inner.clone()), + _ => None, + }) + .unwrap_or_default() + } +} + +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::Trait => Ok(()), + Attribute::Traits(_) => Ok(()), + _ => bail!(format!("{attr:?} not supported for interface definition")), + })?; + if attrs.iter().any(|a| matches!(a, Attribute::Enum)) && attrs.len() != 1 { + // If `[Enum]` is specified it must be the only attribute. + 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 FromIterator<Attribute> for ConstructorAttributes { + fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + +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 { .. })) + } + + pub(super) fn external_kind(&self) -> Option<ExternalKind> { + self.0.iter().find_map(|attr| match attr { + Attribute::External { kind, .. } => Some(*kind), + _ => None, + }) + } + + pub(super) fn external_tagged(&self) -> Option<bool> { + // If it was "exported" via a proc-macro the FfiConverter was not tagged. + self.0.iter().find_map(|attr| match attr { + Attribute::External { export, .. } => Some(!*export), + _ => None, + }) + } +} + +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_trait() -> Result<()> { + let (_, node) = weedle::attribute::ExtendedAttribute::parse("Trait").unwrap(); + let attr = Attribute::try_from(&node)?; + assert!(matches!(attr, Attribute::Trait)); + 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!(attrs.get_throws_err().is_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!(attrs.get_name().is_none()); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Name=MyFactory]").unwrap(); + let attrs = ConstructorAttributes::try_from(&node).unwrap(); + assert!(attrs.get_throws_err().is_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_trait_attribute() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert_eq!(attrs.object_impl(), ObjectImpl::Trait); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert_eq!(attrs.object_impl(), ObjectImpl::Struct); + } + + #[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("[Trait]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.contains_enum_attr(), false)); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait, 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("[Trait, 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"); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[ExternalInterface=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_udl/src/collectors.rs b/third_party/rust/uniffi_udl/src/collectors.rs new file mode 100644 index 0000000000..6a91ab4a93 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/collectors.rs @@ -0,0 +1,293 @@ +/* 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/. */ + +//! # Collects metadata from UDL. + +use crate::attributes; +use crate::converters::APIConverter; +use crate::finder; +use crate::resolver::TypeResolver; +use anyhow::{bail, Result}; +use std::collections::{hash_map, BTreeSet, HashMap}; +use uniffi_meta::Type; + +/// The implementation of this crate - we collect weedle definitions from UDL and convert +/// them into `uniffi_meta` metadata. +/// We don't really check the sanity of the output in terms of type correctness/duplications/etc +/// etc, that's the job of the consumer. +#[derive(Debug, Default)] +pub(crate) struct InterfaceCollector { + /// All of the types used in the interface. + pub types: TypeCollector, + /// The output we collect and supply to our consumer. + pub items: BTreeSet<uniffi_meta::Metadata>, +} + +impl InterfaceCollector { + /// Parse an `InterfaceCollector` from a string containing a WebIDL definition. + pub fn from_webidl(idl: &str, crate_name: &str) -> Result<Self> { + let mut ci = Self::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"); + } + // We process the WebIDL definitions in 3 passes. + // First, find the namespace. + // XXX - TODO: it's no longer necessary to do this pass. + ci.types.namespace = ci.find_namespace(&defns)?; + ci.types.crate_name = crate_name.to_string(); + // Next, 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)?; + // Any misc items we need to add to the set. + for t in ci.types.type_definitions.values() { + if let Type::Custom { + module_path, + name, + builtin, + } = t + { + ci.items.insert( + uniffi_meta::CustomTypeMetadata { + module_path: module_path.clone(), + name: name.clone(), + builtin: (**builtin).clone(), + } + .into(), + ); + } + } + Ok(ci) + } + + fn find_namespace(&mut self, defns: &Vec<weedle::Definition<'_>>) -> Result<String> { + for defn in defns { + if let weedle::Definition::Namespace(n) = defn { + return Ok(n.identifier.0.to_string()); + } + } + bail!("Failed to find the namespace"); + } + + /// The module path which should be used by all items in this namespace. + pub fn module_path(&self) -> String { + self.types.module_path() + } + + /// Get a specific type + pub fn get_type(&self, name: &str) -> Option<Type> { + self.types.get_type_definition(name) + } + + /// Resolve a weedle type expression into a `Type`. + /// + /// This method uses the current state of our `TypeCollector` 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. + pub fn resolve_type_expression<T: 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. + pub 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 definition to the `InterfaceCollector`. + fn add_definition(&mut self, defn: uniffi_meta::Metadata) -> Result<()> { + self.items.insert(defn); + Ok(()) + } +} + +/// Turn our internal object into an outgoing public `MetadataGroup`. +impl From<InterfaceCollector> for uniffi_meta::MetadataGroup { + fn from(value: InterfaceCollector) -> Self { + Self { + namespace: uniffi_meta::NamespaceMetadata { + crate_name: value.types.module_path(), + name: value.types.namespace, + }, + items: value.items, + } + } +} + +/// Trait to help build an `InterfaceCollector` 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 +/// `InterfaceCollector`. +trait APIBuilder { + fn process(&self, ci: &mut InterfaceCollector) -> Result<()>; +} + +/// Add to an `InterfaceCollector` from a list of weedle definitions, +/// by processing each in turn. +impl<T: APIBuilder> APIBuilder for Vec<T> { + fn process(&self, ci: &mut InterfaceCollector) -> Result<()> { + for item in self { + item.process(ci)?; + } + Ok(()) + } +} + +/// Add to an `InterfaceCollector` 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 InterfaceCollector) -> 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 e: uniffi_meta::ErrorMetadata = d.convert(ci)?; + ci.add_definition(e.into())?; + } else { + let e: uniffi_meta::EnumMetadata = d.convert(ci)?; + ci.add_definition(e.into())?; + } + } + weedle::Definition::Dictionary(d) => { + let rec = d.convert(ci)?; + ci.add_definition(rec.into())?; + } + weedle::Definition::Interface(d) => { + let attrs = attributes::InterfaceAttributes::try_from(d.attributes.as_ref())?; + if attrs.contains_enum_attr() { + let e: uniffi_meta::EnumMetadata = d.convert(ci)?; + ci.add_definition(e.into())?; + } else if attrs.contains_error_attr() { + let e: uniffi_meta::ErrorMetadata = d.convert(ci)?; + ci.add_definition(e.into())?; + } else { + let obj: uniffi_meta::ObjectMetadata = d.convert(ci)?; + ci.add_definition(obj.into())?; + } + } + weedle::Definition::CallbackInterface(d) => { + let obj = d.convert(ci)?; + ci.add_definition(obj.into())?; + } + // everything needed for typedefs is done in finder.rs. + weedle::Definition::Typedef(_) => {} + _ => bail!("don't know how to deal with {:?}", self), + } + Ok(()) + } +} + +impl APIBuilder for weedle::NamespaceDefinition<'_> { + fn process(&self, ci: &mut InterfaceCollector) -> Result<()> { + if self.attributes.is_some() { + bail!("namespace attributes are not supported yet"); + } + if self.identifier.0 != ci.types.namespace { + bail!("duplicate namespace definition"); + } + for func in self.members.body.convert(ci)? { + ci.add_definition(func.into())?; + } + Ok(()) + } +} + +#[derive(Debug, Default)] +pub(crate) struct TypeCollector { + /// The unique prefix that we'll use for namespacing when exposing this component's API. + pub namespace: String, + + pub crate_name: String, + + // Named type definitions (including aliases). + pub type_definitions: HashMap<String, Type>, +} + +impl TypeCollector { + /// The module path which should be used by all items in this namespace. + pub fn module_path(&self) -> String { + self.crate_name.clone() + } + + /// 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 fn add_type_definitions_from<T: finder::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<()> { + match self.type_definitions.entry(name.to_string()) { + hash_map::Entry::Occupied(o) => { + let existing_def = o.get(); + if type_ == *existing_def + && matches!(type_, Type::Record { .. } | Type::Enum { .. }) + { + // 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_:?}" + ); + } + } + hash_map::Entry::Vacant(e) => { + e.insert(type_); + Ok(()) + } + } + } + + /// Get the [Type] corresponding to a given name, if any. + pub 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 fn resolve_type_expression<T: TypeResolver>(&mut self, expr: T) -> Result<Type> { + expr.resolve_type_expression(self) + } +} diff --git a/third_party/rust/uniffi_udl/src/converters/callables.rs b/third_party/rust/uniffi_udl/src/converters/callables.rs new file mode 100644 index 0000000000..3e15bb8e02 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/converters/callables.rs @@ -0,0 +1,219 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::APIConverter; +use crate::attributes::ArgumentAttributes; +use crate::attributes::{ConstructorAttributes, FunctionAttributes, MethodAttributes}; +use crate::literal::convert_default_value; +use crate::InterfaceCollector; +use anyhow::{bail, Result}; + +use uniffi_meta::{ + ConstructorMetadata, FieldMetadata, FnMetadata, FnParamMetadata, MethodMetadata, + TraitMethodMetadata, Type, +}; + +impl APIConverter<FieldMetadata> for weedle::argument::Argument<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<FieldMetadata> { + match self { + weedle::argument::Argument::Single(t) => t.convert(ci), + weedle::argument::Argument::Variadic(_) => bail!("variadic arguments not supported"), + } + } +} + +impl APIConverter<FieldMetadata> for weedle::argument::SingleArgument<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<FieldMetadata> { + 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(FieldMetadata { + name: self.identifier.0.to_string(), + ty: type_, + default: None, + }) + } +} + +impl APIConverter<FnParamMetadata> for weedle::argument::Argument<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<FnParamMetadata> { + match self { + weedle::argument::Argument::Single(t) => t.convert(ci), + weedle::argument::Argument::Variadic(_) => bail!("variadic arguments not supported"), + } + } +} + +impl APIConverter<FnParamMetadata> for weedle::argument::SingleArgument<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<FnParamMetadata> { + 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(FnParamMetadata { + name: self.identifier.0.to_string(), + ty: type_, + by_ref, + optional: self.optional.is_some(), + default, + }) + } +} + +impl APIConverter<FnMetadata> for weedle::namespace::NamespaceMember<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<FnMetadata> { + match self { + weedle::namespace::NamespaceMember::Operation(f) => f.convert(ci), + _ => bail!("no support for namespace member type {:?} yet", self), + } + } +} + +impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<FnMetadata> { + let return_type = ci.resolve_return_type_expression(&self.return_type)?; + let name = match self.identifier { + None => bail!("anonymous functions are not supported {:?}", self), + Some(id) => id.0.to_string(), + }; + let attrs = FunctionAttributes::try_from(self.attributes.as_ref())?; + let throws = match attrs.get_throws_err() { + None => None, + Some(name) => match ci.get_type(name) { + Some(t) => Some(t), + None => bail!("unknown type for error: {name}"), + }, + }; + Ok(FnMetadata { + module_path: ci.module_path(), + name, + is_async: false, + return_type, + inputs: self.args.body.list.convert(ci)?, + throws, + checksum: None, + }) + } +} + +impl APIConverter<ConstructorMetadata> for weedle::interface::ConstructorInterfaceMember<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<ConstructorMetadata> { + let attributes = match &self.attributes { + Some(attr) => ConstructorAttributes::try_from(attr)?, + None => Default::default(), + }; + let throws = attributes + .get_throws_err() + .map(|name| ci.get_type(name).expect("invalid throws type")); + Ok(ConstructorMetadata { + module_path: ci.module_path(), + name: String::from(attributes.get_name().unwrap_or("new")), + // We don't know the name of the containing `Object` at this point, fill it in later. + self_name: Default::default(), + // Also fill in checksum_fn_name later, since it depends on object_name + inputs: self.args.body.list.convert(ci)?, + throws, + checksum: None, + }) + } +} + +impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMember<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<MethodMetadata> { + 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)?; + let attributes = MethodAttributes::try_from(self.attributes.as_ref())?; + + let throws = match attributes.get_throws_err() { + Some(name) => match ci.get_type(name) { + Some(t) => Some(t), + None => bail!("unknown type for error: {name}"), + }, + None => None, + }; + + let takes_self_by_arc = attributes.get_self_by_arc(); + Ok(MethodMetadata { + module_path: ci.module_path(), + 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. + self_name: Default::default(), + is_async: false, // not supported in UDL + inputs: self.args.body.list.convert(ci)?, + return_type, + throws, + takes_self_by_arc, + checksum: None, + }) + } +} + +impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterfaceMember<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<TraitMethodMetadata> { + 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)?; + let attributes = MethodAttributes::try_from(self.attributes.as_ref())?; + + let throws = match attributes.get_throws_err() { + Some(name) => match ci.get_type(name) { + Some(t) => Some(t), + None => bail!("unknown type for error: {name}"), + }, + None => None, + }; + + let takes_self_by_arc = attributes.get_self_by_arc(); + Ok(TraitMethodMetadata { + module_path: ci.module_path(), + trait_name: Default::default(), // we'll fill these in later. + index: Default::default(), + 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 + } + }, + is_async: false, // not supported in udl + inputs: self.args.body.list.convert(ci)?, + return_type, + throws, + takes_self_by_arc, + checksum: None, + }) + } +} diff --git a/third_party/rust/uniffi_udl/src/converters/enum_.rs b/third_party/rust/uniffi_udl/src/converters/enum_.rs new file mode 100644 index 0000000000..a3e68fd23e --- /dev/null +++ b/third_party/rust/uniffi_udl/src/converters/enum_.rs @@ -0,0 +1,139 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::APIConverter; +use crate::InterfaceCollector; +use anyhow::{bail, Result}; + +use uniffi_meta::{EnumMetadata, ErrorMetadata, VariantMetadata}; + +// Note that we have four `APIConverter` impls here - one for the `enum` case, +// one for the `[Error] enum` case, and and one for the `[Enum] interface` case, +// and one for the `[Error] interface` case. +impl APIConverter<EnumMetadata> for weedle::EnumDefinition<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<EnumMetadata> { + Ok(EnumMetadata { + module_path: ci.module_path(), + name: self.identifier.0.to_string(), + variants: self + .values + .body + .list + .iter() + .map::<Result<_>, _>(|v| { + Ok(VariantMetadata { + name: v.0.to_string(), + fields: vec![], + }) + }) + .collect::<Result<Vec<_>>>()?, + }) + } +} + +impl APIConverter<ErrorMetadata> for weedle::EnumDefinition<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<ErrorMetadata> { + Ok(ErrorMetadata::Enum { + enum_: EnumMetadata { + module_path: ci.module_path(), + name: self.identifier.0.to_string(), + variants: self + .values + .body + .list + .iter() + .map::<Result<_>, _>(|v| { + Ok(VariantMetadata { + name: v.0.to_string(), + fields: vec![], + }) + }) + .collect::<Result<Vec<_>>>()?, + }, + is_flat: true, + }) + } +} + +impl APIConverter<EnumMetadata> for weedle::InterfaceDefinition<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<EnumMetadata> { + 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(EnumMetadata { + module_path: ci.module_path(), + name: self.identifier.0.to_string(), + variants: self + .members + .body + .iter() + .map::<Result<VariantMetadata>, _>(|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, + }) + } +} + +impl APIConverter<ErrorMetadata> for weedle::InterfaceDefinition<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<ErrorMetadata> { + if self.inheritance.is_some() { + bail!("interface inheritance is not supported for enum interfaces"); + } + // We don't need to check `self.attributes` here; callers have already checked them + // to work out which version to dispatch to. + Ok(ErrorMetadata::Enum { + enum_: EnumMetadata { + module_path: ci.module_path(), + name: self.identifier.0.to_string(), + variants: self + .members + .body + .iter() + .map::<Result<VariantMetadata>, _>(|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<_>>>()?, + }, + is_flat: false, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use uniffi_meta::Metadata; + + #[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 mut ci = InterfaceCollector::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.types.namespace, "test"); + assert_eq!(ci.types.module_path(), "crate_name"); + assert_eq!(ci.items.len(), 1); + let e = &ci.items.pop_first().unwrap(); + match e { + Metadata::Enum(e) => assert_eq!(e.variants.len(), 3), + _ => unreachable!(), + } + } +} diff --git a/third_party/rust/uniffi_udl/src/converters/interface.rs b/third_party/rust/uniffi_udl/src/converters/interface.rs new file mode 100644 index 0000000000..58e6a9c8a0 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/converters/interface.rs @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::APIConverter; +use crate::attributes::InterfaceAttributes; +use crate::InterfaceCollector; +use anyhow::{bail, Result}; +use std::collections::HashSet; +use uniffi_meta::{ + ConstructorMetadata, FnParamMetadata, MethodMetadata, ObjectImpl, ObjectMetadata, Type, + UniffiTraitMetadata, +}; + +impl APIConverter<ObjectMetadata> for weedle::InterfaceDefinition<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<ObjectMetadata> { + if self.inheritance.is_some() { + bail!("interface inheritance is not supported"); + } + let attributes = match &self.attributes { + Some(attrs) => InterfaceAttributes::try_from(attrs)?, + None => Default::default(), + }; + + let object_name = self.identifier.0; + let object_impl = attributes.object_impl(); + // Convert each member into a constructor or method, guarding against duplicate names. + // They get added to the ci and aren't carried in ObjectMetadata. + let mut member_names = HashSet::new(); + for member in &self.members.body { + match member { + weedle::interface::InterfaceMember::Constructor(t) => { + let mut cons: ConstructorMetadata = t.convert(ci)?; + if object_impl == ObjectImpl::Trait { + bail!( + "Trait interfaces can not have constructors: \"{}\"", + cons.name + ) + } + if !member_names.insert(cons.name.clone()) { + bail!("Duplicate interface member name: \"{}\"", cons.name) + } + cons.self_name = object_name.to_string(); + ci.items.insert(cons.into()); + } + weedle::interface::InterfaceMember::Operation(t) => { + let mut method: MethodMetadata = t.convert(ci)?; + if !member_names.insert(method.name.clone()) { + bail!("Duplicate interface member name: \"{}\"", method.name) + } + method.self_name = object_name.to_string(); + ci.items.insert(method.into()); + } + _ => bail!("no support for interface member type {:?} yet", member), + } + } + // A helper for our trait methods + let make_trait_method = |name: &str, + inputs: Vec<FnParamMetadata>, + return_type: Option<Type>| + -> Result<MethodMetadata> { + Ok(MethodMetadata { + module_path: ci.module_path(), + // The name is used to create the ffi function for the method. + name: name.to_string(), + self_name: object_name.to_string(), + is_async: false, + inputs, + return_type, + throws: None, + takes_self_by_arc: false, + checksum: None, + }) + }; + // Trait methods are in the Metadata. + let uniffi_traits = attributes + .get_traits() + .into_iter() + .map(|trait_name| { + Ok(match trait_name.as_str() { + "Debug" => UniffiTraitMetadata::Debug { + fmt: make_trait_method("uniffi_trait_debug", vec![], Some(Type::String))?, + }, + "Display" => UniffiTraitMetadata::Display { + fmt: make_trait_method("uniffi_trait_display", vec![], Some(Type::String))?, + }, + "Eq" => UniffiTraitMetadata::Eq { + eq: make_trait_method( + "uniffi_trait_eq_eq", + vec![FnParamMetadata { + name: "other".to_string(), + ty: Type::Object { + module_path: ci.module_path(), + name: object_name.to_string(), + imp: object_impl, + }, + by_ref: true, + default: None, + optional: false, + }], + Some(Type::Boolean), + )?, + ne: make_trait_method( + "uniffi_trait_eq_ne", + vec![FnParamMetadata { + name: "other".to_string(), + ty: Type::Object { + module_path: ci.module_path(), + name: object_name.to_string(), + imp: object_impl, + }, + by_ref: true, + default: None, + optional: false, + }], + Some(Type::Boolean), + )?, + }, + "Hash" => UniffiTraitMetadata::Hash { + hash: make_trait_method("uniffi_trait_hash", vec![], Some(Type::UInt64))?, + }, + _ => bail!("Invalid trait name: {}", trait_name), + }) + }) + .collect::<Result<Vec<_>>>()?; + for ut in uniffi_traits { + ci.items.insert(ut.into()); + } + Ok(ObjectMetadata { + module_path: ci.module_path(), + name: object_name.to_string(), + imp: object_impl, + }) + } +} diff --git a/third_party/rust/uniffi_udl/src/converters/mod.rs b/third_party/rust/uniffi_udl/src/converters/mod.rs new file mode 100644 index 0000000000..7a2d22ac42 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/converters/mod.rs @@ -0,0 +1,222 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::literal::convert_default_value; +use crate::InterfaceCollector; +use anyhow::{bail, Result}; + +use uniffi_meta::{ + CallbackInterfaceMetadata, FieldMetadata, RecordMetadata, TraitMethodMetadata, VariantMetadata, +}; + +mod callables; +mod enum_; +mod interface; + +/// Trait to help convert WedIDL syntax nodes into `InterfaceCollector` 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 +/// `InterfaceCollector`. It is basically the `TryFrom` trait except that the conversion +/// always happens in the context of a given `InterfaceCollector`, which is used for +/// resolving e.g. type definitions. +/// +/// The difference between this trait and `APIBuilder` is that `APIConverter` treats the +/// `InterfaceCollector` as a read-only data source for resolving types, while `APIBuilder` +/// actually mutates the `InterfaceCollector` to add new definitions. +pub(crate) trait APIConverter<T> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<T>; +} + +/// Convert a list of weedle items into a list of `InterfaceCollector` 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 InterfaceCollector) -> Result<Vec<U>> { + self.iter().map(|v| v.convert(ci)).collect::<Result<_>>() + } +} + +impl APIConverter<VariantMetadata> for weedle::interface::OperationInterfaceMember<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<VariantMetadata> { + 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::{self, Identifier}, + ReturnType, + SingleType::NonAny, + Type::Single, + }; + match &self.return_type { + ReturnType::Type(Single(NonAny(Identifier(id)))) => id.type_.0.to_owned(), + // Using recognized/parsed types as enum variant names can lead to the bail error because they match + // before `Identifier`. `Error` is one that's likely to be common, so we're circumventing what is + // likely a parsing issue here. As an example of the issue `Promise` (`Promise(PromiseType<'a>)`) as + // a variant matches the `Identifier` arm, but `DataView` (`DataView(MayBeNull<term!(DataView)>)`) + // fails. + ReturnType::Type(Single(NonAny(NonAnyType::Error(_)))) => "Error".to_string(), + _ => bail!("enum interface members must have plain identifiers as names"), + } + }; + Ok(VariantMetadata { + name, + fields: self + .args + .body + .list + .iter() + .map(|arg| arg.convert(ci)) + .collect::<Result<Vec<_>>>()?, + }) + } +} + +impl APIConverter<RecordMetadata> for weedle::DictionaryDefinition<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<RecordMetadata> { + if self.attributes.is_some() { + bail!("dictionary attributes are not supported yet"); + } + if self.inheritance.is_some() { + bail!("dictionary inheritance is not supported"); + } + Ok(RecordMetadata { + module_path: ci.module_path(), + name: self.identifier.0.to_string(), + fields: self.members.body.convert(ci)?, + }) + } +} + +impl APIConverter<FieldMetadata> for weedle::dictionary::DictionaryMember<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<FieldMetadata> { + 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(FieldMetadata { + name: self.identifier.0.to_string(), + ty: type_, + default, + }) + } +} + +impl APIConverter<CallbackInterfaceMetadata> for weedle::CallbackInterfaceDefinition<'_> { + fn convert(&self, ci: &mut InterfaceCollector) -> Result<CallbackInterfaceMetadata> { + 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 object_name = self.identifier.0; + for (index, member) in self.members.body.iter().enumerate() { + match member { + weedle::interface::InterfaceMember::Operation(t) => { + let mut method: TraitMethodMetadata = t.convert(ci)?; + // A CallbackInterface is described in Rust as a trait, but uniffi + // generates a struct implementing the trait and passes the concrete version + // of that. + // This really just reflects the fact that CallbackInterface and Object + // should be merged; we'd still need a way to ask for a struct delegating to + // foreign implementations be done. + // But currently they are passed as a concrete type with no associated types. + method.trait_name = object_name.to_string(); + method.index = index as u32; + ci.items.insert(method.into()); + } + _ => bail!( + "no support for callback interface member type {:?} yet", + member + ), + } + } + Ok(CallbackInterfaceMetadata { + module_path: ci.module_path(), + name: object_name.to_string(), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use uniffi_meta::{LiteralMetadata, Metadata, Radix, Type}; + + #[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 mut ci = InterfaceCollector::from_webidl(UDL, "crate-name").unwrap(); + assert_eq!(ci.items.len(), 3); + match &ci.items.pop_first().unwrap() { + Metadata::Record(record) => { + assert_eq!(record.name, "Complex"); + assert_eq!(record.fields.len(), 3); + assert_eq!(record.fields[0].name, "key"); + assert_eq!( + record.fields[0].ty, + Type::Optional { + inner_type: Box::new(Type::String) + } + ); + assert!(record.fields[0].default.is_none()); + assert_eq!(record.fields[1].name, "value"); + assert_eq!(record.fields[1].ty, Type::UInt32); + assert!(matches!( + record.fields[1].default, + Some(LiteralMetadata::UInt(0, Radix::Decimal, Type::UInt32)) + )); + assert_eq!(record.fields[2].name, "spin"); + assert_eq!(record.fields[2].ty, Type::Boolean); + assert!(record.fields[2].default.is_none()); + } + _ => unreachable!(), + } + + match &ci.items.pop_first().unwrap() { + Metadata::Record(record) => { + assert_eq!(record.name, "Empty"); + assert_eq!(record.fields.len(), 0); + } + _ => unreachable!(), + } + + match &ci.items.pop_first().unwrap() { + Metadata::Record(record) => { + assert_eq!(record.name, "Simple"); + assert_eq!(record.fields.len(), 1); + assert_eq!(record.fields[0].name, "field"); + assert_eq!(record.fields[0].ty, Type::UInt32); + assert!(record.fields[0].default.is_none()); + } + _ => unreachable!(), + } + } +} diff --git a/third_party/rust/uniffi_udl/src/finder.rs b/third_party/rust/uniffi_udl/src/finder.rs new file mode 100644 index 0000000000..0c4c187dc0 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/finder.rs @@ -0,0 +1,285 @@ +/* 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 [`TypeCollector`]. +//! +//! 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::TypeCollector; +use crate::attributes::{InterfaceAttributes, TypedefAttributes}; +use uniffi_meta::Type; + +/// 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 `TypeCollector`. +pub(crate) trait TypeFinder { + fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()>; +} + +impl<T: TypeFinder> TypeFinder for &[T] { + fn add_type_definitions_to(&self, types: &mut TypeCollector) -> 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 TypeCollector) -> 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 TypeCollector) -> Result<()> { + let name = self.identifier.0.to_string(); + let attrs = InterfaceAttributes::try_from(self.attributes.as_ref())?; + // Some enum types are defined using an `interface` with a special attribute. + if attrs.contains_enum_attr() || attrs.contains_error_attr() { + types.add_type_definition( + self.identifier.0, + Type::Enum { + name, + module_path: types.module_path(), + }, + ) + } else { + types.add_type_definition( + self.identifier.0, + Type::Object { + name, + module_path: types.module_path(), + imp: attrs.object_impl(), + }, + ) + } + } +} + +impl TypeFinder for weedle::DictionaryDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()> { + let name = self.identifier.0.to_string(); + types.add_type_definition( + self.identifier.0, + Type::Record { + name, + module_path: types.module_path(), + }, + ) + } +} + +impl TypeFinder for weedle::EnumDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()> { + let name = self.identifier.0.to_string(); + // Our error types are defined using an `enum` with a special attribute. + types.add_type_definition( + self.identifier.0, + Type::Enum { + name, + module_path: types.module_path(), + }, + ) + } +} + +impl TypeFinder for weedle::TypedefDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeCollector) -> 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 { + module_path: types.module_path(), + name: name.to_string(), + builtin: builtin.into(), + }, + ) + } else { + let kind = attrs.external_kind().expect("External missing"); + let tagged = attrs.external_tagged().expect("External missing"); + // 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(), + namespace: "".to_string(), // we don't know this yet + module_path: attrs.get_crate_name(), + kind, + tagged, + }, + ) + } + } +} + +impl TypeFinder for weedle::CallbackInterfaceDefinition<'_> { + fn add_type_definitions_to(&self, types: &mut TypeCollector) -> 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, + module_path: types.module_path(), + }, + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + use uniffi_meta::ExternalKind; + + // 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(TypeCollector), + { + let idl = weedle::parse(udl).unwrap(); + let mut types = TypeCollector::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 { name, .. } if name == "TestCallbacks") + ); + }, + ); + + test_a_finding( + r#" + dictionary TestRecord { + u32 field; + }; + "#, + |types| { + assert!( + matches!(types.get_type_definition("TestRecord").unwrap(), Type::Record { name, .. } if name == "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 { name, .. } if name == "TestItems") + ); + assert!( + matches!(types.get_type_definition("TestError").unwrap(), Type::Enum { name, .. } if name == "TestError") + ); + }, + ); + + test_a_finding( + r#" + interface TestObject { + constructor(); + }; + "#, + |types| { + assert!( + matches!(types.get_type_definition("TestObject").unwrap(), Type::Object{ name, .. } if name == "TestObject") + ); + }, + ); + + test_a_finding( + r#" + [External="crate-name"] + typedef extern ExternalType; + + [ExternalInterface="crate-name"] + typedef extern ExternalInterfaceType; + + [Custom] + typedef string CustomType; + "#, + |types| { + assert!( + matches!(types.get_type_definition("ExternalType").unwrap(), Type::External { name, module_path, kind: ExternalKind::DataClass, .. } + if name == "ExternalType" && module_path == "crate-name") + ); + assert!( + matches!(types.get_type_definition("ExternalInterfaceType").unwrap(), Type::External { name, module_path, kind: ExternalKind::Interface, .. } + if name == "ExternalInterfaceType" && module_path == "crate-name") + ); + assert!( + matches!(types.get_type_definition("CustomType").unwrap(), Type::Custom { name, builtin, ..} + if name == "CustomType" && *builtin == Type::String) + ); + }, + ); + } + + fn get_err(udl: &str) -> String { + let parsed = weedle::parse(udl).unwrap(); + let mut types = TypeCollector::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_udl/src/lib.rs b/third_party/rust/uniffi_udl/src/lib.rs new file mode 100644 index 0000000000..5e6e72a7f7 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/lib.rs @@ -0,0 +1,47 @@ +/* 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/. */ + +//! # Uniffi support for webidl syntax, typically from a .udl file, as described by weedle. +//! +//! This library is dedicated to parsing a string in a webidl syntax, as described by +//! weedle and with our own custom take on the attributes etc, pushing the boundaries +//! of that syntax to describe a uniffi `MetatadataGroup`. +//! +//! The output of this module is consumed by uniffi_bindgen to generate stuff. + +mod attributes; +mod collectors; +mod converters; +mod finder; +mod literal; +mod resolver; + +use anyhow::Result; +use collectors::{InterfaceCollector, TypeCollector}; +use uniffi_meta::Type; + +/// The single entry-point to this module. +pub fn parse_udl(udl: &str, crate_name: &str) -> Result<uniffi_meta::MetadataGroup> { + Ok(InterfaceCollector::from_webidl(udl, crate_name)?.into()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_group() { + const UDL: &str = r#" + namespace test{}; + dictionary Empty {}; + "#; + let group = parse_udl(UDL, "crate_name").unwrap(); + assert_eq!(group.namespace.name, "test"); + assert_eq!(group.items.len(), 1); + assert!(matches!( + group.items.into_iter().next().unwrap(), + uniffi_meta::Metadata::Record(r) if r.module_path == "crate_name" && r.name == "Empty" && r.fields.is_empty() + )); + } +} diff --git a/third_party/rust/uniffi_udl/src/literal.rs b/third_party/rust/uniffi_udl/src/literal.rs new file mode 100644 index 0000000000..78f2544254 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/literal.rs @@ -0,0 +1,164 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use anyhow::{bail, Result}; +use uniffi_meta::{LiteralMetadata, Radix, Type}; + +// We are able to use LiteralMetadata directly. +pub type Literal = LiteralMetadata; + +// Convert weedle/udl syntax. +pub(super) fn convert_default_value( + default_value: &weedle::literal::DefaultValue<'_>, + type_: &Type, +) -> Result<LiteralMetadata> { + 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 { name: "E".into(), module_path: "".into() })?, Literal::Enum(v, Type::Enum { name, .. }) if v == "one" && name == "E") + ); + assert!(matches!( + parse_and_convert( + "[]", + Type::Sequence { + inner_type: Box::new(Type::String) + } + )?, + Literal::EmptySequence + )); + assert!(matches!( + parse_and_convert( + "null", + Type::Optional { + inner_type: 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_udl/src/resolver.rs b/third_party/rust/uniffi_udl/src/resolver.rs new file mode 100644 index 0000000000..14a7a4c6f1 --- /dev/null +++ b/third_party/rust/uniffi_udl/src/resolver.rs @@ -0,0 +1,284 @@ +/* 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 [`TypeCollector`] 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, TypeCollector}; + +/// 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 [`TypeCollector`] 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 TypeCollector) -> Result<Type>; +} + +impl TypeResolver for &weedle::types::Type<'_> { + fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result<Type> { + (*self).resolve_type_expression(types) + } +} + +impl TypeResolver for weedle::types::Type<'_> { + fn resolve_type_expression(&self, types: &mut TypeCollector) -> 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 TypeCollector) -> 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 TypeCollector) -> 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 TypeCollector) -> 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 TypeCollector) -> Result<Type> { + let type_ = self.type_.resolve_type_expression(types)?; + match self.q_mark { + None => Ok(type_), + Some(_) => { + let ty = Type::Optional { + inner_type: Box::new(type_), + }; + Ok(ty) + } + } + } +} + +impl TypeResolver for weedle::types::IntegerType { + fn resolve_type_expression(&self, _types: &mut TypeCollector) -> 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 TypeCollector) -> 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 TypeCollector) -> Result<Type> { + let t = self.generics.body.as_ref().resolve_type_expression(types)?; + let ty = Type::Sequence { + inner_type: Box::new(t), + }; + Ok(ty) + } +} + +impl TypeResolver for weedle::types::RecordKeyType<'_> { + fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result<Type> { + use weedle::types::RecordKeyType::*; + match self { + Byte(_) | USV(_) => bail!( + "WebIDL Byte or USV string type not implemented ({self:?}); \ + consider using a string", + ), + DOM(_) => Ok(Type::String), + NonAny(t) => t.resolve_type_expression(types), + } + } +} + +impl TypeResolver for weedle::types::RecordType<'_> { + fn resolve_type_expression(&self, types: &mut TypeCollector) -> 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 { + key_type: Box::new(key_type), + value_type: Box::new(value_type), + }; + Ok(map) + } +} + +impl TypeResolver for weedle::common::Identifier<'_> { + fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result<Type> { + match resolve_builtin_type(self.0) { + Some(type_) => Ok(type_), + None => match types.get_type_definition(self.0) { + Some(type_) => Ok(type_), + None => bail!("unknown type reference: {}", self.0), + }, + } + } +} + +impl TypeResolver for weedle::term::Boolean { + fn resolve_type_expression(&self, _types: &mut TypeCollector) -> Result<Type> { + Ok(Type::Boolean) + } +} + +impl TypeResolver for weedle::types::FloatType { + fn resolve_type_expression(&self, _types: &mut TypeCollector) -> Result<Type> { + if self.unrestricted.is_some() { + bail!("we don't support `unrestricted float`"); + } + Ok(Type::Float32) + } +} + +impl TypeResolver for weedle::types::DoubleType { + fn resolve_type_expression(&self, _types: &mut TypeCollector) -> Result<Type> { + if self.unrestricted.is_some() { + bail!("we don't support `unrestricted double`"); + } + 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(crate) fn resolve_builtin_type(name: &str) -> Option<Type> { + match name { + "string" => Some(Type::String), + "bytes" => Some(Type::Bytes), + "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), + "ForeignExecutor" => Some(Type::ForeignExecutor), + _ => None, + } +} + +#[cfg(test)] +mod test { + use super::*; + use weedle::Parse; + + #[test] + fn test_named_type_resolution() -> Result<()> { + let mut types = TypeCollector::default(); + types.add_type_definition( + "TestRecord", + Type::Record { + name: "TestRecord".into(), + module_path: "".into(), + }, + )?; + assert_eq!(types.type_definitions.len(), 1); + + let (_, expr) = weedle::types::Type::parse("TestRecord").unwrap(); + let t = types.resolve_type_expression(expr).unwrap(); + assert!(matches!(t, Type::Record { name, .. } if name == "TestRecord")); + assert_eq!(types.type_definitions.len(), 1); + + let (_, expr) = weedle::types::Type::parse("TestRecord?").unwrap(); + let t = types.resolve_type_expression(expr).unwrap(); + assert!(matches!(t, Type::Optional { .. })); + assert!(match t { + Type::Optional { inner_type } => + matches!(*inner_type, Type::Record { name, .. } if name == "TestRecord"), + _ => false, + }); + // assert_eq!(types.type_definitions.len(), 2); + + Ok(()) + } + + #[test] + fn test_error_on_unknown_type() -> Result<()> { + let mut types = TypeCollector::default(); + types.add_type_definition( + "TestRecord", + Type::Record { + name: "TestRecord".into(), + module_path: "".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 = TypeCollector::default(); + types.add_type_definition( + "TestRecord", + Type::Record { + name: "TestRecord".into(), + module_path: "".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(()) + } +} |