/* 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::{attributes::DictionaryAttributes, literal::convert_default_value, InterfaceCollector}; use anyhow::{bail, Result}; use uniffi_meta::{ CallbackInterfaceMetadata, DefaultValueMetadata, FieldMetadata, FnParamMetadata, MethodMetadata, RecordMetadata, TraitMethodMetadata, Type, UniffiTraitMetadata, 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 { fn convert(&self, ci: &mut InterfaceCollector) -> Result; } // Convert UDL docstring into metadata docstring pub(crate) fn convert_docstring(docstring: &str) -> String { textwrap::dedent(docstring) } /// Convert a list of weedle items into a list of `InterfaceCollector` items, /// by doing a direct item-by-item mapping. impl> APIConverter> for Vec { fn convert(&self, ci: &mut InterfaceCollector) -> Result> { self.iter().map(|v| v.convert(ci)).collect::>() } } impl APIConverter for weedle::interface::OperationInterfaceMember<'_> { fn convert(&self, ci: &mut InterfaceCollector) -> Result { 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)`) // fails. ReturnType::Type(Single(NonAny(NonAnyType::Error(_)))) => "Error".to_string(), _ => bail!("enum interface members must have plain identifiers as names"), } }; Ok(VariantMetadata { name, discr: None, fields: self .args .body .list .iter() .map(|arg| arg.convert(ci)) .collect::>>()?, docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } impl APIConverter for weedle::DictionaryDefinition<'_> { fn convert(&self, ci: &mut InterfaceCollector) -> Result { let attributes = DictionaryAttributes::try_from(self.attributes.as_ref())?; if self.inheritance.is_some() { bail!("dictionary inheritance is not supported"); } let other = Type::Record { module_path: ci.module_path().to_string(), name: self.identifier.0.to_string(), }; for ut in make_uniffi_traits( &ci.module_path(), self.identifier.0, &attributes.get_uniffi_traits(), &other, )? { ci.items.insert(ut.into()); } Ok(RecordMetadata { module_path: ci.module_path(), name: self.identifier.0.to_string(), remote: attributes.contains_remote(), fields: self.members.body.convert(ci)?, docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } impl APIConverter for weedle::dictionary::DictionaryMember<'_> { fn convert(&self, ci: &mut InterfaceCollector) -> Result { 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(DefaultValueMetadata::Literal(convert_default_value( &v.value, &type_, )?)), }; Ok(FieldMetadata { name: self.identifier.0.to_string(), ty: type_, default, docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } impl APIConverter for weedle::CallbackInterfaceDefinition<'_> { fn convert(&self, ci: &mut InterfaceCollector) -> Result { 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(), docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } fn make_uniffi_traits( module_path: &str, self_name: &str, names: &[String], other: &Type, ) -> Result> { // A helper for our trait methods let make_trait_method = |name: &str, inputs: Vec, return_type: Option| -> Result { Ok(MethodMetadata { module_path: module_path.to_string(), self_name: self_name.to_string(), name: name.to_string(), is_async: false, inputs, return_type, throws: None, takes_self_by_arc: false, checksum: None, docstring: None, }) }; names .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: other.clone(), 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: other.clone(), by_ref: true, default: None, optional: false, }], Some(Type::Boolean), )?, }, "Hash" => UniffiTraitMetadata::Hash { hash: make_trait_method("uniffi_trait_hash", vec![], Some(Type::UInt64))?, }, "Ord" => UniffiTraitMetadata::Ord { cmp: make_trait_method( "uniffi_trait_ord_cmp", vec![FnParamMetadata { name: "other".to_string(), ty: other.clone(), by_ref: true, default: None, optional: false, }], Some(Type::Int8), )?, }, _ => bail!("Invalid trait name: {}", trait_name), }) }) .collect::>>() } #[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(DefaultValueMetadata::Literal(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!(), } } }