From b9dc937d73a7fe35aa332492ebd5dbaf7828294e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 28 Aug 2018 16:32:31 -0700 Subject: [PATCH] Refactor WebIDL code generation This commit refactors WebIDL code generation to walk over the fields of `FirstPassRecord` instead of walking the AST again. This helps remove redundancies like checking `is_chrome_only` as well as revisiting partial interfaces and such. This should make it more clear that the first pass's job is to walk the AST and collect all relevant information, while the codegen pass is purely about appending items to a `Program`. Additionally this refactoring will also soon be used to prepare different data structures for operation overloadings, avoiding the need to walk those ASTs twice. --- crates/webidl-tests/simple.js | 4 + crates/webidl-tests/simple.rs | 8 + crates/webidl-tests/simple.webidl | 7 + crates/webidl/src/first_pass.rs | 297 ++++--- crates/webidl/src/idl_type.rs | 2 +- crates/webidl/src/lib.rs | 1213 +++++++++-------------------- examples/julia_set/Cargo.toml | 1 + examples/julia_set/src/lib.rs | 23 +- 8 files changed, 622 insertions(+), 933 deletions(-) diff --git a/crates/webidl-tests/simple.js b/crates/webidl-tests/simple.js index bb1ace6b..d6859032 100644 --- a/crates/webidl-tests/simple.js +++ b/crates/webidl-tests/simple.js @@ -142,3 +142,7 @@ global.MixinFoo = class MixinFoo { this._bar += other; } }; + +global.Overloads = class { + foo() {} +}; diff --git a/crates/webidl-tests/simple.rs b/crates/webidl-tests/simple.rs index b59a9ad9..c9911a1d 100644 --- a/crates/webidl-tests/simple.rs +++ b/crates/webidl-tests/simple.rs @@ -114,3 +114,11 @@ fn mixin() { f.add_to_bar(MixinFoo::default_bar()); assert_eq!(f.bar(), 8); } + +#[wasm_bindgen_test] +fn overload_naming() { + let o = Overloads::new().unwrap(); + o.foo(); + o.foo_with_arg("x"); + o.foo_with_arg_and_a("x", 3); +} diff --git a/crates/webidl-tests/simple.webidl b/crates/webidl-tests/simple.webidl index 6df6de63..63d7b9d2 100644 --- a/crates/webidl-tests/simple.webidl +++ b/crates/webidl-tests/simple.webidl @@ -88,3 +88,10 @@ partial interface mixin MixinBar { }; MixinFoo includes MixinBar; + +[Constructor()] +interface Overloads { + void foo(); + void foo(DOMString arg, optional long a); + void foo(DOMString arg, (float or short) b); +}; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index ad93f575..a0614768 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -11,10 +11,10 @@ use std::collections::{BTreeMap, BTreeSet}; use weedle::{DictionaryDefinition, PartialDictionaryDefinition}; use weedle::argument::Argument; -use weedle::attribute::ExtendedAttribute; -use weedle::interface::{StringifierOrStatic, Special}; -use weedle::mixin::MixinMember; -use weedle::namespace::NamespaceMember; +use weedle::attribute::*; +use weedle::interface::*; +use weedle::mixin::*; +use weedle::namespace::OperationNamespaceMember; use weedle; use super::Result; @@ -25,7 +25,7 @@ use util::camel_case_ident; #[derive(Default)] pub(crate) struct FirstPassRecord<'src> { pub(crate) interfaces: BTreeMap<&'src str, InterfaceData<'src>>, - pub(crate) enums: BTreeSet<&'src str>, + pub(crate) enums: BTreeMap<&'src str, &'src weedle::EnumDefinition<'src>>, /// The mixins, mapping their name to the webidl ast node for the mixin. pub(crate) mixins: BTreeMap<&'src str, MixinData<'src>>, pub(crate) typedefs: BTreeMap<&'src str, &'src weedle::types::Type<'src>>, @@ -40,8 +40,13 @@ pub(crate) struct InterfaceData<'src> { /// Whether only partial interfaces were encountered pub(crate) partial: bool, pub(crate) global: bool, + pub(crate) attributes: Vec<&'src AttributeInterfaceMember<'src>>, + pub(crate) consts: Vec<&'src ConstMember<'src>>, + pub(crate) methods: Vec<&'src OperationInterfaceMember<'src>>, pub(crate) operations: BTreeMap, OperationData<'src>>, pub(crate) superclass: Option<&'src str>, + pub(crate) definition_attributes: Option<&'src [ExtendedAttribute<'src>]>, + pub(crate) constructors: Vec<(&'src str, &'src [Argument<'src>])>, } /// We need to collect mixin data during the first pass, to be used later. @@ -49,7 +54,9 @@ pub(crate) struct InterfaceData<'src> { pub(crate) struct MixinData<'src> { /// Whether only partial mixins were encountered pub(crate) partial: bool, - pub(crate) members: Vec<&'src MixinMember<'src>>, + pub(crate) attributes: Vec<&'src AttributeMixinMember<'src>>, + pub(crate) consts: Vec<&'src ConstMember<'src>>, + pub(crate) methods: Vec<&'src OperationMixinMember<'src>>, pub(crate) operations: BTreeMap, OperationData<'src>>, } @@ -57,8 +64,7 @@ pub(crate) struct MixinData<'src> { #[derive(Default)] pub(crate) struct NamespaceData<'src> { /// Whether only partial namespaces were encountered - pub(crate) partial: bool, - pub(crate) members: Vec<&'src NamespaceMember<'src>>, + pub(crate) members: Vec<&'src OperationNamespaceMember<'src>>, pub(crate) operations: BTreeMap, OperationData<'src>>, } @@ -117,8 +123,14 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { 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 + + Implements(_) => Ok(()), + Callback(..) => { + warn!("Unsupported WebIDL Callback definition: {:?}", self); + Ok(()) + } + CallbackInterface(..) => { + warn!("Unsupported WebIDL CallbackInterface definition: {:?}", self); Ok(()) } } @@ -127,6 +139,10 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { impl<'src> FirstPass<'src, ()> for weedle::DictionaryDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { + if util::is_chrome_only(&self.attributes) { + return Ok(()); + } + record.dictionaries.entry(self.identifier.0) .or_default() .definition = Some(self); @@ -136,6 +152,10 @@ impl<'src> FirstPass<'src, ()> for weedle::DictionaryDefinition<'src> { impl<'src> FirstPass<'src, ()> for weedle::PartialDictionaryDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { + if util::is_chrome_only(&self.attributes) { + return Ok(()); + } + record.dictionaries.entry(self.identifier.0) .or_default() .partials @@ -150,7 +170,7 @@ impl<'src> FirstPass<'src, ()> for weedle::EnumDefinition<'src> { return Ok(()); } - if !record.enums.insert(self.identifier.0) { + if record.enums.insert(self.identifier.0, self).is_some() { info!("Encountered multiple enum declarations: {}", self.identifier.0); } @@ -187,7 +207,7 @@ fn first_pass_operation<'src>( self_name: &'src str, ids: &[OperationId<'src>], arguments: &[Argument<'src>], -) -> Result<()> { +) { let mut names = Vec::with_capacity(arguments.len()); for argument in arguments { match argument { @@ -228,8 +248,6 @@ fn first_pass_operation<'src>( .and_modify(|same_argument_names| *same_argument_names = true) .or_insert(false); } - - Ok(()) } impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { @@ -238,6 +256,11 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { return Ok(()); } + if util::is_no_interface_object(&self.attributes) { + info!("Skipping because of `NoInterfaceObject` attribute: {:?}", self.identifier.0); + return Ok(()); + } + { let interface_data = record .interfaces @@ -245,11 +268,16 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { .or_default(); interface_data.partial = false; interface_data.superclass = self.inheritance.map(|s| s.identifier.0); + interface_data.definition_attributes = self.attributes.as_ref() + .map(|l| &l.body.list[..]); } - if let Some(attrs) = &self.attributes { - for attr in &attrs.body.list { - attr.first_pass(record, self.identifier.0)?; + for attr in attrs.body.list.iter() { + process_interface_attribute( + record, + self.identifier.0, + attr, + ); } } @@ -261,84 +289,123 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { } } +fn process_interface_attribute<'src>( + record: &mut FirstPassRecord<'src>, + self_name: &'src str, + attr: &'src ExtendedAttribute<'src> +) { + match attr { + ExtendedAttribute::ArgList(list) + if list.identifier.0 == "Constructor" => + { + record.interfaces + .get_mut(self_name) + .unwrap() + .constructors.push((self_name, &list.args.body.list)); + first_pass_operation( + record, + FirstPassOperationType::Interface, + self_name, + &[OperationId::Constructor], + &list.args.body.list, + ) + } + ExtendedAttribute::NoArgs(other) if (other.0).0 == "Constructor" => { + record.interfaces + .get_mut(self_name) + .unwrap() + .constructors.push((self_name, &[])); + first_pass_operation( + record, + FirstPassOperationType::Interface, + self_name, + &[OperationId::Constructor], + &[], + ) + } + ExtendedAttribute::NamedArgList(list) + if list.lhs_identifier.0 == "NamedConstructor" => + { + record.interfaces + .get_mut(self_name) + .unwrap() + .constructors.push((list.rhs_identifier.0, &list.args.body.list)); + first_pass_operation( + record, + FirstPassOperationType::Interface, + self_name, + &[OperationId::Constructor], + &list.args.body.list, + ) + } + ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "Global" => { + record.interfaces.get_mut(self_name).unwrap().global = true; + } + ExtendedAttribute::IdentList(id) if id.identifier.0 == "Global" => { + record.interfaces.get_mut(self_name).unwrap().global = true; + } + _ => {} + } +} + impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } - record .interfaces .entry(self.identifier.0) .or_insert_with(|| InterfaceData { partial: true, - global: false, - operations: Default::default(), - superclass: None, + ..Default::default() }, ); - for member in &self.members.body { member.first_pass(record, self.identifier.0)?; } - Ok(()) } } -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( - record, - FirstPassOperationType::Interface, - self_name, - &[OperationId::Constructor], - &list.args.body.list, - ) - } - ExtendedAttribute::NoArgs(name) if (name.0).0 == "Constructor" => { - first_pass_operation( - record, - FirstPassOperationType::Interface, - self_name, - &[OperationId::Constructor], - &[], - ) - } - ExtendedAttribute::NamedArgList(list) - if list.lhs_identifier.0 == "NamedConstructor" => - { - first_pass_operation( - record, - FirstPassOperationType::Interface, - self_name, - &[OperationId::Constructor], - &list.args.body.list, - ) - } - ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "Global" => { - record.interfaces.get_mut(self_name).unwrap().global = true; - Ok(()) - } - ExtendedAttribute::IdentList(id) if id.identifier.0 == "Global" => { - record.interfaces.get_mut(self_name).unwrap().global = true; - Ok(()) - } - _ => Ok(()) - } - } -} - impl<'src> FirstPass<'src, &'src str> for weedle::interface::InterfaceMember<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str) -> Result<()> { match self { - weedle::interface::InterfaceMember::Operation(op) => { + InterfaceMember::Attribute(attr) => { + attr.first_pass(record, self_name) + } + InterfaceMember::Operation(op) => { op.first_pass(record, self_name) } - _ => Ok(()), + InterfaceMember::Const(const_) => { + if util::is_chrome_only(&const_.attributes) { + return Ok(()); + } + record.interfaces + .get_mut(self_name) + .unwrap() + .consts + .push(const_); + Ok(()) + } + InterfaceMember::Iterable(_iterable) => { + warn!("Unsupported WebIDL iterable interface member: {:?}", self); + Ok(()) + } + // TODO + InterfaceMember::Maplike(_) => { + warn!("Unsupported WebIDL Maplike interface member: {:?}", self); + Ok(()) + } + InterfaceMember::Stringifier(_) => { + warn!("Unsupported WebIDL Stringifier interface member: {:?}", self); + Ok(()) + } + InterfaceMember::Setlike(_) => { + warn!("Unsupported WebIDL Setlike interface member: {:?}", self); + Ok(()) + } } } } @@ -357,6 +424,13 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceM warn!("Unsupported webidl stringifier: {:?}", self); return Ok(()) } + + record.interfaces + .get_mut(self_name) + .unwrap() + .methods + .push(self); + let mut ids = vec![OperationId::Operation(self.identifier.map(|s| s.0))]; for special in self.specials.iter() { ids.push(match special { @@ -372,7 +446,23 @@ impl<'src> FirstPass<'src, &'src str> for weedle::interface::OperationInterfaceM self_name, &ids, &self.args.body.list, - ) + ); + Ok(()) + } +} + +impl<'src> FirstPass<'src, &'src str> for weedle::interface::AttributeInterfaceMember<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str) -> Result<()> { + if util::is_chrome_only(&self.attributes) { + return Ok(()); + } + + record.interfaces + .get_mut(self_name) + .unwrap() + .attributes + .push(self); + Ok(()) } } @@ -388,7 +478,6 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceMixinDefinition<'src>{ .entry(self.identifier.0) .or_default(); mixin_data.partial = false; - mixin_data.members.extend(&self.members.body); } for member in &self.members.body { @@ -411,12 +500,9 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceMixinDefinition<'src> .or_insert_with(|| MixinData { partial: true, - members: Default::default(), - operations: Default::default(), + ..Default::default() }, - ) - .members - .extend(&self.members.body); + ); for member in &self.members.body { member.first_pass(record, self.identifier.0)?; @@ -429,10 +515,23 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceMixinDefinition<'src> impl<'src> FirstPass<'src, &'src str> for weedle::mixin::MixinMember<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str) -> Result<()> { match self { - weedle::mixin::MixinMember::Operation(op) => { - op.first_pass(record, self_name) + MixinMember::Operation(op) => op.first_pass(record, self_name), + MixinMember::Attribute(a) => a.first_pass(record, self_name), + MixinMember::Const(a) => { + if util::is_chrome_only(&a.attributes) { + return Ok(()); + } + record.mixins + .get_mut(self_name) + .unwrap() + .consts + .push(a); + Ok(()) + } + MixinMember::Stringifier(_) => { + warn!("Unsupported WebIDL stringifier mixin member: {:?}", self); + Ok(()) } - _ => Ok(()), } } } @@ -448,13 +547,34 @@ impl<'src> FirstPass<'src, &'src str> for weedle::mixin::OperationMixinMember<'s return Ok(()) } + record.mixins + .get_mut(self_name) + .unwrap() + .methods + .push(self); + first_pass_operation( record, FirstPassOperationType::Mixin, self_name, &[OperationId::Operation(self.identifier.map(|s| s.0.clone()))], &self.args.body.list, - ) + ); + Ok(()) + } +} + +impl<'src> FirstPass<'src, &'src str> for weedle::mixin::AttributeMixinMember<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, self_name: &'src str) -> Result<()> { + if util::is_chrome_only(&self.attributes) { + return Ok(()); + } + record.mixins + .get_mut(self_name) + .unwrap() + .attributes + .push(self); + Ok(()) } } @@ -481,16 +601,12 @@ impl<'src> FirstPass<'src, ()> for weedle::NamespaceDefinition<'src> { record .namespaces .entry(self.identifier.0) - .and_modify(|namespace_data| namespace_data.partial = false) .or_insert_with(|| NamespaceData { - partial: true, members: Default::default(), operations: Default::default(), }, - ) - .members - .extend(&self.members.body); + ); for member in &self.members.body { member.first_pass(record, self.identifier.0)?; @@ -509,9 +625,7 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialNamespaceDefinition<'src> { record .namespaces .entry(self.identifier.0) - .or_default() - .members - .extend(&self.members.body); + .or_default(); for member in &self.members.body { member.first_pass(record, self.identifier.0)?; @@ -538,13 +652,16 @@ impl<'src> FirstPass<'src, &'src str> for weedle::namespace::OperationNamespaceM return Ok(()) } + record.namespaces.get_mut(self_name).unwrap().members.push(self); + first_pass_operation( record, FirstPassOperationType::Namespace, self_name, &[OperationId::Operation(self.identifier.map(|s| s.0.clone()))], &self.args.body.list, - ) + ); + Ok(()) } } diff --git a/crates/webidl/src/idl_type.rs b/crates/webidl/src/idl_type.rs index 1f88d008..3992b20d 100644 --- a/crates/webidl/src/idl_type.rs +++ b/crates/webidl/src/idl_type.rs @@ -287,7 +287,7 @@ impl<'a> ToIdlType<'a> for Identifier<'a> { Some(IdlType::Interface(self.0)) } else if record.dictionaries.contains_key(self.0) { Some(IdlType::Dictionary(self.0)) - } else if record.enums.contains(self.0) { + } else if record.enums.contains_key(self.0) { Some(IdlType::Enum(self.0)) } else { warn!("Unrecognized type: {}", self.0); diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index af09eaa9..d1b4205e 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -44,7 +44,7 @@ use failure::ResultExt; use heck::{ShoutySnakeCase, SnakeCase}; use proc_macro2::{Ident, Span}; use weedle::argument::Argument; -use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList}; +use weedle::attribute::{ExtendedAttributeList}; use weedle::dictionary::DictionaryMember; use first_pass::{FirstPass, FirstPassRecord, OperationId}; @@ -81,13 +81,6 @@ fn parse(webidl_source: &str) -> Result { format_err!("failed to parse WebIDL") .context(ErrorKind::ParsingWebIDLSourcePos(pos)).into() } - // webidl::ParseError::UnrecognizedToken { token: Some((start, ..)), .. } => { - // ErrorKind::ParsingWebIDLSourcePos(*start) - // } - // webidl::ParseError::ExtraToken { token: (start, ..) } => { - // ErrorKind::ParsingWebIDLSourcePos(*start) - // }, - // _ => ErrorKind::ParsingWebIDLSource }); } }; @@ -95,7 +88,19 @@ fn parse(webidl_source: &str) -> Result { let mut first_pass_record = Default::default(); definitions.first_pass(&mut first_pass_record, ())?; let mut program = Default::default(); - definitions.webidl_parse(&mut program, &first_pass_record, ())?; + + for e in first_pass_record.enums.values() { + first_pass_record.append_enum(&mut program, e); + } + for d in first_pass_record.dictionaries.values() { + first_pass_record.append_dictionary(&mut program, d); + } + for (name, n) in first_pass_record.namespaces.iter() { + first_pass_record.append_ns(&mut program, name, n); + } + for (name, d) in first_pass_record.interfaces.iter() { + first_pass_record.append_interface(&mut program, name, d); + } Ok(program) } @@ -145,673 +150,19 @@ fn compile_ast(mut ast: backend::ast::Program) -> String { tokens.to_string() } -/// The main trait for parsing WebIDL AST into wasm-bindgen AST. -trait WebidlParse<'src, Ctx> { - /// Parse `self` into wasm-bindgen AST, and insert it into `program`. - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - context: Ctx, - ) -> Result<()>; -} - -impl<'src> WebidlParse<'src, ()> for [weedle::Definition<'src>] { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - (): (), - ) -> Result<()> { - for def in self { - def.webidl_parse(program, first_pass, ())?; - } - Ok(()) - } -} - -impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - (): (), - ) -> Result<()> { - match self { - weedle::Definition::Enum(enumeration) => { - enumeration.webidl_parse(program, first_pass, ())? - } - weedle::Definition::Interface(interface) => { - interface.webidl_parse(program, first_pass, ())? - } - weedle::Definition::PartialInterface(interface) => { - interface.webidl_parse(program, first_pass, ())? - } - | weedle::Definition::Typedef(_) - | weedle::Definition::InterfaceMixin(_) - | weedle::Definition::PartialInterfaceMixin(_) - | weedle::Definition::IncludesStatement(..) - | weedle::Definition::PartialDictionary(..) - | weedle::Definition::PartialNamespace(..)=> { - // handled in the first pass - } - weedle::Definition::Implements(..) => { - // nothing to do for this, ignore it - } - weedle::Definition::Namespace(namespace) => { - namespace.webidl_parse(program, first_pass, ())? - } - weedle::Definition::Dictionary(dict) => { - dict.webidl_parse(program, first_pass, ())? - } - - // TODO - weedle::Definition::Callback(..) => { - warn!("Unsupported WebIDL Callback definition: {:?}", self) - } - weedle::Definition::CallbackInterface(..) => { - warn!("Unsupported WebIDL CallbackInterface definition: {:?}", self) - } - } - Ok(()) - } -} - -impl<'src> WebidlParse<'src, ()> for weedle::InterfaceDefinition<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - (): (), - ) -> Result<()> { - if util::is_chrome_only(&self.attributes) { - info!("Skipping because of `ChromeOnly` attribute: {:?}", self); - return Ok(()); - } - - if util::is_no_interface_object(&self.attributes) { - info!("Skipping because of `NoInterfaceObject` attribute: {:?}", self); - return Ok(()); - } - - let doc_comment = Some(format!( - "The `{}` object\n\n{}", - self.identifier.0, - mdn_doc(self.identifier.0, None), - )); - - program.imports.push(backend::ast::Import { - module: None, - js_namespace: None, - kind: backend::ast::ImportKind::Type(backend::ast::ImportType { - vis: public(), - rust_name: rust_ident(camel_case_ident(self.identifier.0).as_str()), - js_name: self.identifier.0.to_string(), - attrs: Vec::new(), - doc_comment, - instanceof_shim: format!("__widl_instanceof_{}", self.identifier.0), - extends: first_pass.all_superclasses(self.identifier.0) - .map(|name| Ident::new(&name, Span::call_site())) - .collect(), - }), - }); - - if let Some(attrs) = &self.attributes { - for attr in &attrs.body.list { - attr.webidl_parse(program, first_pass, self)?; - } - } - - fn parse<'src>( - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &str, - mixin_name: &str, - ) -> Result<()> { - if let Some(mixin_data) = first_pass.mixins.get(mixin_name) { - for member in &mixin_data.members { - member.webidl_parse(program, first_pass, self_name)?; - } - } - if let Some(mixin_names) = first_pass.includes.get(mixin_name) { - for mixin_name in mixin_names { - parse(program, first_pass, self_name, mixin_name)?; - } - } - Ok(()) - } - - for member in &self.members.body { - member.webidl_parse(program, first_pass, self.identifier.0)?; - } - - parse(program, first_pass, self.identifier.0, self.identifier.0)?; - - Ok(()) - } -} - -impl<'src> WebidlParse<'src, ()> for weedle::PartialInterfaceDefinition<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - (): (), - ) -> Result<()> { - if util::is_chrome_only(&self.attributes) { - return Ok(()); - } - - if first_pass - .interfaces - .get(self.identifier.0) - .map(|interface_data| interface_data.partial) - .unwrap_or(true) { - info!( - "Partial interface {} missing non-partial interface", - self.identifier.0 - ); - } - - for member in &self.members.body { - member.webidl_parse(program, first_pass, self.identifier.0)?; - } - - Ok(()) - } -} - -impl<'src> WebidlParse<'src, &'src weedle::InterfaceDefinition<'src>> for ExtendedAttribute<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - interface: &'src weedle::InterfaceDefinition<'src>, - ) -> Result<()> { - let mut add_constructor = |arguments: &[Argument], class: &str| { - let (overloaded, same_argument_names) = first_pass.get_operation_overloading( - arguments, - &::first_pass::OperationId::Constructor, - interface.identifier.0, - false, - ); - - let self_ty = ident_ty(rust_ident(camel_case_ident(interface.identifier.0).as_str())); - - let kind = backend::ast::ImportFunctionKind::Method { - class: class.to_string(), - ty: self_ty.clone(), - kind: backend::ast::MethodKind::Constructor, - }; - - let structural = false; - - // Constructors aren't annotated with `[Throws]` extended attributes - // (how could they be, since they themselves are extended - // attributes?) so we must conservatively assume that they can - // always throw. - // - // From https://heycam.github.io/webidl/#Constructor (emphasis - // mine): - // - // > The prose definition of a constructor must either return an IDL - // > value of a type corresponding to the interface the - // > `[Constructor]` extended attribute appears on, **or throw an - // > exception**. - let throws = true; - - for import_function in first_pass.create_function( - "new", - overloaded, - same_argument_names, - &match first_pass.convert_arguments(arguments) { - Some(arguments) => arguments, - None => return, - }, - IdlType::Interface(interface.identifier.0), - kind, - structural, - throws, - None, - ) { - program.imports.push(wrap_import_function(import_function)); - } - }; - - match self { - ExtendedAttribute::ArgList(list) - if list.identifier.0 == "Constructor" => - { - add_constructor(&list.args.body.list, interface.identifier.0) - } - ExtendedAttribute::NoArgs(other) if (other.0).0 == "Constructor" => { - add_constructor(&[], interface.identifier.0) - } - ExtendedAttribute::NamedArgList(list) - if list.lhs_identifier.0 == "NamedConstructor" => - { - add_constructor(&list.args.body.list, list.rhs_identifier.0) - } - - // these appear to be mapping to gecko preferences, seems like we - // can safely ignore - ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "Pref" => {} - - // looks to be a gecko-specific attribute to tie WebIDL back to C++ - // functions perhaps - ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "Func" => {} - ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "JSImplementation" => {} - ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "HeaderFile" => {} - - // Not actually mentioned in the spec and presumably a hint to - // Gecko's JS engine? Unsure, but seems like it doesn't matter to us - ExtendedAttribute::NoArgs(id) - if (id.0).0 == "ProbablyShortLivingWrapper" => {} - - // Indicates something about enumerable properties, we're not too - // interested in it - // https://heycam.github.io/webidl/#LegacyUnenumerableNamedProperties - ExtendedAttribute::NoArgs(id) - if (id.0).0 == "LegacyUnenumerableNamedProperties" => {} - - // Indicates where objects are defined (web workers and such), we - // may later want to use this for cfgs but for now we ignore it. - // https://heycam.github.io/webidl/#Exposed - ExtendedAttribute::Ident(id) if id.lhs_identifier.0 == "Exposed" => {} - ExtendedAttribute::IdentList(id) if id.identifier.0 == "Exposed" => {} - - // We handle this with the "structural" attribute elsewhere - ExtendedAttribute::IdentList(id) if id.identifier.0 == "Global" => {} - - // Seems like it's safe to ignore for now, just telling us where a - // binding appears - // https://heycam.github.io/webidl/#SecureContext - ExtendedAttribute::NoArgs(id) if (id.0).0 == "SecureContext" => {} - - // We handle this elsewhere - ExtendedAttribute::NoArgs(id) if (id.0).0 == "Unforgeable" => {} - - // Looks like this attribute just says that we can't call the - // constructor - // https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor - ExtendedAttribute::NoArgs(id) if (id.0).0 == "HTMLConstructor" => {} - - ExtendedAttribute::ArgList(_) - | ExtendedAttribute::Ident(_) - | ExtendedAttribute::IdentList(_) - | ExtendedAttribute::NamedArgList(_) - | ExtendedAttribute::NoArgs(_) => { - warn!("Unsupported WebIDL extended attribute: {:?}", self); - } - } - - Ok(()) - } -} - -impl<'src> WebidlParse<'src, &'src str> for weedle::interface::InterfaceMember<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &'src str, - ) -> Result<()> { - use weedle::interface::InterfaceMember::*; - - match self { - Attribute(attr) => { - attr.webidl_parse(program, first_pass, self_name) - } - Operation(op) => { - op.webidl_parse(program, first_pass, self_name) - } - Const(const_) => { - const_.webidl_parse(program, first_pass, self_name) - } - Iterable(iterable) => { - iterable.webidl_parse(program, first_pass, self_name) - } - // TODO - Maplike(_) => { - warn!("Unsupported WebIDL Maplike interface member: {:?}", self); - Ok(()) - } - Stringifier(_) => { - warn!("Unsupported WebIDL Stringifier interface member: {:?}", self); - Ok(()) - } - Setlike(_) => { - warn!("Unsupported WebIDL Setlike interface member: {:?}", self); - Ok(()) - } - } - } -} - -impl<'a, 'src> WebidlParse<'src, &'a str> for weedle::mixin::MixinMember<'src> { - fn webidl_parse( +impl<'src> FirstPassRecord<'src> { + fn append_enum( &self, program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &'a str, - ) -> Result<()> { - match self { - weedle::mixin::MixinMember::Attribute(attr) => { - attr.webidl_parse(program, first_pass, self_name) - } - weedle::mixin::MixinMember::Operation(op) => { - op.webidl_parse(program, first_pass, self_name) - } - weedle::mixin::MixinMember::Const(const_) => { - const_.webidl_parse(program, first_pass, self_name) - } - // TODO - weedle::mixin::MixinMember::Stringifier(_) => { - warn!("Unsupported WebIDL stringifier mixin member: {:?}", self); - Ok(()) - } - } - } -} - -impl<'src> WebidlParse<'src, &'src str> for weedle::interface::AttributeInterfaceMember<'src> { - fn webidl_parse( - &self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &'src str, - ) -> Result<()> { - member_attribute( - program, - first_pass, - self_name, - &self.attributes, - self.modifier, - self.readonly.is_some(), - &self.type_, - self.identifier.0, - ) - } -} - -impl<'src> WebidlParse<'src, &'src str> for weedle::mixin::AttributeMixinMember<'src> { - fn webidl_parse( - &self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &'src str, - ) -> Result<()> { - member_attribute( - program, - first_pass, - self_name, - &self.attributes, - if let Some(s) = self.stringifier { - Some(weedle::interface::StringifierOrInheritOrStatic::Stringifier(s)) - } else { - None - }, - self.readonly.is_some(), - &self.type_, - self.identifier.0, - ) - } -} - -fn member_attribute<'src>( - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &'src str, - attrs: &'src Option, - modifier: Option, - readonly: bool, - type_: &'src weedle::types::AttributedType<'src>, - identifier: &'src str, -) -> Result<()> { - use weedle::interface::StringifierOrInheritOrStatic::*; - - if util::is_chrome_only(attrs) { - return Ok(()); - } - - let is_static = match modifier { - Some(Stringifier(_)) => { - warn!("Unsupported stringifier on type: {:?}", (self_name, identifier)); - return Ok(()) - } - Some(Inherit(_)) => false, - Some(Static(_)) => true, - None => false, - }; - - if type_.attributes.is_some() { - warn!("Unsupported attributes on type: {:?}", (self_name, identifier)); - return Ok(()) - } - - let is_structural = util::is_structural(attrs); - let throws = util::throws(attrs); - let global = first_pass - .interfaces - .get(self_name) - .map(|interface_data| interface_data.global) - .unwrap_or(false); - - for import_function in first_pass.create_getter( - identifier, - &type_.type_, - self_name, - is_static, - is_structural, - throws, - global, + enum_: &'src weedle::EnumDefinition<'src> ) { - program.imports.push(wrap_import_function(import_function)); - } - - if !readonly { - for import_function in first_pass.create_setter( - identifier, - type_.type_.clone(), - self_name, - is_static, - is_structural, - throws, - global, - ) { - program.imports.push(wrap_import_function(import_function)); - } - } - - Ok(()) -} - -impl<'src> WebidlParse<'src, &'src str> for weedle::interface::OperationInterfaceMember<'src> { - fn webidl_parse( - &self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &'src str, - ) -> Result<()> { - member_operation( - program, - first_pass, - self_name, - &self.attributes, - self.modifier, - &self.specials, - &self.return_type, - &self.args.body.list, - &self.identifier, - ) - } -} - -impl<'src> WebidlParse<'src, &'src str> for weedle::mixin::OperationMixinMember<'src> { - fn webidl_parse( - &self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &'src str, - ) -> Result<()> { - member_operation( - program, - first_pass, - self_name, - &self.attributes, - None, - &[], - &self.return_type, - &self.args.body.list, - &self.identifier, - ) - } -} - -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::*; - use weedle::interface::Special; - - if util::is_chrome_only(attrs) { - info!("Skipping `ChromeOnly` operation: {:?}", (self_name, identifier)); - return Ok(()); - } - - let is_static = match modifier { - Some(Stringifier(_)) => { - warn!("Unsupported stringifier on type: {:?}", (self_name, identifier)); - return Ok(()) - } - Some(Static(_)) => true, - None => false, - }; - - let mut operation_ids = vec![ - OperationId::Operation(identifier.map(|s| s.0)), - ]; - if specials.len() > 1 { - warn!( - "Unsupported specials: ({:?}) on type {:?}", - specials, - (self_name, identifier), - ); - return Ok(()) - } else if specials.len() == 1 { - let id = match specials[0] { - Special::Getter(weedle::term::Getter) => OperationId::IndexingGetter, - Special::Setter(weedle::term::Setter) => OperationId::IndexingSetter, - Special::Deleter(weedle::term::Deleter) => OperationId::IndexingDeleter, - Special::LegacyCaller(weedle::term::LegacyCaller) => { - warn!("Unsupported legacy caller: {:?}", (self_name, identifier)); - return Ok(()); - }, - }; - operation_ids.push(id); - } - - let global = first_pass - .interfaces - .get(self_name) - .map(|interface_data| interface_data.global) - .unwrap_or(false); - - for id in operation_ids { - let methods = first_pass - .create_basic_method( - args, - id, - return_type, - self_name, - is_static, - match id { - OperationId::IndexingGetter | - OperationId::IndexingSetter | - OperationId::IndexingDeleter => true, - _ => false, - }, - util::throws(attrs), - global, - ); - - for method in methods { - program.imports.push(wrap_import_function(method)); - } - } - Ok(()) -} - -impl<'src> WebidlParse<'src, &'src str> for weedle::interface::IterableInterfaceMember<'src> { - fn webidl_parse( - &self, - _program: &mut backend::ast::Program, - _first_pass: &FirstPassRecord<'src>, - _self_name: &'src str, - ) -> Result<()> { - // if util::is_chrome_only(&self.attributes) { - // return Ok(()); - // } - -/* TODO - let throws = util::throws(&self.extended_attributes); - let return_value = weedle::ReturnType::NonVoid(self.value_type.clone()); - let args = []; - first_pass - .create_basic_method( - &args, - Some(&"values".to_string()), - &return_value, - self_name, - false, - false, // Should be false - ) - .map(wrap_import_function) - .map(|import| program.imports.push(import)); - - first_pass - .create_basic_method( - &args, - Some(&"keys".to_string()), - &return_value, // Should be a number - self_name, - false, - false, // Should be false - ) - .map(wrap_import_function) - .map(|import| program.imports.push(import)); -*/ - - Ok(()) - } -} - -impl<'src> WebidlParse<'src, ()> for weedle::EnumDefinition<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - _: &FirstPassRecord<'src>, - (): (), - ) -> Result<()> { - if util::is_chrome_only(&self.attributes) { - return Ok(()); - } - - let variants = &self.values.body.list; + let variants = &enum_.values.body.list; program.imports.push(backend::ast::Import { module: None, js_namespace: None, kind: backend::ast::ImportKind::Enum(backend::ast::ImportEnum { vis: public(), - name: rust_ident(camel_case_ident(self.identifier.0).as_str()), + name: rust_ident(camel_case_ident(enum_.identifier.0).as_str()), variants: variants .iter() .map(|v| { @@ -826,174 +177,29 @@ impl<'src> WebidlParse<'src, ()> for weedle::EnumDefinition<'src> { rust_attrs: vec![parse_quote!(#[derive(Copy, Clone, PartialEq, Debug)])], }), }); - - Ok(()) } -} -impl<'src> WebidlParse<'src, &'src str> for weedle::interface::ConstMember<'src> { - fn webidl_parse( - &'src self, + // tons more data for what's going on here at + // https://www.w3.org/TR/WebIDL-1/#idl-dictionaries + fn append_dictionary( + &self, program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - self_name: &'src str, - ) -> Result<()> { - if util::is_chrome_only(&self.attributes) { - return Ok(()); - } - - let idl_type = match self.const_type.to_idl_type(first_pass) { - None => return Ok(()), - Some(idl_type) => idl_type, + data: &first_pass::DictionaryData, + ) { + let def = match data.definition { + Some(def) => def, + None => return, }; - - let ty = match idl_type.to_syn_type(TypePosition::Return) { - None => { - warn!( - "Cannot convert const type to syn type: {:?} in {:?} on {:?}", - idl_type, - self, - self_name - ); - return Ok(()); - }, - Some(ty) => ty, - }; - - program.consts.push(backend::ast::Const { - vis: public(), - name: rust_ident(self.identifier.0.to_shouty_snake_case().as_str()), - class: Some(rust_ident(camel_case_ident(&self_name).as_str())), - ty, - value: webidl_const_v_to_backend_const_v(&self.const_value), - }); - - Ok(()) - } -} - -impl<'src> WebidlParse<'src, ()> for weedle::NamespaceDefinition<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - (): (), - ) -> Result<()> { - if util::is_chrome_only(&self.attributes) { - return Ok(()); - } - - if let Some(attrs) = &self.attributes { - for attr in &attrs.body.list { - attr.webidl_parse(program, first_pass, self)?; - } - } - - let mut module = backend::ast::Module { - vis: public(), - name: rust_ident(self.identifier.0.to_snake_case().as_str()), - imports: Default::default(), - }; - - if let Some(namespace_data) = first_pass.namespaces.get(&self.identifier.0) { - for member in &namespace_data.members { - member.webidl_parse(program, first_pass, (&self.identifier.0, &mut module))?; - } - } - - program.modules.push(module); - - Ok(()) - } -} - -impl<'src> WebidlParse<'src, &'src weedle::NamespaceDefinition<'src>> for ExtendedAttribute<'src> { - fn webidl_parse( - &'src self, - _program: &mut backend::ast::Program, - _first_pass: &FirstPassRecord<'src>, - _namespace: &'src weedle::NamespaceDefinition<'src>, - ) -> Result<()> { - warn!("Unsupported WebIDL extended attribute: {:?}", self); - Ok(()) - } -} - -impl<'src> WebidlParse<'src, (&'src str, &'src mut backend::ast::Module)> for weedle::namespace::NamespaceMember<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - (self_name, module): (&'src str, &mut backend::ast::Module), - ) -> Result<()> { - match self { - weedle::namespace::NamespaceMember::Operation(op) => { - op.webidl_parse(program, first_pass, (self_name, module))?; - } - weedle::namespace::NamespaceMember::Attribute(attr) => { - warn!("Unsupported attribute namespace member: {:?}", attr) - } - } - Ok(()) - } -} - -impl<'src> WebidlParse<'src, (&'src str, &'src mut backend::ast::Module)> for weedle::namespace::OperationNamespaceMember<'src> { - fn webidl_parse( - &'src self, - _program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - (self_name, module): (&'src str, &mut backend::ast::Module), - ) -> Result<()> { - if util::is_chrome_only(&self.attributes) { - return Ok(()); - } - - for import_function in first_pass.create_namespace_operation( - &self.args.body.list, - self.identifier.as_ref().map(|id| id.0), - &self.return_type, - self_name, - util::throws(&self.attributes) - ) { - module.imports.push( - backend::ast::Import { - module: None, - js_namespace: Some(raw_ident(self_name)), - kind: backend::ast::ImportKind::Function(import_function), - } - ); - }; - - Ok(()) - } -} - -// tons more data for what's going on here at -// https://www.w3.org/TR/WebIDL-1/#idl-dictionaries -impl<'src> WebidlParse<'src, ()> for weedle::DictionaryDefinition<'src> { - fn webidl_parse( - &'src self, - program: &mut backend::ast::Program, - first_pass: &FirstPassRecord<'src>, - (): (), - ) -> Result<()> { - if util::is_chrome_only(&self.attributes) { - return Ok(()); - } - let mut fields = Vec::new(); - if !push_members(first_pass, self.identifier.0, &mut fields) { - return Ok(()) + if !push_members(self, def.identifier.0, &mut fields) { + return } program.dictionaries.push(ast::Dictionary { - name: rust_ident(&camel_case_ident(self.identifier.0)), + name: rust_ident(&camel_case_ident(def.identifier.0)), fields, }); - return Ok(()); - fn push_members<'src>( data: &FirstPassRecord<'src>, dict: &'src str, @@ -1078,4 +284,361 @@ impl<'src> WebidlParse<'src, ()> for weedle::DictionaryDefinition<'src> { }) } } + + fn append_ns( + &'src self, + program: &mut backend::ast::Program, + name: &'src str, + ns: &'src first_pass::NamespaceData<'src>, + ) { + let mut module = backend::ast::Module { + vis: public(), + name: rust_ident(name.to_snake_case().as_str()), + imports: Default::default(), + }; + + for member in ns.members.iter() { + self.append_ns_member(&mut module, name, member); + } + + program.modules.push(module); + } + + fn append_ns_member( + &self, + module: &mut backend::ast::Module, + self_name: &'src str, + member: &'src weedle::namespace::OperationNamespaceMember<'src>, + ) { + for import_function in self.create_namespace_operation( + &member.args.body.list, + member.identifier.as_ref().map(|id| id.0), + &member.return_type, + self_name, + util::throws(&member.attributes) + ) { + module.imports.push( + backend::ast::Import { + module: None, + js_namespace: Some(raw_ident(self_name)), + kind: backend::ast::ImportKind::Function(import_function), + } + ); + } + } + + fn append_const( + &self, + program: &mut backend::ast::Program, + self_name: &'src str, + member: &'src weedle::interface::ConstMember<'src>, + ) { + let idl_type = match member.const_type.to_idl_type(self) { + Some(idl_type) => idl_type, + None => return, + }; + + let ty = match idl_type.to_syn_type(TypePosition::Return) { + Some(ty) => ty, + None => { + warn!( + "Cannot convert const type to syn type: {:?} in {:?} on {:?}", + idl_type, + member, + self_name + ); + return + }, + }; + + program.consts.push(backend::ast::Const { + vis: public(), + name: rust_ident(member.identifier.0.to_shouty_snake_case().as_str()), + class: Some(rust_ident(camel_case_ident(&self_name).as_str())), + ty, + value: webidl_const_v_to_backend_const_v(&member.const_value), + }); + } + + fn append_interface( + &self, + program: &mut backend::ast::Program, + name: &'src str, + data: &first_pass::InterfaceData, + ) { + let doc_comment = Some(format!( + "The `{}` object\n\n{}", + name, + mdn_doc(name, None), + )); + + program.imports.push(backend::ast::Import { + module: None, + js_namespace: None, + kind: backend::ast::ImportKind::Type(backend::ast::ImportType { + vis: public(), + rust_name: rust_ident(camel_case_ident(name).as_str()), + js_name: name.to_string(), + attrs: Vec::new(), + doc_comment, + instanceof_shim: format!("__widl_instanceof_{}", name), + extends: self.all_superclasses(name) + .map(|name| Ident::new(&name, Span::call_site())) + .collect(), + }), + }); + + for (ctor_name, args) in data.constructors.iter() { + self.append_constructor(program, name, ctor_name, args); + } + for member in data.methods.iter() { + self.member_operation( + program, + name, + &member.attributes, + member.modifier, + &member.specials, + &member.return_type, + &member.args.body.list, + &member.identifier, + ) + } + for member in data.consts.iter() { + self.append_const(program, name, member); + } + for member in data.attributes.iter() { + self.member_attribute( + program, + name, + &member.attributes, + member.modifier, + member.readonly.is_some(), + &member.type_, + member.identifier.0, + ); + } + + self.append_interface_mixins(program, name, name); + } + + fn append_interface_mixins( + &self, + program: &mut backend::ast::Program, + self_name: &str, + mixin_name: &str, + ) { + if let Some(mixin_data) = self.mixins.get(mixin_name) { + for member in &mixin_data.methods { + self.member_operation( + program, + self_name, + &member.attributes, + None, + &[], + &member.return_type, + &member.args.body.list, + &member.identifier, + ); + } + for member in &mixin_data.consts { + self.append_const(program, self_name, member); + } + for member in &mixin_data.attributes { + self.member_attribute( + program, + self_name, + &member.attributes, + if let Some(s) = member.stringifier { + Some(weedle::interface::StringifierOrInheritOrStatic::Stringifier(s)) + } else { + None + }, + member.readonly.is_some(), + &member.type_, + member.identifier.0, + ); + } + } + if let Some(mixin_names) = self.includes.get(mixin_name) { + for mixin_name in mixin_names { + self.append_interface_mixins(program, self_name, mixin_name); + } + } + } + + fn append_constructor( + &self, + program: &mut backend::ast::Program, + iface_name: &'src str, + ctor_name: &'src str, + args: &[Argument<'src>], + ) { + let (overloaded, same_argument_names) = self.get_operation_overloading( + args, + &::first_pass::OperationId::Constructor, + iface_name, + false, + ); + + let self_ty = ident_ty(rust_ident(camel_case_ident(iface_name).as_str())); + + let kind = backend::ast::ImportFunctionKind::Method { + class: ctor_name.to_string(), + ty: self_ty.clone(), + kind: backend::ast::MethodKind::Constructor, + }; + + let structural = false; + + // Constructors aren't annotated with `[Throws]` extended attributes + // (how could they be, since they themselves are extended + // attributes?) so we must conservatively assume that they can + // always throw. + // + // From https://heycam.github.io/webidl/#Constructor (emphasis + // mine): + // + // > The prose definition of a constructor must either return an IDL + // > value of a type corresponding to the interface the + // > `[Constructor]` extended attribute appears on, **or throw an + // > exception**. + let throws = true; + + for import_function in self.create_function( + "new", + overloaded, + same_argument_names, + &match self.convert_arguments(args) { + Some(arguments) => arguments, + None => return, + }, + IdlType::Interface(iface_name), + kind, + structural, + throws, + None, + ) { + program.imports.push(wrap_import_function(import_function)); + } + } + + fn member_attribute( + &self, + program: &mut backend::ast::Program, + self_name: &'src str, + attrs: &'src Option, + modifier: Option, + readonly: bool, + type_: &'src weedle::types::AttributedType<'src>, + identifier: &'src str, + ) { + use weedle::interface::StringifierOrInheritOrStatic::*; + + let is_static = match modifier { + Some(Stringifier(_)) => unimplemented!(), // filtered out earlier + Some(Inherit(_)) => false, + Some(Static(_)) => true, + None => false, + }; + + let is_structural = util::is_structural(attrs); + let throws = util::throws(attrs); + let global = self + .interfaces + .get(self_name) + .map(|interface_data| interface_data.global) + .unwrap_or(false); + + for import_function in self.create_getter( + identifier, + &type_.type_, + self_name, + is_static, + is_structural, + throws, + global, + ) { + program.imports.push(wrap_import_function(import_function)); + } + + if !readonly { + for import_function in self.create_setter( + identifier, + type_.type_.clone(), + self_name, + is_static, + is_structural, + throws, + global, + ) { + program.imports.push(wrap_import_function(import_function)); + } + } + } + + fn member_operation( + &self, + program: &mut backend::ast::Program, + 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>, + ) { + use weedle::interface::StringifierOrStatic::*; + use weedle::interface::Special; + + let is_static = match modifier { + Some(Stringifier(_)) => uniimplemented!(), // filtered out earlier + Some(Static(_)) => true, + None => false, + }; + + let mut operation_ids = vec![ + OperationId::Operation(identifier.map(|s| s.0)), + ]; + if specials.len() == 1 { + let id = match specials[0] { + Special::Getter(weedle::term::Getter) => OperationId::IndexingGetter, + Special::Setter(weedle::term::Setter) => OperationId::IndexingSetter, + Special::Deleter(weedle::term::Deleter) => OperationId::IndexingDeleter, + Special::LegacyCaller(weedle::term::LegacyCaller) => { + warn!("Unsupported legacy caller: {:?}", (self_name, identifier)); + return + }, + }; + operation_ids.push(id); + } + + let global = self + .interfaces + .get(self_name) + .map(|interface_data| interface_data.global) + .unwrap_or(false); + + for id in operation_ids { + let methods = self + .create_basic_method( + args, + id, + return_type, + self_name, + is_static, + match id { + OperationId::IndexingGetter | + OperationId::IndexingSetter | + OperationId::IndexingDeleter => true, + _ => false, + }, + util::throws(attrs), + global, + ); + + for method in methods { + program.imports.push(wrap_import_function(method)); + } + } + } } diff --git a/examples/julia_set/Cargo.toml b/examples/julia_set/Cargo.toml index 509b6cc8..f6b0dbdb 100644 --- a/examples/julia_set/Cargo.toml +++ b/examples/julia_set/Cargo.toml @@ -8,3 +8,4 @@ crate-type = ["cdylib"] [dependencies] wasm-bindgen = { path = "../.." } +web-sys = { path = "../../crates/web-sys" } diff --git a/examples/julia_set/src/lib.rs b/examples/julia_set/src/lib.rs index a92eea48..2d9e6ac1 100644 --- a/examples/julia_set/src/lib.rs +++ b/examples/julia_set/src/lib.rs @@ -1,18 +1,13 @@ extern crate wasm_bindgen; +extern crate web_sys; + use wasm_bindgen::prelude::*; +use web_sys::{CanvasRenderingContext2D, ImageData}; mod fractal; use fractal::get_julia_set; use fractal::complex::Complex; -#[wasm_bindgen] -extern "C" { - pub type ImageData; - - #[wasm_bindgen(constructor)] - pub fn new(arr: &Uint8ClampedArray, width: u32, height: u32) -> ImageData; -} - #[wasm_bindgen] extern "C" { pub type Uint8ClampedArray; @@ -21,19 +16,13 @@ extern "C" { pub fn new(arr: &[u8]) -> Uint8ClampedArray; } -#[wasm_bindgen] -extern "C" { - pub type CanvasRenderingContext2D; - - #[wasm_bindgen(method, js_name = putImageData)] - pub fn put_image_data(this: &CanvasRenderingContext2D, image_data: &ImageData, p_1: i32, p_2: i32); -} - #[wasm_bindgen] pub fn draw(ctx: &CanvasRenderingContext2D, width: u32, height: u32, real: f64, imaginary: f64) { let c = Complex { real, imaginary }; let data = get_julia_set(width, height, c); let uint8_array = Uint8ClampedArray::new(&data); - ctx.put_image_data(&ImageData::new(&uint8_array, width, height), 0, 0); + ctx.put_image_data( + &ImageData::with_data_and_sw_and_sh_with_sh(&data, width, height).unwrap(), + 0.0, 0.0); }