From 1e02ca7eab7a26ce15d578c3ae7f28b291790c60 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Thu, 9 Aug 2018 18:07:41 +0100 Subject: [PATCH 01/13] Add support for modules to the backend. --- crates/backend/src/ast.rs | 15 +++++++++++++++ crates/backend/src/codegen.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 57869a891..08c64975a 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -19,6 +19,19 @@ pub struct Program { pub structs: Vec, /// rust consts pub consts: Vec, + /// rust submodules + pub modules: Vec, +} + +/// A rust module +/// +/// This exists to give the ability to namespace js imports. +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub struct Module { + /// module name + pub name: String, + /// js -> rust interfaces + pub imports: Vec, } /// A rust to js interface. Allows interaction with rust objects/functions @@ -227,6 +240,8 @@ impl Program { structs: self.structs.iter().map(|a| a.shared()).collect(), enums: self.enums.iter().map(|a| a.shared()).collect(), imports: self.imports.iter() + // add in imports from inside modules + .chain(self.modules.iter().flat_map(|m| m.imports.iter())) .map(|a| a.shared()) .collect::>()?, version: shared::version(), diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index a40cbf3a6..5274a0ceb 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -43,6 +43,8 @@ impl TryToTokens for ast::Program { for i in self.imports.iter() { DescribeImport(&i.kind).to_tokens(tokens); + // If there is a js namespace, check that name isn't a type. If it is, + // this import might be a method on that type. if let Some(ns) = &i.js_namespace { if types.contains(ns) && i.kind.fits_on_impl() { let kind = match i.kind.try_to_token_stream() { @@ -61,6 +63,11 @@ impl TryToTokens for ast::Program { errors.push(e); } } + for m in self.modules.iter() { + if let Err(e) = m.try_to_tokens(tokens) { + errors.push(e); + } + } for e in self.enums.iter() { e.to_tokens(tokens); } @@ -87,6 +94,7 @@ impl TryToTokens for ast::Program { // Each JSON blob is prepended with the length of the JSON blob so when // all these sections are concatenated in the final wasm file we know // how to extract all the JSON pieces, so insert the byte length here. + // The value is little-endian. let generated_static_length = description.len() + 4; let mut bytes = vec![ (description.len() >> 0) as u8, @@ -1103,6 +1111,29 @@ impl ToTokens for ast::Const { } } +impl TryToTokens for ast::Module { + fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { + let mut errors = Vec::new(); + for i in self.imports.iter() { + DescribeImport(&i.kind).to_tokens(tokens); + } + let name = &self.name; + let mut body = TokenStream::new(); + for i in self.imports.iter() { + if let Err(e) = i.kind.try_to_tokens(&mut body) { + errors.push(e); + } + } + Diagnostic::from_vec(errors)?; + (quote!{ + pub mod #name { + #body + } + }).to_tokens(tokens); + Ok(()) + } +} + /// Emits the necessary glue tokens for "descriptor", generating an appropriate /// symbol name as well as attributes around the descriptor function itself. struct Descriptor<'a, T>(&'a Ident, T); From 615f8fbc4d9c00af681fdeda13f51ecde7b64fd5 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Thu, 9 Aug 2018 21:38:37 +0100 Subject: [PATCH 02/13] Push updates - still WIP --- crates/backend/src/ast.rs | 25 ++++++++++--------- crates/backend/src/codegen.rs | 23 ++++++++++++++---- crates/web-sys/build.rs | 13 +++++----- crates/webidl/src/lib.rs | 45 +++++++++++++++++++++++++++-------- 4 files changed, 71 insertions(+), 35 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 08c64975a..719f385fb 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use proc_macro2::{Ident, Span}; use shared; use syn; @@ -20,18 +21,7 @@ pub struct Program { /// rust consts pub consts: Vec, /// rust submodules - pub modules: Vec, -} - -/// A rust module -/// -/// This exists to give the ability to namespace js imports. -#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] -pub struct Module { - /// module name - pub name: String, - /// js -> rust interfaces - pub imports: Vec, + pub modules: HashMap, } /// A rust to js interface. Allows interaction with rust objects/functions @@ -233,6 +223,15 @@ pub enum ConstValue { Null, } +/// A rust module +/// +/// This exists to give the ability to namespace js imports. +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub struct Module { + /// js -> rust interfaces + pub imports: Vec, +} + impl Program { pub(crate) fn shared(&self) -> Result { Ok(shared::Program { @@ -241,7 +240,7 @@ impl Program { enums: self.enums.iter().map(|a| a.shared()).collect(), imports: self.imports.iter() // add in imports from inside modules - .chain(self.modules.iter().flat_map(|m| m.imports.iter())) + .chain(self.modules.values().flat_map(|m| m.imports.iter())) .map(|a| a.shared()) .collect::>()?, version: shared::version(), diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 5274a0ceb..1908441de 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -64,7 +64,7 @@ impl TryToTokens for ast::Program { } } for m in self.modules.iter() { - if let Err(e) = m.try_to_tokens(tokens) { + if let Err(e) = ModuleInIter::from(m).try_to_tokens(tokens) { errors.push(e); } } @@ -1111,15 +1111,28 @@ impl ToTokens for ast::Const { } } -impl TryToTokens for ast::Module { +/// Struct to help implementing TryToTokens over the key/value pairs from the hashmap. +struct ModuleInIter<'a> { + name: &'a Ident, + module: &'a ast::Module +} + +impl<'a> From<(&'a Ident, &'a ast::Module)> for ModuleInIter<'a> { + fn from((name, module): (&'a Ident, &'a ast::Module)) -> ModuleInIter<'a> { + ModuleInIter { name, module } + } +} + +impl<'a> TryToTokens for ModuleInIter<'a> { fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { + let name = &self.name; + let imports = &self.module.imports; let mut errors = Vec::new(); - for i in self.imports.iter() { + for i in imports.iter() { DescribeImport(&i.kind).to_tokens(tokens); } - let name = &self.name; let mut body = TokenStream::new(); - for i in self.imports.iter() { + for i in imports.iter() { if let Err(e) = i.kind.try_to_tokens(&mut body) { errors.push(e); } diff --git a/crates/web-sys/build.rs b/crates/web-sys/build.rs index 8dbb232c8..630975c96 100644 --- a/crates/web-sys/build.rs +++ b/crates/web-sys/build.rs @@ -13,6 +13,8 @@ use std::path; use std::process::{self, Command}; fn main() { + env_logger::init(); + if let Err(e) = try_main() { eprintln!("Error: {}", e); for c in e.iter_causes() { @@ -24,11 +26,9 @@ fn main() { fn try_main() -> Result<(), failure::Error> { println!("cargo:rerun-if-changed=build.rs"); - env_logger::init(); - println!("cargo:rerun-if-changed=webidls/enabled"); - let entries = fs::read_dir("webidls/enabled").context("reading webidls/enabled directory")?; + let entries = fs::read_dir("webidls/enabled").context("reading webidls/enabled directory")?; let mut source = SourceFile::default(); for entry in entries { let entry = entry.context("getting webidls/enabled/*.webidl entry")?; @@ -38,8 +38,7 @@ fn try_main() -> Result<(), failure::Error> { } println!("cargo:rerun-if-changed={}", path.display()); source = source.add_file(&path) - .with_context(|_| format!("reading contents of file \"{}\"", - path.display()))?; + .with_context(|_| format!("reading contents of file \"{}\"", path.display()))?; } let bindings = match wasm_bindgen_webidl::compile(&source.contents) { @@ -70,9 +69,9 @@ fn try_main() -> Result<(), failure::Error> { let status = Command::new("rustfmt") .arg(&out_file_path) .status() - .context("running rustfmt")?; + .context("running rustfmt")?; if !status.success() { - bail!("rustfmt failed: {}", status) + bail!("rustfmt failed: {}", status) } } diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 0fb33c90e..76d2cad4b 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -185,13 +185,18 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> { weedle::Definition::Implements(..) => { // nothing to do for this, ignore it } + weedle::Definition::Namespace(namespace) => { + namespace.webidl_parse(program, first_pass, ())? + } + weedle::Definition::PartialNamespace(namespace) => { + // TODO + warn!("Unsupported WebIDL definition: {:?}", self) + } // TODO weedle::Definition::Callback(..) | weedle::Definition::CallbackInterface(..) | weedle::Definition::Dictionary(..) - | weedle::Definition::PartialDictionary(..) - | weedle::Definition::Namespace(..) - | weedle::Definition::PartialNamespace(..) => { + | weedle::Definition::PartialDictionary(..) => { warn!("Unsupported WebIDL definition: {:?}", self) } } @@ -657,12 +662,17 @@ fn member_operation<'src>( 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(()), + 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())), + Some(ref name) if specials.is_empty() => + ::first_pass::OperationId::Operation(Some(name.clone())), _ => { warn!("Unsupported specials on type {:?}", (self_name, identifier)); return Ok(()) @@ -744,8 +754,8 @@ impl<'src> WebidlParse<'src, ()> for weedle::EnumDefinition<'src> { variants: variants .iter() .map(|v| { - if !v.0.is_empty() { - rust_ident(camel_case_ident(&v.0).as_str()) + if !v.0.is_empty() { + rust_ident(camel_case_ident(&v.0).as_str()) } else { rust_ident("None") } @@ -783,3 +793,18 @@ impl<'src> WebidlParse<'src, &'src str> for weedle::interface::ConstMember<'src> Ok(()) } } + +impl<'src> WebidlParse<'src, ()> for weedle::interface::NamespaceDefinition<'src> { + fn webidl_parse( + &'src self, + program: &mut backend::ast::Program, + record: &FirstPassRecord<'src>, + (): (), + ) -> Result<()> { + if util::is_chrome_only(&self.attributes) { + return Ok(()); + } + + + } +} From 56b0f64d0baba03d062f47d1ef85bad57fa31825 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Fri, 10 Aug 2018 10:29:16 +0100 Subject: [PATCH 03/13] Fix warning in doc gen --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2f6a2be06..a42901558 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -790,7 +790,7 @@ pub mod __rt { /// above. That means if this function is called and referenced we'll pull /// in the object file and link the intrinsics. /// - /// Note that this is an #[inline] function to remove the function call + /// Note that this is an `#[inline]` function to remove the function call /// overhead we inject in functions, but right now it's unclear how to do /// this in a zero-cost fashion. The lowest cost seems to be generating a /// store that can't be optimized away (to a global), which is listed below. From 6c1f32fa5b98b84d77c6105e910ef24cd7004c80 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Fri, 10 Aug 2018 17:06:11 +0100 Subject: [PATCH 04/13] Saving commit --- crates/backend/src/ast.rs | 1 + crates/webidl/src/first_pass.rs | 54 ++++++++++-- crates/webidl/src/lib.rs | 150 +++++++++++++++++++++++++++++++- crates/webidl/src/util.rs | 134 +++++++++++++++++++++++----- 4 files changed, 307 insertions(+), 32 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 719f385fb..0a495de5d 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 4e58443a9..004dc31a4 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 76d2cad4b..7fcf04608 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 554f90f0b..3320c38b1 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, From 0d897e9b8d0112104466d0aa66e0d60e96963fa0 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Fri, 10 Aug 2018 19:00:56 +0100 Subject: [PATCH 05/13] Unsure about error --- crates/backend/src/ast.rs | 6 +- crates/backend/src/codegen.rs | 3 +- crates/webidl/src/first_pass.rs | 107 ++++++++++++++++--- crates/webidl/src/lib.rs | 177 ++++++++++++-------------------- crates/webidl/src/util.rs | 37 +++---- 5 files changed, 178 insertions(+), 152 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 0a495de5d..a95fbe619 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::BTreeMap; use proc_macro2::{Ident, Span}; use shared; use syn; @@ -21,7 +21,7 @@ pub struct Program { /// rust consts pub consts: Vec, /// rust submodules - pub modules: HashMap, + pub modules: BTreeMap, } /// A rust to js interface. Allows interaction with rust objects/functions @@ -227,8 +227,8 @@ 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 { + pub vis: syn::Visibility, /// js -> rust interfaces pub imports: Vec, } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 1908441de..ac8f1096e 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -1131,6 +1131,7 @@ impl<'a> TryToTokens for ModuleInIter<'a> { for i in imports.iter() { DescribeImport(&i.kind).to_tokens(tokens); } + let vis = &self.module.vis; let mut body = TokenStream::new(); for i in imports.iter() { if let Err(e) = i.kind.try_to_tokens(&mut body) { @@ -1139,7 +1140,7 @@ impl<'a> TryToTokens for ModuleInIter<'a> { } Diagnostic::from_vec(errors)?; (quote!{ - pub mod #name { + #vis mod #name { #body } }).to_tokens(tokens); diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 004dc31a4..09ffafbc8 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -41,17 +41,6 @@ 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> { @@ -60,9 +49,10 @@ pub(crate) struct NamespaceData<'src> { pub(crate) operations: BTreeMap, OperationData<'src>>, } -impl<'src> Default for NamespaceData<'src> { - fn default() -> Self { - NamespaceData { +impl<'src> NamespaceData<'src> { + /// Same as `Default::default` but sets `partial` to true. + pub(crate) fn default_for_partial() -> Self { + Self { partial: true, operations: Default::default(), } @@ -178,7 +168,7 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { let interface = record .interfaces .entry(self.identifier.0) - .or_insert_with(Default::default); + .or_default(); interface.partial = false; interface.superclass = self.inheritance.map(|s| s.identifier.0); } @@ -348,6 +338,93 @@ impl<'src> FirstPass<'src, ()> for weedle::TypedefDefinition<'src> { } } +impl<'src> FirstPass<'src, ()> for weedle::NamespaceDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { + { + let namespace = record + .namespaces + .entry(self.identifier.0) + .or_default(); + namespace.partial = false; + } + + if util::is_chrome_only(&self.attributes) { + return Ok(()) + } + + // We ignore all attributes. + + for member in &self.members.body { + member.first_pass(record, self.identifier.0)?; + } + + Ok(()) + } +} + +impl<'src> FirstPass<'src, ()> for weedle::PartialNamespaceDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { + record + .namespaces + .entry(self.identifier.0) + .or_insert_with(NamespaceData::default_for_partial); + + if util::is_chrome_only(&self.attributes) { + return Ok(()) + } + + for member in &self.members.body { + member.first_pass(record, self.identifier.0)?; + } + + Ok(()) + } +} + +impl<'src> FirstPass<'src, &'src str> for weedle::namespace::NamespaceMember<'src> { + fn first_pass(&'src self, + record: &mut FirstPassRecord<'src>, + namespace_name: &'src str) -> Result<()> + { + match self { + weedle::namespace::NamespaceMember::Operation(op) => { + op.first_pass(record, namespace_name) + } + _ => Ok(()), + } + } +} + +impl<'src> FirstPass<'src, &'src str> for weedle::namespace::OperationNamespaceMember<'src> { + fn first_pass(&'src self, + record: &mut FirstPassRecord<'src>, + namespace_name: &'src str) -> Result<()> + { + let identifier = self.identifier.map(|s| s.0); + let arguments = &self.args.body.list; + let mut names = Vec::with_capacity(arguments.len()); + for argument in arguments { + match argument { + Argument::Single(arg) => names.push(arg.identifier.0), + Argument::Variadic(_) => return Ok(()), + } + } + record + .namespaces + .get_mut(namespace_name) + .unwrap() // call this after creating the namespace + .operations + .entry(identifier) + .and_modify(|operation_data| operation_data.overloaded = true) + .or_insert_with(Default::default) + .argument_names_same + .entry(names) + .and_modify(|same_argument_names| *same_argument_names = true) + .or_insert(false); + Ok(()) + } +} + impl<'a> FirstPassRecord<'a> { pub fn all_superclasses<'me>(&'me self, interface: &str) -> impl Iterator + 'me diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 7fcf04608..20bc0d443 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -774,10 +774,10 @@ impl<'src> WebidlParse<'src, &'src str> for weedle::interface::ConstMember<'src> fn webidl_parse( &'src self, program: &mut backend::ast::Program, - record: &FirstPassRecord<'src>, + first_pass: &FirstPassRecord<'src>, self_name: &'src str, ) -> Result<()> { - let ty = match self.const_type.to_syn_type(record, TypePosition::Return) { + let ty = match self.const_type.to_syn_type(first_pass, TypePosition::Return) { Some(s) => s, None => return Ok(()), }; @@ -798,7 +798,7 @@ impl<'src> WebidlParse<'src, ()> for weedle::NamespaceDefinition<'src> { fn webidl_parse( &'src self, program: &mut backend::ast::Program, - record: &FirstPassRecord<'src>, + first_pass: &FirstPassRecord<'src>, (): (), ) -> Result<()> { if util::is_chrome_only(&self.attributes) { @@ -806,29 +806,59 @@ impl<'src> WebidlParse<'src, ()> for weedle::NamespaceDefinition<'src> { } 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(); + .or_insert_with(|| backend::ast::Module { + vis: public(), + imports: Default::default() + }); - for member in self.members.body.iter() { - member.webidl_parse(program, record, js_name)? + if let Some(attrs) = &self.attributes { + for attr in &attrs.body.list { + attr.webidl_parse(program, first_pass, self)?; + } } + + let namespace_names = NamespaceNames { + rust_name: &rust_name, + js_name: &self.identifier.0, + }; + for member in &self.members.body { + member.webidl_parse(program, first_pass, namespace_names)?; + } + Ok(()) } } -impl<'src> WebidlParse<'src, &'src str> for weedle::namespace::NamespaceMember<'src> { +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(()) + } +} +#[derive(Copy, Clone)] +struct NamespaceNames<'a> { + rust_name: &'a Ident, + js_name: &'a str, +} + +impl<'src> WebidlParse<'src, NamespaceNames<'src>> for weedle::namespace::NamespaceMember<'src> { fn webidl_parse( &'src self, program: &mut backend::ast::Program, - record: &FirstPassRecord<'src>, - module_name: &'src str + first_pass: &FirstPassRecord<'src>, + ns_names: NamespaceNames<'src> ) -> Result<()> { match self { weedle::namespace::NamespaceMember::Operation(op) => { - op.webidl_parse(program, record, module_name)?; + op.webidl_parse(program, first_pass, ns_names)?; } weedle::namespace::NamespaceMember::Attribute(_) => { warn!("Attribute namespace members are not supported") @@ -838,117 +868,42 @@ impl<'src> WebidlParse<'src, &'src str> for weedle::namespace::NamespaceMember<' } } -impl<'src> WebidlParse<'src, &'src str> for weedle::namespace::OperationNamespaceMember<'src> { +impl<'src> WebidlParse<'src, NamespaceNames<'src>> for weedle::namespace::OperationNamespaceMember<'src> { fn webidl_parse( &'src self, program: &mut backend::ast::Program, - record: &FirstPassRecord<'src>, - module_name: &'src str + first_pass: &FirstPassRecord<'src>, + ns_names: NamespaceNames<'src> ) -> Result<()> { if util::is_chrome_only(&self.attributes) { return Ok(()); } - let structural = util::is_structural(&self.attributes); + let imported_fn = match first_pass.create_namespace_operation( + &self.args.body.list, + self.identifier.as_ref().map(|id| id.0), + &self.return_type, + ns_names.js_name, + util::throws(&self.attributes) + ) { + Some(f) => f, + None => { return Ok(()) } + }; - let name = match &self.identifier { - Some(ident) => ident.0, - None => { - warn!("Operations without identifiers are not supported"); - return Ok(()); - } + let import = backend::ast::Import { + module: None, + js_namespace: Some(ns_names.js_name.clone()), + kind: backend::ast::ImportKind::Function(imported_fn), + }; + + program + .modules + .get_mut(ns_names.rust_name) + .unwrap() + .imports + .push(import); - 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 3320c38b1..f654937b6 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -1029,33 +1029,25 @@ impl<'src> FirstPassRecord<'src> { pub fn create_namespace_operation( &self, arguments: &[weedle::argument::Argument], - operation_name: &str, + operation_name: Option<&str>, return_type: &weedle::types::ReturnType, - structural: bool, + namespace_name: &str, catch: bool, ) -> Option { let (overloaded, same_argument_names) = self.get_namespaced_operation_overloading( arguments, - &first_pass::OperationId::Operation(Some(operation_name)) - self_name, + operation_name, + namespace_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"); + let name = match operation_name { + Some(name) => name.to_string(), + None => { + warn!("Operations without a name are unsupported"); return None; } }; - let kind = backend::ast::ImportFunctionKind::Normal; - let ret = match return_type { weedle::types::ReturnType::Void(_) => None, weedle::types::ReturnType::Type(ty) => { @@ -1069,8 +1061,9 @@ impl<'src> FirstPassRecord<'src> { } }; let doc_comment = format!("The `{}.{}()` function\n\n{}", + namespace_name, name, - mdn_doc(self_name, Some(&name))); + mdn_doc(namespace_name, Some(&name))); // TODO check link self.create_function( &name, @@ -1078,10 +1071,10 @@ impl<'src> FirstPassRecord<'src> { same_argument_names, arguments, ret, - kind, - structural, + backend::ast::ImportFunctionKind::Normal, + false, catch, - doc_comment, + Some(doc_comment), ) } @@ -1090,14 +1083,14 @@ impl<'src> FirstPassRecord<'src> { pub fn get_namespaced_operation_overloading( &self, arguments: &[weedle::argument::Argument], - id: &first_pass::OperationId, + operation_name: Option<&str>, 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) { + let data = match data.operations.get(&operation_name) { Some(data) => data, None => return (false, false), }; From a23fa03ad06a06e9ab00627a581dd19fdb03a327 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sat, 11 Aug 2018 12:38:58 +0100 Subject: [PATCH 06/13] Closer to finished - Tried `cargo doc` and seen methods generated. - Added test with a few method calls to the console operations. --- crates/web-sys/tests/wasm/console.rs | 9 +++++++++ crates/web-sys/tests/wasm/main.rs | 1 + crates/webidl/src/lib.rs | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 crates/web-sys/tests/wasm/console.rs diff --git a/crates/web-sys/tests/wasm/console.rs b/crates/web-sys/tests/wasm/console.rs new file mode 100644 index 000000000..61ce5c294 --- /dev/null +++ b/crates/web-sys/tests/wasm/console.rs @@ -0,0 +1,9 @@ +use wasm_bindgen_test::*; +use wasm_bindgen::prelude::*; +use web_sys::console; + +#[wasm_bindgen_test] +fn test_console() { + console::time("test label"); + console::time_end("test label"); +} diff --git a/crates/web-sys/tests/wasm/main.rs b/crates/web-sys/tests/wasm/main.rs index 279f9bdeb..69e4d6f08 100644 --- a/crates/web-sys/tests/wasm/main.rs +++ b/crates/web-sys/tests/wasm/main.rs @@ -14,6 +14,7 @@ pub mod anchor_element; pub mod body_element; pub mod br_element; pub mod button_element; +pub mod console; pub mod div_element; pub mod element; pub mod event; diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 20bc0d443..f36285903 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -36,7 +36,7 @@ use std::path::Path; use backend::TryToTokens; use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports}; -use backend::util::{ident_ty, rust_ident, wrap_import_function}; +use backend::util::{ident_ty, rust_ident, raw_ident, wrap_import_function}; use failure::ResultExt; use heck::{ShoutySnakeCase, SnakeCase}; use proc_macro2::{Ident, Span}; @@ -892,7 +892,7 @@ impl<'src> WebidlParse<'src, NamespaceNames<'src>> for weedle::namespace::Operat let import = backend::ast::Import { module: None, - js_namespace: Some(ns_names.js_name.clone()), + js_namespace: Some(raw_ident(ns_names.js_name)), kind: backend::ast::ImportKind::Function(imported_fn), }; From df1342398dc4a09efaedb62cbfebf11ac19b5019 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sat, 11 Aug 2018 12:50:18 +0100 Subject: [PATCH 07/13] Add support for partial namespaces --- crates/webidl/src/lib.rs | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index f36285903..4d139c0c2 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -189,8 +189,7 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> { namespace.webidl_parse(program, first_pass, ())? } weedle::Definition::PartialNamespace(namespace) => { - // TODO - warn!("Unsupported WebIDL definition: {:?}", self) + namespace.webidl_parse(program, first_pass, ())? } // TODO weedle::Definition::Callback(..) @@ -832,6 +831,38 @@ impl<'src> WebidlParse<'src, ()> for weedle::NamespaceDefinition<'src> { } } +impl<'src> WebidlParse<'src, ()> for weedle::PartialNamespaceDefinition<'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 rust_name = rust_ident(self.identifier.0.to_snake_case().as_str()); + + if !first_pass.namespaces.contains_key(self.identifier.0) { + warn!( + "Partial namespace {} missing non-partial namespace", + self.identifier.0 + ); + } + + let namespace_names = NamespaceNames { + rust_name: &rust_name, + js_name: &self.identifier.0, + }; + for member in &self.members.body { + member.webidl_parse(program, first_pass, namespace_names)?; + } + + Ok(()) + } +} + impl<'src> WebidlParse<'src, &'src weedle::NamespaceDefinition<'src>> for ExtendedAttribute<'src> { fn webidl_parse( &'src self, From eaacdc8966c545d64073cdd2260f019d31a469c2 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sat, 11 Aug 2018 12:57:45 +0100 Subject: [PATCH 08/13] Mark that link is checked; --- crates/webidl/src/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index f654937b6..2754ea7ac 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -1063,7 +1063,7 @@ impl<'src> FirstPassRecord<'src> { let doc_comment = format!("The `{}.{}()` function\n\n{}", namespace_name, name, - mdn_doc(namespace_name, Some(&name))); // TODO check link + mdn_doc(namespace_name, Some(&name))); // checked link self.create_function( &name, From e66d4da8358b5a31b22bcd9ab6ac75624ebb27e1 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sun, 12 Aug 2018 12:11:09 +0100 Subject: [PATCH 09/13] Fix some of @ohanar issues --- crates/webidl/src/first_pass.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 09ffafbc8..46263fd7d 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -42,7 +42,6 @@ pub(crate) struct InterfaceData<'src> { } /// 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, @@ -50,8 +49,15 @@ pub(crate) struct NamespaceData<'src> { } impl<'src> NamespaceData<'src> { - /// Same as `Default::default` but sets `partial` to true. - pub(crate) fn default_for_partial() -> Self { + /// Creates an empty node for a non-partial namespace. + pub(crate) fn empty_non_partial() -> Self { + Self { + partial: false, + operations: Default::default(), + } + } + /// Creates an empty node for a partial namespace. + pub(crate) fn empty_partial() -> Self { Self { partial: true, operations: Default::default(), @@ -153,7 +159,7 @@ fn first_pass_interface_operation<'src>( .operations .entry(id) .and_modify(|operation_data| operation_data.overloaded = true) - .or_insert_with(Default::default) + .or_default() .argument_names_same .entry(names) .and_modify(|same_argument_names| *same_argument_names = true) @@ -340,13 +346,11 @@ impl<'src> FirstPass<'src, ()> for weedle::TypedefDefinition<'src> { impl<'src> FirstPass<'src, ()> for weedle::NamespaceDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { - { - let namespace = record - .namespaces - .entry(self.identifier.0) - .or_default(); - namespace.partial = false; - } + record + .namespaces + .entry(self.identifier.0) + .and_modify(|entry| entry.partial = false) + .or_insert_with(Namespace::empty_non_partial); if util::is_chrome_only(&self.attributes) { return Ok(()) @@ -367,7 +371,7 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialNamespaceDefinition<'src> { record .namespaces .entry(self.identifier.0) - .or_insert_with(NamespaceData::default_for_partial); + .or_insert_with(NamespaceData::empty_partial); if util::is_chrome_only(&self.attributes) { return Ok(()) From 833099dd0dd17554a14ac05616ab4cc1a126e5e5 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sun, 12 Aug 2018 13:11:53 +0100 Subject: [PATCH 10/13] Fix error --- crates/webidl/src/first_pass.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 46263fd7d..f6c109feb 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -350,7 +350,7 @@ impl<'src> FirstPass<'src, ()> for weedle::NamespaceDefinition<'src> { .namespaces .entry(self.identifier.0) .and_modify(|entry| entry.partial = false) - .or_insert_with(Namespace::empty_non_partial); + .or_insert_with(NamespaceData::empty_non_partial); if util::is_chrome_only(&self.attributes) { return Ok(()) From 23009dbc1e0eff25b644f749e4884dc292f943d3 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sun, 12 Aug 2018 21:11:02 +0100 Subject: [PATCH 11/13] Add simple test for namespaces. --- crates/webidl-tests/main.rs | 1 + crates/webidl-tests/namespace.js | 11 +++++++++++ crates/webidl-tests/namespace.rs | 10 ++++++++++ crates/webidl-tests/namespace.webidl | 4 ++++ 4 files changed, 26 insertions(+) create mode 100644 crates/webidl-tests/namespace.js create mode 100644 crates/webidl-tests/namespace.rs create mode 100644 crates/webidl-tests/namespace.webidl diff --git a/crates/webidl-tests/main.rs b/crates/webidl-tests/main.rs index 2a31140bd..12635f50c 100644 --- a/crates/webidl-tests/main.rs +++ b/crates/webidl-tests/main.rs @@ -10,3 +10,4 @@ pub mod consts; pub mod enums; pub mod simple; pub mod throws; +pub mod namespace; diff --git a/crates/webidl-tests/namespace.js b/crates/webidl-tests/namespace.js new file mode 100644 index 000000000..a3b83d1f5 --- /dev/null +++ b/crates/webidl-tests/namespace.js @@ -0,0 +1,11 @@ +const strictEqual = require('assert').strictEqual; + +global.math = class { + powf(base, exp) { + return Math.pow(base, exp); + } + + add_one(val) { + return val + 1; + } +}; diff --git a/crates/webidl-tests/namespace.rs b/crates/webidl-tests/namespace.rs new file mode 100644 index 000000000..848239aa4 --- /dev/null +++ b/crates/webidl-tests/namespace.rs @@ -0,0 +1,10 @@ +use wasm_bindgen_test::*; + +include!(concat!(env!("OUT_DIR"), "/namespace.rs")); + +#[wasm_bindgen_test] +fn simple_namespace_test() { + assert_eq!(math::add_one(1), 2); + assert_eq!(math::powf(1.0, 100.0), 1.0); + assert_eq!(math::powf(10.0, 2.0), 100.0); +} diff --git a/crates/webidl-tests/namespace.webidl b/crates/webidl-tests/namespace.webidl new file mode 100644 index 000000000..c42a4cf60 --- /dev/null +++ b/crates/webidl-tests/namespace.webidl @@ -0,0 +1,4 @@ +namespace math { + long add_one(long val); + double powf(double base, double exponent); +}; From 4f0ddd25ce745f426fd04b23e291eb5aa98aece4 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sun, 12 Aug 2018 21:27:27 +0100 Subject: [PATCH 12/13] Fix tests --- crates/webidl-tests/namespace.js | 16 ++++++++-------- crates/webidl-tests/namespace.rs | 6 +++--- crates/webidl-tests/namespace.webidl | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/webidl-tests/namespace.js b/crates/webidl-tests/namespace.js index a3b83d1f5..7c86dcd57 100644 --- a/crates/webidl-tests/namespace.js +++ b/crates/webidl-tests/namespace.js @@ -1,11 +1,11 @@ const strictEqual = require('assert').strictEqual; -global.math = class { - powf(base, exp) { - return Math.pow(base, exp); - } +global.mathtest = {}; - add_one(val) { - return val + 1; - } -}; +global.mathtest.powf = function powf(base, exp) { + return Math.pow(base, exp); +} + +global.mathtest.add_one = function add_one(val) { + return val + 1; +} diff --git a/crates/webidl-tests/namespace.rs b/crates/webidl-tests/namespace.rs index 848239aa4..49afc6239 100644 --- a/crates/webidl-tests/namespace.rs +++ b/crates/webidl-tests/namespace.rs @@ -4,7 +4,7 @@ include!(concat!(env!("OUT_DIR"), "/namespace.rs")); #[wasm_bindgen_test] fn simple_namespace_test() { - assert_eq!(math::add_one(1), 2); - assert_eq!(math::powf(1.0, 100.0), 1.0); - assert_eq!(math::powf(10.0, 2.0), 100.0); + assert_eq!(mathtest::add_one(1), 2); + assert_eq!(mathtest::powf(1.0, 100.0), 1.0); + assert_eq!(mathtest::powf(10.0, 2.0), 100.0); } diff --git a/crates/webidl-tests/namespace.webidl b/crates/webidl-tests/namespace.webidl index c42a4cf60..1c3dbf3ad 100644 --- a/crates/webidl-tests/namespace.webidl +++ b/crates/webidl-tests/namespace.webidl @@ -1,4 +1,4 @@ -namespace math { +namespace mathtest { long add_one(long val); double powf(double base, double exponent); }; From ea05235985e90827a052546acdc95e8c7f22e2fd Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sun, 12 Aug 2018 21:28:59 +0100 Subject: [PATCH 13/13] Fix docs about testing webidl --- guide/src/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/testing.md b/guide/src/testing.md index 8f4c29746..fe3bca622 100644 --- a/guide/src/testing.md +++ b/guide/src/testing.md @@ -26,7 +26,7 @@ cargo test ## The Web IDL Frontend's Tests ``` -cargo test -p wasm-bindgen-webidl +cargo test -p webidl-tests --target wasm32-unknown-unknown ``` ## The Macro UI Tests