diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 719f385f..0a495de5 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -227,6 +227,7 @@ pub enum ConstValue { /// /// This exists to give the ability to namespace js imports. #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +#[derive(Default)] pub struct Module { /// js -> rust interfaces pub imports: Vec, diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 4e58443a..004dc31a 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -28,6 +28,7 @@ pub(crate) struct FirstPassRecord<'src> { /// The mixins, mapping their name to the webidl ast node for the mixin. pub(crate) mixins: BTreeMap<&'src str, Vec<&'src MixinMembers<'src>>>, pub(crate) typedefs: BTreeMap<&'src str, &'src weedle::types::Type<'src>>, + pub(crate) namespaces: BTreeMap<&'src str, NamespaceData<'src>>, } /// We need to collect interface data during the first pass, to be used later. @@ -40,6 +41,34 @@ pub(crate) struct InterfaceData<'src> { pub(crate) superclass: Option<&'src str>, } +impl<'src> Default for InterfaceData<'src> { + fn default() -> Self { + InterfaceData { + partial: true, + global: false, + operations: Default::default(), + superclass: Default::default(), + } + } +} + +/// We need to collect namespace data during the first pass, to be used later. +#[derive(Default)] +pub(crate) struct NamespaceData<'src> { + /// Whether only partial namespaces were encountered + pub(crate) partial: bool, + pub(crate) operations: BTreeMap, OperationData<'src>>, +} + +impl<'src> Default for NamespaceData<'src> { + fn default() -> Self { + NamespaceData { + partial: true, + operations: Default::default(), + } + } +} + #[derive(PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum OperationId<'src> { Constructor, @@ -83,6 +112,8 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { PartialInterface(interface) => interface.first_pass(record, ()), InterfaceMixin(mixin) => mixin.first_pass(record, ()), PartialInterfaceMixin(mixin) => mixin.first_pass(record, ()), + Namespace(namespace) => namespace.first_pass(record, ()), + PartialNamespace(namespace) => namespace.first_pass(record, ()), Typedef(typedef) => typedef.first_pass(record, ()), _ => { // Other definitions aren't currently used in the first pass @@ -111,7 +142,8 @@ impl<'src> FirstPass<'src, ()> for weedle::EnumDefinition<'src> { } } -fn first_pass_operation<'src>( +/// Helper function to add an operation to an interface. +fn first_pass_interface_operation<'src>( record: &mut FirstPassRecord<'src>, self_name: &'src str, id: OperationId<'src>, @@ -199,7 +231,7 @@ impl<'src> FirstPass<'src, &'src str> for ExtendedAttribute<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str) -> Result<()> { match self { ExtendedAttribute::ArgList(list) if list.identifier.0 == "Constructor" => { - first_pass_operation( + first_pass_interface_operation( record, self_name, OperationId::Constructor, @@ -207,7 +239,7 @@ impl<'src> FirstPass<'src, &'src str> for ExtendedAttribute<'src> { ) } ExtendedAttribute::NoArgs(name) if (name.0).0 == "Constructor" => { - first_pass_operation( + first_pass_interface_operation( record, self_name, OperationId::Constructor, @@ -217,7 +249,7 @@ impl<'src> FirstPass<'src, &'src str> for ExtendedAttribute<'src> { ExtendedAttribute::NamedArgList(list) if list.lhs_identifier.0 == "NamedConstructor" => { - first_pass_operation( + first_pass_interface_operation( record, self_name, OperationId::Constructor, @@ -258,16 +290,20 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceM warn!("Unsupported webidl operation {:?}", self); return Ok(()) } - first_pass_operation( + first_pass_interface_operation( record, self_name, match self.identifier.map(|s| s.0) { None => match self.specials.get(0) { None => OperationId::Operation(None), - Some(weedle::interface::Special::Getter(weedle::term::Getter)) => OperationId::IndexingGetter, - Some(weedle::interface::Special::Setter(weedle::term::Setter)) => OperationId::IndexingSetter, - Some(weedle::interface::Special::Deleter(weedle::term::Deleter)) => OperationId::IndexingDeleter, - Some(weedle::interface::Special::LegacyCaller(weedle::term::LegacyCaller)) => return Ok(()), + Some(weedle::interface::Special::Getter(weedle::term::Getter)) + => OperationId::IndexingGetter, + Some(weedle::interface::Special::Setter(weedle::term::Setter)) + => OperationId::IndexingSetter, + Some(weedle::interface::Special::Deleter(weedle::term::Deleter)) + => OperationId::IndexingDeleter, + Some(weedle::interface::Special::LegacyCaller(weedle::term::LegacyCaller)) + => return Ok(()), }, Some(ref name) => OperationId::Operation(Some(name.clone())), }, diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 76d2cad4..7fcf0460 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -38,7 +38,7 @@ use backend::TryToTokens; use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports}; use backend::util::{ident_ty, rust_ident, wrap_import_function}; use failure::ResultExt; -use heck::{ShoutySnakeCase}; +use heck::{ShoutySnakeCase, SnakeCase}; use proc_macro2::{Ident, Span}; use weedle::argument::Argument; use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList}; @@ -308,7 +308,7 @@ impl<'src> WebidlParse<'src, &'src weedle::InterfaceDefinition<'src>> for Extend interface: &'src weedle::InterfaceDefinition<'src>, ) -> Result<()> { let mut add_constructor = |arguments: &[Argument], class: &str| { - let (overloaded, same_argument_names) = first_pass.get_operation_overloading( + let (overloaded, same_argument_names) = first_pass.get_method_overloading( arguments, &::first_pass::OperationId::Constructor, interface.identifier.0, @@ -794,7 +794,7 @@ impl<'src> WebidlParse<'src, &'src str> for weedle::interface::ConstMember<'src> } } -impl<'src> WebidlParse<'src, ()> for weedle::interface::NamespaceDefinition<'src> { +impl<'src> WebidlParse<'src, ()> for weedle::NamespaceDefinition<'src> { fn webidl_parse( &'src self, program: &mut backend::ast::Program, @@ -805,6 +805,150 @@ impl<'src> WebidlParse<'src, ()> for weedle::interface::NamespaceDefinition<'src return Ok(()); } + let rust_name = rust_ident(self.identifier.0.to_snake_case().as_str()); + let js_name = &self.identifier.0; + program.modules.entry(rust_name.clone()) + .and_modify(|_| warn!("Namespace with rust name {:?} added more than once", rust_name)) + .or_default(); + + for member in self.members.body.iter() { + member.webidl_parse(program, record, js_name)? + } + Ok(()) } } + +impl<'src> WebidlParse<'src, &'src str> for weedle::namespace::NamespaceMember<'src> { + fn webidl_parse( + &'src self, + program: &mut backend::ast::Program, + record: &FirstPassRecord<'src>, + module_name: &'src str + ) -> Result<()> { + match self { + weedle::namespace::NamespaceMember::Operation(op) => { + op.webidl_parse(program, record, module_name)?; + } + weedle::namespace::NamespaceMember::Attribute(_) => { + warn!("Attribute namespace members are not supported") + } + } + Ok(()) + } +} + +impl<'src> WebidlParse<'src, &'src str> for weedle::namespace::OperationNamespaceMember<'src> { + fn webidl_parse( + &'src self, + program: &mut backend::ast::Program, + record: &FirstPassRecord<'src>, + module_name: &'src str + ) -> Result<()> { + if util::is_chrome_only(&self.attributes) { + return Ok(()); + } + + let structural = util::is_structural(&self.attributes); + + let name = match &self.identifier { + Some(ident) => ident.0, + None => { + warn!("Operations without identifiers are not supported"); + return Ok(()); + } + + let import_fn = first_pass + .create_function( + name, + false, + same_argument_names: bool, + arguments: &[Argument], + mut ret: Option, + kind: backend::ast::ImportFunctionKind, + structural: bool, + catch: bool, + doc_comment: Option, + ) -> Option + .create_basic_method( + args, + match identifier.map(|s| s.0) { + Some(ref name) if specials.is_empty() => + ::first_pass::OperationId::Operation(Some(name.clone())), + }, + &self.return_type, + self_name, + specials.len() == 1 || first_pass + .interfaces + .get(self_name) + .map(|interface_data| interface_data.global) + .unwrap_or(false), + util::throws(&self.attributes), + ) + .map(wrap_import_function) + .map(|import| program.imports.push(import)); + Ok(()) + } +} + +fn member_operation<'src>( + program: &mut backend::ast::Program, + first_pass: &FirstPassRecord<'src>, + self_name: &'src str, + attrs: &'src Option, + modifier: Option, + specials: &[weedle::interface::Special], + return_type: &'src weedle::types::ReturnType<'src>, + args: &'src [Argument], + identifier: &Option>, +) -> Result<()> { + use weedle::interface::StringifierOrStatic::*; + + if util::is_chrome_only(attrs) { + return Ok(()); + } + let statik = match modifier { + Some(Stringifier(_)) => { + warn!("Unsupported stringifier on type {:?}", (self_name, identifier)); + return Ok(()) + } + Some(Static(_)) => true, + None => false, + }; + + first_pass + .create_basic_method( + args, + match identifier.map(|s| s.0) { + None if specials.is_empty() => ::first_pass::OperationId::Operation(None), + None if specials.len() == 1 => match specials[0] { + weedle::interface::Special::Getter(weedle::term::Getter) => + ::first_pass::OperationId::IndexingGetter, + weedle::interface::Special::Setter(weedle::term::Setter) => + ::first_pass::OperationId::IndexingSetter, + weedle::interface::Special::Deleter(weedle::term::Deleter) => + ::first_pass::OperationId::IndexingDeleter, + weedle::interface::Special::LegacyCaller(weedle::term::LegacyCaller) => + return Ok(()), + }, + Some(ref name) if specials.is_empty() => + ::first_pass::OperationId::Operation(Some(name.clone())), + _ => { + warn!("Unsupported specials on type {:?}", (self_name, identifier)); + return Ok(()) + } + }, + return_type, + self_name, + statik, + specials.len() == 1 || first_pass + .interfaces + .get(self_name) + .map(|interface_data| interface_data.global) + .unwrap_or(false), + util::throws(attrs), + ) + .map(wrap_import_function) + .map(|import| program.imports.push(import)); + Ok(()) +} diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 554f90f0..3320c38b 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -12,7 +12,7 @@ use weedle::common::Identifier; use weedle::types::*; use weedle::literal::{ConstValue, FloatLit, IntegerLit}; -use first_pass::FirstPassRecord; +use first_pass::{self, FirstPassRecord}; /// Take a type and create an immutable shared reference to that type. fn shared_ref(ty: syn::Type) -> syn::Type { @@ -830,6 +830,8 @@ impl<'src> FirstPassRecord<'src> { } /// Create a wasm-bindgen function, if possible. + /// + /// Currently fails on any variadic args. pub fn create_function( &self, name: &str, @@ -910,31 +912,31 @@ impl<'src> FirstPassRecord<'src> { pub fn create_basic_method( &self, arguments: &[weedle::argument::Argument], - operation_id: ::first_pass::OperationId, + operation_id: first_pass::OperationId, return_type: &weedle::types::ReturnType, self_name: &str, is_static: bool, structural: bool, catch: bool, ) -> Option { - let (overloaded, same_argument_names) = self.get_operation_overloading( + let (overloaded, same_argument_names) = self.get_method_overloading( arguments, &operation_id, self_name, ); let name = match &operation_id { - ::first_pass::OperationId::Constructor => panic!("constructors are unsupported"), - ::first_pass::OperationId::Operation(name) => match name { + first_pass::OperationId::Constructor => panic!("constructors are unsupported"), + first_pass::OperationId::Operation(name) => match name { None => { warn!("Operations without a name are unsupported"); return None; } Some(name) => name.to_string(), }, - ::first_pass::OperationId::IndexingGetter => "get".to_string(), - ::first_pass::OperationId::IndexingSetter => "set".to_string(), - ::first_pass::OperationId::IndexingDeleter => "delete".to_string(), + first_pass::OperationId::IndexingGetter => "get".to_string(), + first_pass::OperationId::IndexingSetter => "set".to_string(), + first_pass::OperationId::IndexingDeleter => "delete".to_string(), }; let kind = backend::ast::ImportFunctionKind::Method { @@ -943,11 +945,11 @@ impl<'src> FirstPassRecord<'src> { kind: backend::ast::MethodKind::Operation(backend::ast::Operation { is_static, kind: match &operation_id { - ::first_pass::OperationId::Constructor => panic!("constructors are unsupported"), - ::first_pass::OperationId::Operation(_) => backend::ast::OperationKind::Regular, - ::first_pass::OperationId::IndexingGetter => backend::ast::OperationKind::IndexingGetter, - ::first_pass::OperationId::IndexingSetter => backend::ast::OperationKind::IndexingSetter, - ::first_pass::OperationId::IndexingDeleter => backend::ast::OperationKind::IndexingDeleter, + first_pass::OperationId::Constructor => panic!("constructors are unsupported"), + first_pass::OperationId::Operation(_) => backend::ast::OperationKind::Regular, + first_pass::OperationId::IndexingGetter => backend::ast::OperationKind::IndexingGetter, + first_pass::OperationId::IndexingSetter => backend::ast::OperationKind::IndexingSetter, + first_pass::OperationId::IndexingDeleter => backend::ast::OperationKind::IndexingDeleter, }, }), }; @@ -965,17 +967,17 @@ impl<'src> FirstPassRecord<'src> { } }; let doc_comment = match &operation_id { - ::first_pass::OperationId::Constructor => panic!("constructors are unsupported"), - ::first_pass::OperationId::Operation(_) => Some( + first_pass::OperationId::Constructor => panic!("constructors are unsupported"), + first_pass::OperationId::Operation(_) => Some( format!( "The `{}()` method\n\n{}", name, mdn_doc(self_name, Some(&name)) ) ), - ::first_pass::OperationId::IndexingGetter => Some("The indexing getter\n\n".to_string()), - ::first_pass::OperationId::IndexingSetter => Some("The indexing setter\n\n".to_string()), - ::first_pass::OperationId::IndexingDeleter => Some("The indexing deleter\n\n".to_string()), + first_pass::OperationId::IndexingGetter => Some("The indexing getter\n\n".to_string()), + first_pass::OperationId::IndexingSetter => Some("The indexing setter\n\n".to_string()), + first_pass::OperationId::IndexingDeleter => Some("The indexing deleter\n\n".to_string()), }; self.create_function( @@ -993,10 +995,10 @@ impl<'src> FirstPassRecord<'src> { /// Whether operation is overloaded and /// whether there overloads with same argument names for given argument types - pub fn get_operation_overloading( + pub fn get_method_overloading( &self, arguments: &[weedle::argument::Argument], - id: &::first_pass::OperationId, + id: &first_pass::OperationId, self_name: &str, ) -> (bool, bool) { let data = match self.interfaces.get(self_name) { @@ -1023,6 +1025,98 @@ impl<'src> FirstPassRecord<'src> { ) } + /// Create a wasm-bindgen operation (free function with no `self` type), if possible. + pub fn create_namespace_operation( + &self, + arguments: &[weedle::argument::Argument], + operation_name: &str, + return_type: &weedle::types::ReturnType, + structural: bool, + catch: bool, + ) -> Option { + let (overloaded, same_argument_names) = self.get_namespaced_operation_overloading( + arguments, + &first_pass::OperationId::Operation(Some(operation_name)) + self_name, + ); + + let name = match &operation_id { + first_pass::OperationId::Operation(name) => match name { + None => { + warn!("Operations without a name are unsupported"); + return None; + } + Some(name) => name.to_string(), + }, + _ => { + warn!("operations in namespaces must be normal functions"); + return None; + } + }; + + let kind = backend::ast::ImportFunctionKind::Normal; + + let ret = match return_type { + weedle::types::ReturnType::Void(_) => None, + weedle::types::ReturnType::Type(ty) => { + match ty.to_syn_type(self, TypePosition::Return) { + None => { + warn!("Operation's return type is not yet supported: {:?}", ty); + return None; + } + Some(ty) => Some(ty), + } + } + }; + let doc_comment = format!("The `{}.{}()` function\n\n{}", + name, + mdn_doc(self_name, Some(&name))); + + self.create_function( + &name, + overloaded, + same_argument_names, + arguments, + ret, + kind, + structural, + catch, + doc_comment, + ) + } + + /// Whether operation is overloaded and + /// whether there overloads with same argument names for given argument types + pub fn get_namespaced_operation_overloading( + &self, + arguments: &[weedle::argument::Argument], + id: &first_pass::OperationId, + namespace_name: &str, + ) -> (bool, bool) { + let data = match self.namespaces.get(namespace_name) { + Some(data) => data, + None => return (false, false), + }; + let data = match data.operations.get(id) { + Some(data) => data, + None => return (false, false), + }; + let mut names = Vec::with_capacity(arguments.len()); + for arg in arguments { + match arg { + Argument::Single(arg) => names.push(arg.identifier.0), + Argument::Variadic(_) => return (false, false), + } + } + ( + data.overloaded, + *data + .argument_names_same + .get(&names) + .unwrap_or(&false) + ) + } + /// Create a wasm-bindgen getter method, if possible. pub fn create_getter( &self,