diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index d8989e35da8a..947ee455cd81 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -99,6 +99,10 @@ pub enum ImportFunctionKind { ty: syn::Type, kind: MethodKind, }, + ScopedMethod { + ty: syn::Type, + operation: Operation, + }, Normal, } @@ -407,6 +411,29 @@ impl ImportFunction { } fn shared(&self) -> shared::ImportFunction { + let shared_operation = |operation: &Operation| { + let is_static = operation.is_static; + let kind = match &operation.kind { + OperationKind::Regular => shared::OperationKind::Regular, + OperationKind::Getter(g) => { + let g = g.as_ref().map(|g| g.to_string()); + shared::OperationKind::Getter( + g.unwrap_or_else(|| self.infer_getter_property()), + ) + } + OperationKind::Setter(s) => { + let s = s.as_ref().map(|s| s.to_string()); + shared::OperationKind::Setter( + s.unwrap_or_else(|| self.infer_setter_property()), + ) + } + OperationKind::IndexingGetter => shared::OperationKind::IndexingGetter, + OperationKind::IndexingSetter => shared::OperationKind::IndexingSetter, + OperationKind::IndexingDeleter => shared::OperationKind::IndexingDeleter, + }; + shared::Operation { is_static, kind } + }; + let method = match self.kind { ImportFunctionKind::Method { ref class, @@ -415,34 +442,21 @@ impl ImportFunction { } => { let kind = match kind { MethodKind::Constructor => shared::MethodKind::Constructor, - MethodKind::Operation(Operation { is_static, kind }) => { - let is_static = *is_static; - let kind = match kind { - OperationKind::Regular => shared::OperationKind::Regular, - OperationKind::Getter(g) => { - let g = g.as_ref().map(|g| g.to_string()); - shared::OperationKind::Getter( - g.unwrap_or_else(|| self.infer_getter_property()), - ) - } - OperationKind::Setter(s) => { - let s = s.as_ref().map(|s| s.to_string()); - shared::OperationKind::Setter( - s.unwrap_or_else(|| self.infer_setter_property()), - ) - } - OperationKind::IndexingGetter => shared::OperationKind::IndexingGetter, - OperationKind::IndexingSetter => shared::OperationKind::IndexingSetter, - OperationKind::IndexingDeleter => shared::OperationKind::IndexingDeleter, - }; - shared::MethodKind::Operation(shared::Operation { is_static, kind }) + MethodKind::Operation(op) => { + shared::MethodKind::Operation(shared_operation(op)) } }; Some(shared::MethodData { - class: class.clone(), + class: Some(class.clone()), kind, }) } + ImportFunctionKind::ScopedMethod { ref operation, .. } => { + Some(shared::MethodData { + class: None, + kind: shared::MethodKind::Operation(shared_operation(operation)), + }) + } ImportFunctionKind::Normal => None, }; diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 8bdd391c3425..df04405f6fde 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -784,6 +784,9 @@ impl TryToTokens for ast::ImportFunction { } class_ty = Some(ty); } + ast::ImportFunctionKind::ScopedMethod { ref ty, .. } => { + class_ty = Some(ty); + } ast::ImportFunctionKind::Normal => {} } let vis = &self.function.rust_vis; diff --git a/crates/backend/src/defined.rs b/crates/backend/src/defined.rs index a7f6359e4177..f697b502b8df 100644 --- a/crates/backend/src/defined.rs +++ b/crates/backend/src/defined.rs @@ -246,6 +246,7 @@ impl ImportedTypes for ast::ImportFunctionKind { { match self { ast::ImportFunctionKind::Method { ty, .. } => ty.imported_types(f), + ast::ImportFunctionKind::ScopedMethod { ty, .. } => ty.imported_types(f), ast::ImportFunctionKind::Normal => {} } } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 9c5045f6062d..25ff39f3eaf4 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1959,127 +1959,27 @@ impl<'a, 'b> SubContext<'a, 'b> { Some(d) => d, }; - let target = match &import.method { - Some(shared::MethodData { class, kind }) => { - let class = self.import_name(info, class)?; - match kind { - shared::MethodKind::Constructor => format!("new {}", class), - shared::MethodKind::Operation(shared::Operation { is_static, kind }) => { - let target = if import.structural { - let location = if *is_static { &class } else { "this" }; - - match kind { - shared::OperationKind::Regular => { - let nargs = descriptor.unwrap_function().arguments.len(); - let mut s = format!("function("); - for i in 0..nargs - 1 { - if i > 0 { - drop(write!(s, ", ")); - } - drop(write!(s, "x{}", i)); - } - s.push_str(") { \nreturn this."); - s.push_str(&import.function.name); - s.push_str("("); - for i in 0..nargs - 1 { - if i > 0 { - drop(write!(s, ", ")); - } - drop(write!(s, "x{}", i)); - } - s.push_str(");\n}"); - s - } - shared::OperationKind::Getter(g) => format!( - "function() {{ - return {}.{}; - }}", - location, g - ), - shared::OperationKind::Setter(s) => format!( - "function(y) {{ - {}.{} = y; - }}", - location, s - ), - shared::OperationKind::IndexingGetter => format!( - "function(y) {{ - return {}[y]; - }}", - location - ), - shared::OperationKind::IndexingSetter => format!( - "function(y, z) {{ - {}[y] = z; - }}", - location - ), - shared::OperationKind::IndexingDeleter => format!( - "function(y) {{ - delete {}[y]; - }}", - location - ), - } - } else { - let (location, binding) = if *is_static { - ("", format!(".bind({})", class)) - } else { - (".prototype", "".into()) - }; - - match kind { - shared::OperationKind::Regular => { - format!("{}{}.{}{}", class, location, import.function.name, binding) - } - shared::OperationKind::Getter(g) => { - self.cx.expose_get_inherited_descriptor(); - format!( - "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').get{}", - class, location, g, binding, - ) - } - shared::OperationKind::Setter(s) => { - self.cx.expose_get_inherited_descriptor(); - format!( - "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').set{}", - class, location, s, binding, - ) - } - shared::OperationKind::IndexingGetter => panic!("indexing getter should be structural"), - shared::OperationKind::IndexingSetter => panic!("indexing setter should be structural"), - shared::OperationKind::IndexingDeleter => panic!("indexing deleter should be structural"), - } - }; - - let fallback = if import.structural { - "".to_string() - } else { - format!( - " || function() {{ - throw new Error(`wasm-bindgen: {} does not exist`); - }}", - target - ) - }; - - self.cx.global(&format!( - " - const {}_target = {} {} ; - ", - import.shim, target, fallback - )); - format!( - "{}_target{}", - import.shim, - if *is_static { "" } else { ".call" } - ) - } - } - } + let target = self.generated_import_target(info, import, &descriptor)?; + + let js = Rust2Js::new(self.cx) + .catch(import.catch) + .process(descriptor.unwrap_function())? + .finish(&target); + self.cx.export(&import.shim, &js, None); + Ok(()) + } + + fn generated_import_target( + &mut self, + info: &shared::Import, + import: &shared::ImportFunction, + descriptor: &Descriptor, + ) -> Result { + let method_data = match &import.method { + Some(data) => data, None => { let name = self.import_name(info, &import.function.name)?; - if name.contains(".") { + return Ok(if name.contains(".") { self.cx.global(&format!( " const {}_target = {}; @@ -2089,16 +1989,145 @@ impl<'a, 'b> SubContext<'a, 'b> { format!("{}_target", import.shim) } else { name + }) + } + }; + + let class = match &method_data.class { + Some(class) => self.import_name(info, class)?, + None => { + let op = match &method_data.kind { + shared::MethodKind::Operation(op) => op, + shared::MethodKind::Constructor => { + bail!("\"no class\" methods cannot be constructors") + } + }; + match &op.kind { + shared::OperationKind::Regular => { + return Ok(import.function.name.to_string()) + } + shared::OperationKind::Getter(g) => { + return Ok(format!("(() => {})", g)); + } + shared::OperationKind::Setter(g) => { + return Ok(format!("(v => {} = v)", g)); + } + _ => bail!("\"no class\" methods must be regular/getter/setter"), + } + + } + }; + let op = match &method_data.kind { + shared::MethodKind::Constructor => return Ok(format!("new {}", class)), + shared::MethodKind::Operation(op) => op, + }; + let target = if import.structural { + let location = if op.is_static { &class } else { "this" }; + + match &op.kind { + shared::OperationKind::Regular => { + let nargs = descriptor.unwrap_function().arguments.len(); + let mut s = format!("function("); + for i in 0..nargs - 1 { + if i > 0 { + drop(write!(s, ", ")); + } + drop(write!(s, "x{}", i)); + } + s.push_str(") { \nreturn this."); + s.push_str(&import.function.name); + s.push_str("("); + for i in 0..nargs - 1 { + if i > 0 { + drop(write!(s, ", ")); + } + drop(write!(s, "x{}", i)); + } + s.push_str(");\n}"); + s + } + shared::OperationKind::Getter(g) => format!( + "function() {{ + return {}.{}; + }}", + location, g + ), + shared::OperationKind::Setter(s) => format!( + "function(y) {{ + {}.{} = y; + }}", + location, s + ), + shared::OperationKind::IndexingGetter => format!( + "function(y) {{ + return {}[y]; + }}", + location + ), + shared::OperationKind::IndexingSetter => format!( + "function(y, z) {{ + {}[y] = z; + }}", + location + ), + shared::OperationKind::IndexingDeleter => format!( + "function(y) {{ + delete {}[y]; + }}", + location + ), + } + } else { + let (location, binding) = if op.is_static { + ("", format!(".bind({})", class)) + } else { + (".prototype", "".into()) + }; + + match &op.kind { + shared::OperationKind::Regular => { + format!("{}{}.{}{}", class, location, import.function.name, binding) + } + shared::OperationKind::Getter(g) => { + self.cx.expose_get_inherited_descriptor(); + format!( + "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').get{}", + class, location, g, binding, + ) } + shared::OperationKind::Setter(s) => { + self.cx.expose_get_inherited_descriptor(); + format!( + "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').set{}", + class, location, s, binding, + ) + } + shared::OperationKind::IndexingGetter => panic!("indexing getter should be structural"), + shared::OperationKind::IndexingSetter => panic!("indexing setter should be structural"), + shared::OperationKind::IndexingDeleter => panic!("indexing deleter should be structural"), } }; - let js = Rust2Js::new(self.cx) - .catch(import.catch) - .process(descriptor.unwrap_function())? - .finish(&target); - self.cx.export(&import.shim, &js, None); - Ok(()) + let fallback = if import.structural { + "".to_string() + } else { + format!( + " || function() {{ + throw new Error(`wasm-bindgen: {} does not exist`); + }}", + target + ) + }; + + self.cx.global(&format!( + "const {}_target = {}{};", + import.shim, target, fallback + )); + Ok(format!( + "{}_target{}", + import.shim, + if op.is_static { "" } else { ".call" } + )) } fn generate_import_type( diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 47fe0fb2976e..33ce3eb28c76 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -514,6 +514,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn let shim = { let ns = match kind { + ast::ImportFunctionKind::ScopedMethod { .. } | ast::ImportFunctionKind::Normal => (0, "n"), ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]), }; diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 20d78db71699..16ffd96404dd 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -48,7 +48,7 @@ pub struct ImportFunction { #[derive(Deserialize, Serialize)] pub struct MethodData { - pub class: String, + pub class: Option, pub kind: MethodKind, } diff --git a/crates/webidl-tests/global.js b/crates/webidl-tests/global.js new file mode 100644 index 000000000000..305de81b3e41 --- /dev/null +++ b/crates/webidl-tests/global.js @@ -0,0 +1,4 @@ +global.global_no_args = () => 3; +global.global_with_args = (a, b) => a + b; +global.global_attribute = 'x'; + diff --git a/crates/webidl-tests/global.rs b/crates/webidl-tests/global.rs new file mode 100644 index 000000000000..12a7512e851d --- /dev/null +++ b/crates/webidl-tests/global.rs @@ -0,0 +1,12 @@ +use wasm_bindgen_test::*; + +include!(concat!(env!("OUT_DIR"), "/global.rs")); + +#[wasm_bindgen_test] +fn works() { + assert_eq!(Global::global_no_args(), 3); + assert_eq!(Global::global_with_args("a", "b"), "ab"); + assert_eq!(Global::global_attribute(), "x"); + Global::set_global_attribute("y"); + assert_eq!(Global::global_attribute(), "y"); +} diff --git a/crates/webidl-tests/global.webidl b/crates/webidl-tests/global.webidl new file mode 100644 index 000000000000..01cb1d976a99 --- /dev/null +++ b/crates/webidl-tests/global.webidl @@ -0,0 +1,6 @@ +[Global=x] +interface Global { + unsigned long global_no_args(); + DOMString global_with_args(DOMString a, DOMString b); + attribute DOMString global_attribute; +}; diff --git a/crates/webidl-tests/main.rs b/crates/webidl-tests/main.rs index 9e7432182de0..66266567bf2c 100644 --- a/crates/webidl-tests/main.rs +++ b/crates/webidl-tests/main.rs @@ -10,3 +10,4 @@ pub mod namespace; pub mod simple; pub mod throws; pub mod dictionary; +pub mod global; diff --git a/crates/webidl-tests/simple.js b/crates/webidl-tests/simple.js index 0c63082b6bdf..bb1ace6b9f25 100644 --- a/crates/webidl-tests/simple.js +++ b/crates/webidl-tests/simple.js @@ -87,11 +87,7 @@ global.Unforgeable = class Unforgeable { } }; -global.GlobalMethod = class GlobalMethod { - constructor() { - this.m = () => 123; - } -}; +global.m = () => 123; global.Indexing = function () { return new Proxy({}, { diff --git a/crates/webidl-tests/simple.rs b/crates/webidl-tests/simple.rs index edec1965556c..b59a9ad9b553 100644 --- a/crates/webidl-tests/simple.rs +++ b/crates/webidl-tests/simple.rs @@ -64,8 +64,7 @@ fn nullable_method() { #[wasm_bindgen_test] fn global_method() { - let f = GlobalMethod::new().unwrap(); - assert_eq!(f.m(), 123); + assert_eq!(GlobalMethod::m(), 123); } #[wasm_bindgen_test] diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 7937cc20cc8a..af09eaa950a9 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -591,6 +591,11 @@ fn member_attribute<'src>( 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, @@ -599,6 +604,7 @@ fn member_attribute<'src>( is_static, is_structural, throws, + global, ) { program.imports.push(wrap_import_function(import_function)); } @@ -611,6 +617,7 @@ fn member_attribute<'src>( is_static, is_structural, throws, + global, ) { program.imports.push(wrap_import_function(import_function)); } @@ -712,6 +719,12 @@ fn member_operation<'src>( 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( @@ -724,15 +737,10 @@ fn member_operation<'src>( OperationId::IndexingGetter | OperationId::IndexingSetter | OperationId::IndexingDeleter => true, - _ => { - first_pass - .interfaces - .get(self_name) - .map(|interface_data| interface_data.global) - .unwrap_or(false) - } + _ => false, }, util::throws(attrs), + global, ); for method in methods { diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 2276064d592c..06e89dfc7f7b 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -297,6 +297,7 @@ impl<'src> FirstPassRecord<'src> { let rust_name = rust_ident(&rust_name); let shim = { let ns = match kind { + backend::ast::ImportFunctionKind::ScopedMethod { .. } | backend::ast::ImportFunctionKind::Normal => "", backend::ast::ImportFunctionKind::Method { ref class, .. } => class, }; @@ -389,6 +390,7 @@ impl<'src> FirstPassRecord<'src> { is_static: bool, structural: bool, catch: bool, + global: bool, ) -> Vec { let (overloaded, same_argument_names) = self.get_operation_overloading( arguments, @@ -410,20 +412,26 @@ impl<'src> FirstPassRecord<'src> { first_pass::OperationId::IndexingSetter => "set", first_pass::OperationId::IndexingDeleter => "delete", }; - - let kind = backend::ast::ImportFunctionKind::Method { - class: self_name.to_string(), - ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())), - 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, - }, - }), + let operation_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, + }; + let operation = backend::ast::Operation { is_static, kind: operation_kind }; + let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str())); + let kind = if global { + backend::ast::ImportFunctionKind::ScopedMethod { + ty, + operation, + } + } else { + backend::ast::ImportFunctionKind::Method { + class: self_name.to_string(), + ty, + kind: backend::ast::MethodKind::Operation(operation), + } }; let ret = match return_type.to_idl_type(self) { @@ -591,19 +599,29 @@ impl<'src> FirstPassRecord<'src> { is_static: bool, is_structural: bool, catch: bool, + global: bool, ) -> Vec { let ret = match ty.to_idl_type(self) { None => return Vec::new(), Some(idl_type) => idl_type, }; + let operation = backend::ast::Operation { + is_static, + kind: backend::ast::OperationKind::Getter(Some(raw_ident(name))), + }; + let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str())); - let kind = backend::ast::ImportFunctionKind::Method { - class: self_name.to_string(), - ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())), - kind: backend::ast::MethodKind::Operation(backend::ast::Operation { - is_static, - kind: backend::ast::OperationKind::Getter(Some(raw_ident(name))), - }), + let kind = if global { + backend::ast::ImportFunctionKind::ScopedMethod { + ty, + operation, + } + } else { + backend::ast::ImportFunctionKind::Method { + class: self_name.to_string(), + ty, + kind: backend::ast::MethodKind::Operation(operation), + } }; let doc_comment = Some(format!("The `{}` getter\n\n{}", name, mdn_doc(self_name, Some(name)))); @@ -614,19 +632,30 @@ impl<'src> FirstPassRecord<'src> { pub fn create_setter( &self, name: &str, - ty: weedle::types::Type, + field_ty: weedle::types::Type, self_name: &str, is_static: bool, is_structural: bool, catch: bool, + global: bool, ) -> Vec { - let kind = backend::ast::ImportFunctionKind::Method { - class: self_name.to_string(), - ty: ident_ty(rust_ident(camel_case_ident(&self_name).as_str())), - kind: backend::ast::MethodKind::Operation(backend::ast::Operation { - is_static, - kind: backend::ast::OperationKind::Setter(Some(raw_ident(name))), - }), + let operation = backend::ast::Operation { + is_static, + kind: backend::ast::OperationKind::Setter(Some(raw_ident(name))), + }; + let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str())); + + let kind = if global { + backend::ast::ImportFunctionKind::ScopedMethod { + ty, + operation, + } + } else { + backend::ast::ImportFunctionKind::Method { + class: self_name.to_string(), + ty, + kind: backend::ast::MethodKind::Operation(operation), + } }; let doc_comment = Some(format!("The `{}` setter\n\n{}", name, mdn_doc(self_name, Some(name)))); @@ -636,7 +665,7 @@ impl<'src> FirstPassRecord<'src> { false, &[( name, - match ty.to_idl_type(self) { + match field_ty.to_idl_type(self) { None => return Vec::new(), Some(idl_type) => idl_type, }, diff --git a/examples/canvas/src/lib.rs b/examples/canvas/src/lib.rs index 722a10a93bd9..420016e29207 100755 --- a/examples/canvas/src/lib.rs +++ b/examples/canvas/src/lib.rs @@ -6,13 +6,9 @@ use std::f64; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -#[wasm_bindgen] -extern "C" { - static document: web_sys::Document; -} - #[wasm_bindgen] pub fn draw() { + let document = web_sys::Window::document().unwrap(); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas: web_sys::HtmlCanvasElement = canvas .dyn_into::()