summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_udl/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/uniffi_udl/src
parentInitial commit. (diff)
downloadfirefox-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.rs839
-rw-r--r--third_party/rust/uniffi_udl/src/collectors.rs293
-rw-r--r--third_party/rust/uniffi_udl/src/converters/callables.rs219
-rw-r--r--third_party/rust/uniffi_udl/src/converters/enum_.rs139
-rw-r--r--third_party/rust/uniffi_udl/src/converters/interface.rs135
-rw-r--r--third_party/rust/uniffi_udl/src/converters/mod.rs222
-rw-r--r--third_party/rust/uniffi_udl/src/finder.rs285
-rw-r--r--third_party/rust/uniffi_udl/src/lib.rs47
-rw-r--r--third_party/rust/uniffi_udl/src/literal.rs164
-rw-r--r--third_party/rust/uniffi_udl/src/resolver.rs284
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(())
+ }
+}