diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 634e6e4e..d8eb5761 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use proc_macro2::{Ident, Span}; use shared; use syn; @@ -19,6 +20,8 @@ pub struct Program { pub structs: Vec<Struct>, /// rust consts pub consts: Vec<Const>, + /// rust submodules + pub modules: BTreeMap<Ident, Module>, } /// A rust to js interface. Allows interaction with rust objects/functions @@ -220,6 +223,16 @@ 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 { + pub vis: syn::Visibility, + /// js -> rust interfaces + pub imports: Vec<Import>, +} + impl Program { pub(crate) fn shared(&self) -> Result<shared::Program, Diagnostic> { Ok(shared::Program { @@ -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.values().flat_map(|m| m.imports.iter())) .map(|a| a.shared()) .collect::<Result<_, Diagnostic>>()?, version: shared::version(), diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index a40cbf3a..ac8f1096 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) = ModuleInIter::from(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,43 @@ impl ToTokens for ast::Const { } } +/// 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 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) { + errors.push(e); + } + } + Diagnostic::from_vec(errors)?; + (quote!{ + #vis 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); diff --git a/crates/web-sys/build.rs b/crates/web-sys/build.rs index 8dbb232c..630975c9 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/web-sys/tests/wasm/console.rs b/crates/web-sys/tests/wasm/console.rs new file mode 100644 index 00000000..61ce5c29 --- /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 279f9bde..69e4d6f0 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-tests/main.rs b/crates/webidl-tests/main.rs index 2a31140b..12635f50 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 00000000..7c86dcd5 --- /dev/null +++ b/crates/webidl-tests/namespace.js @@ -0,0 +1,11 @@ +const strictEqual = require('assert').strictEqual; + +global.mathtest = {}; + +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 new file mode 100644 index 00000000..49afc623 --- /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!(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 new file mode 100644 index 00000000..1c3dbf3a --- /dev/null +++ b/crates/webidl-tests/namespace.webidl @@ -0,0 +1,4 @@ +namespace mathtest { + long add_one(long val); + double powf(double base, double exponent); +}; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 4e58443a..f6c109fe 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,30 @@ pub(crate) struct InterfaceData<'src> { pub(crate) superclass: Option<&'src str>, } +/// We need to collect namespace data during the first pass, to be used later. +pub(crate) struct NamespaceData<'src> { + /// Whether only partial namespaces were encountered + pub(crate) partial: bool, + pub(crate) operations: BTreeMap<Option<&'src str>, OperationData<'src>>, +} + +impl<'src> NamespaceData<'src> { + /// 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(), + } + } +} + #[derive(PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum OperationId<'src> { Constructor, @@ -83,6 +108,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 +138,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>, @@ -131,7 +159,7 @@ fn first_pass_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) @@ -146,7 +174,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); } @@ -199,7 +227,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 +235,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 +245,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 +286,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())), }, @@ -312,6 +344,91 @@ 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<()> { + record + .namespaces + .entry(self.identifier.0) + .and_modify(|entry| entry.partial = false) + .or_insert_with(NamespaceData::empty_non_partial); + + 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::empty_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<Item = String> + 'me diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 0fb33c90..4d139c0c 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -36,9 +36,9 @@ 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}; +use heck::{ShoutySnakeCase, SnakeCase}; use proc_macro2::{Ident, Span}; use weedle::argument::Argument; use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList}; @@ -185,13 +185,17 @@ 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) => { + namespace.webidl_parse(program, first_pass, ())? + } // 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) } } @@ -303,7 +307,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, @@ -657,12 +661,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 +753,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") } @@ -764,10 +773,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(()), }; @@ -783,3 +792,149 @@ impl<'src> WebidlParse<'src, &'src str> for weedle::interface::ConstMember<'src> 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(()); + } + + let rust_name = rust_ident(self.identifier.0.to_snake_case().as_str()); + + program.modules.entry(rust_name.clone()) + .and_modify(|_| warn!("Namespace with rust name {:?} added more than once", rust_name)) + .or_insert_with(|| backend::ast::Module { + vis: public(), + imports: Default::default() + }); + + 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, ()> 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, + _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, + first_pass: &FirstPassRecord<'src>, + ns_names: NamespaceNames<'src> + ) -> Result<()> { + match self { + weedle::namespace::NamespaceMember::Operation(op) => { + op.webidl_parse(program, first_pass, ns_names)?; + } + weedle::namespace::NamespaceMember::Attribute(_) => { + warn!("Attribute namespace members are not supported") + } + } + Ok(()) + } +} + +impl<'src> WebidlParse<'src, NamespaceNames<'src>> for weedle::namespace::OperationNamespaceMember<'src> { + fn webidl_parse( + &'src self, + program: &mut backend::ast::Program, + first_pass: &FirstPassRecord<'src>, + ns_names: NamespaceNames<'src> + ) -> Result<()> { + if util::is_chrome_only(&self.attributes) { + return Ok(()); + } + + 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 import = backend::ast::Import { + module: None, + js_namespace: Some(raw_ident(ns_names.js_name)), + kind: backend::ast::ImportKind::Function(imported_fn), + }; + + program + .modules + .get_mut(ns_names.rust_name) + .unwrap() + .imports + .push(import); + + Ok(()) + } +} + diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 554f90f0..2754ea7a 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<backend::ast::ImportFunction> { - 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,91 @@ 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: Option<&str>, + return_type: &weedle::types::ReturnType, + namespace_name: &str, + catch: bool, + ) -> Option<backend::ast::ImportFunction> { + let (overloaded, same_argument_names) = self.get_namespaced_operation_overloading( + arguments, + operation_name, + namespace_name, + ); + + let name = match operation_name { + Some(name) => name.to_string(), + None => { + warn!("Operations without a name are unsupported"); + return None; + } + }; + + 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{}", + namespace_name, + name, + mdn_doc(namespace_name, Some(&name))); // checked link + + self.create_function( + &name, + overloaded, + same_argument_names, + arguments, + ret, + backend::ast::ImportFunctionKind::Normal, + false, + catch, + Some(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], + 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(&operation_name) { + 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, diff --git a/guide/src/testing.md b/guide/src/testing.md index 8f4c2974..fe3bca62 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 diff --git a/src/lib.rs b/src/lib.rs index 2f6a2be0..a4290155 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.