From fe5cde86362018277122e05bda84a81bc4c685d9 Mon Sep 17 00:00:00 2001 From: "R. Andrew Ohana" Date: Thu, 14 Jun 2018 19:21:33 -0700 Subject: [PATCH] webidl: add support for static methods --- crates/backend/src/ast.rs | 35 +++++++---- crates/backend/src/codegen.rs | 11 ++-- crates/webidl/src/lib.rs | 109 ++++++++++++---------------------- crates/webidl/src/util.rs | 63 +++++++++++++++++++- tests/all/webidl/simple.rs | 56 +++++++++++++++++ 5 files changed, 186 insertions(+), 88 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 8d1ac66c..77aebe74 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -49,11 +49,21 @@ pub struct ImportFunction { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub enum ImportFunctionKind { - Method { class: String, ty: syn::Type }, - JsConstructor { class: String, ty: syn::Type }, + Method { + class: String, + ty: syn::Type, + kind: MethodKind, + }, Normal, } +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub enum MethodKind { + Normal, + Constructor, + Static, +} + #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub struct ImportStatic { pub vis: syn::Visibility, @@ -398,6 +408,7 @@ impl Program { ImportFunctionKind::Method { class: class_name.to_string(), ty: class.clone(), + kind: MethodKind::Normal, } } else if wasm.opts.constructor() { let class = match wasm.ret { @@ -414,9 +425,10 @@ impl Program { let class_name = extract_path_ident(class_name) .expect("first argument of method must be a bare type"); - ImportFunctionKind::JsConstructor { + ImportFunctionKind::Method { class: class_name.to_string(), ty: class.clone(), + kind: MethodKind::Constructor, } } else { ImportFunctionKind::Normal @@ -426,7 +438,6 @@ impl Program { let ns = match kind { ImportFunctionKind::Normal => "n", ImportFunctionKind::Method { ref class, .. } => class, - ImportFunctionKind::JsConstructor { ref class, .. } => class, }; format!("__wbg_f_{}_{}_{}", js_name, f.ident, ns) }; @@ -694,12 +705,16 @@ impl ImportFunction { let mut js_new = false; let mut class_name = None; match self.kind { - ImportFunctionKind::Method { ref class, .. } => { - method = true; - class_name = Some(class); - } - ImportFunctionKind::JsConstructor { ref class, .. } => { - js_new = true; + ImportFunctionKind::Method { + ref class, + ref kind, + .. + } => { + match kind { + MethodKind::Normal => method = true, + MethodKind::Constructor => js_new = true, + MethodKind::Static => {} + } class_name = Some(class); } ImportFunctionKind::Normal => {} diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 96275b82..a397aa4b 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -552,11 +552,12 @@ impl ToTokens for ast::ImportFunction { let mut class_ty = None; let mut is_method = false; match self.kind { - ast::ImportFunctionKind::Method { ref ty, .. } => { - is_method = true; - class_ty = Some(ty); - } - ast::ImportFunctionKind::JsConstructor { ref ty, .. } => { + ast::ImportFunctionKind::Method { + ref ty, ref kind, .. + } => { + if let ast::MethodKind::Normal = kind { + is_method = true; + } class_ty = Some(ty); } ast::ImportFunctionKind::Normal => {} diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 04f409cc..297ed813 100755 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -24,12 +24,14 @@ use std::iter; use std::path::Path; use failure::ResultExt; -use heck::CamelCase; use quote::ToTokens; mod util; -use util::{create_function, ident_ty, raw_ident, rust_ident, webidl_ty_to_syn_ty, TypePosition}; +use util::{ + create_basic_method, create_function, ident_ty, raw_ident, rust_ident, webidl_ty_to_syn_ty, + wrap_import_function, TypePosition, +}; /// Either `Ok(t)` or `Err(failure::Error)`. pub type Result = ::std::result::Result; @@ -184,9 +186,10 @@ impl<'a> WebidlParse<&'a webidl::ast::NonPartialInterface> for webidl::ast::Exte ) -> Result<()> { let mut add_constructor = |arguments: &[webidl::ast::Argument], class: &str| { let self_ty = ident_ty(rust_ident(&interface.name)); - let kind = backend::ast::ImportFunctionKind::JsConstructor { + let kind = backend::ast::ImportFunctionKind::Method { class: class.to_string(), ty: self_ty.clone(), + kind: backend::ast::MethodKind::Constructor, }; create_function( "new", @@ -216,7 +219,7 @@ impl<'a> WebidlParse<&'a webidl::ast::NonPartialInterface> for webidl::ast::Exte webidl::ast::ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) if name == "Constructor" => { - add_constructor(&[] as &[_], &interface.name); + add_constructor(&[], &interface.name); } webidl::ast::ExtendedAttribute::NamedArgumentList( webidl::ast::NamedArgumentListExtendedAttribute { @@ -275,12 +278,11 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::Attribute { impl<'a> WebidlParse<&'a str> for webidl::ast::Operation { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { - match *self { - webidl::ast::Operation::Regular(ref op) => op.webidl_parse(program, self_name), + match self { + webidl::ast::Operation::Regular(op) => op.webidl_parse(program, self_name), + webidl::ast::Operation::Static(op) => op.webidl_parse(program, self_name), // TODO - webidl::ast::Operation::Special(_) - | webidl::ast::Operation::Static(_) - | webidl::ast::Operation::Stringifier(_) => { + webidl::ast::Operation::Special(_) | webidl::ast::Operation::Stringifier(_) => { warn!("Unsupported WebIDL operation: {:?}", self); Ok(()) } @@ -306,6 +308,7 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute { let kind = backend::ast::ImportFunctionKind::Method { class: self_name.to_string(), ty: ident_ty(rust_ident(self_name)), + kind: backend::ast::MethodKind::Normal, }; create_function( @@ -316,12 +319,7 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute { vec![backend::ast::BindgenAttr::Getter(Some(raw_ident( &this.name, )))], - ).map(|function| backend::ast::Import { - module: None, - version: None, - js_namespace: None, - kind: backend::ast::ImportKind::Function(function), - }) + ).map(wrap_import_function) } fn create_setter( @@ -331,22 +329,18 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute { let kind = backend::ast::ImportFunctionKind::Method { class: self_name.to_string(), ty: ident_ty(rust_ident(self_name)), + kind: backend::ast::MethodKind::Normal, }; create_function( - &format!("set_{}", this.name.to_camel_case()), + &format!("set_{}", this.name), iter::once((&*this.name, &*this.type_, false)), kind, None, vec![backend::ast::BindgenAttr::Setter(Some(raw_ident( &this.name, )))], - ).map(|function| backend::ast::Import { - module: None, - version: None, - js_namespace: None, - kind: backend::ast::ImportKind::Function(function), - }) + ).map(wrap_import_function) } create_getter(self, self_name).map(|import| program.imports.push(import)); @@ -361,54 +355,29 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute { impl<'a> WebidlParse<&'a str> for webidl::ast::RegularOperation { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { - let name = match self.name { - None => { - warn!( - "Operations without a name are unsupported. Skipping {:?}", - self - ); - return Ok(()); - } - Some(ref name) => name, - }; - - let kind = backend::ast::ImportFunctionKind::Method { - class: self_name.to_string(), - ty: ident_ty(rust_ident(self_name)), - }; - - let ret = match self.return_type { - webidl::ast::ReturnType::Void => None, - webidl::ast::ReturnType::NonVoid(ref ty) => { - match webidl_ty_to_syn_ty(ty, TypePosition::Return) { - None => { - warn!( - "Operation's return type is not yet supported: {:?}. Skipping bindings for {:?}", - ty, self - ); - return Ok(()); - } - Some(ty) => Some(ty), - } - } - }; - - create_function( - &name, - self.arguments - .iter() - .map(|arg| (&*arg.name, &*arg.type_, arg.variadic)), - kind, - ret, - Vec::new(), - ).map(|function| { - program.imports.push(backend::ast::Import { - module: None, - version: None, - js_namespace: None, - kind: backend::ast::ImportKind::Function(function), - }) - }); + create_basic_method( + &self.arguments, + self.name.as_ref(), + &self.return_type, + self_name, + backend::ast::MethodKind::Normal, + ).map(wrap_import_function) + .map(|import| program.imports.push(import)); + + Ok(()) + } +} + +impl<'a> WebidlParse<&'a str> for webidl::ast::StaticOperation { + fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { + create_basic_method( + &self.arguments, + self.name.as_ref(), + &self.return_type, + self_name, + backend::ast::MethodKind::Static, + ).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 7d07569e..111e22c5 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -163,7 +163,12 @@ where { let estimate = arguments.size_hint(); let len = estimate.1.unwrap_or(estimate.0); - let mut res = if let backend::ast::ImportFunctionKind::Method { ty, .. } = kind { + let mut res = if let backend::ast::ImportFunctionKind::Method { + ty, + kind: backend::ast::MethodKind::Normal, + .. + } = kind + { let mut res = Vec::with_capacity(len + 1); res.push(simple_fn_arg(raw_ident("self_"), shared_ref(ty.clone()))); res @@ -189,7 +194,7 @@ where Some(res) } -pub fn create_function<'a, 'b, I>( +pub fn create_function<'a, I>( name: &str, arguments: I, kind: backend::ast::ImportFunctionKind, @@ -216,7 +221,6 @@ where let ns = match kind { backend::ast::ImportFunctionKind::Normal => "", backend::ast::ImportFunctionKind::Method { ref class, .. } => class, - backend::ast::ImportFunctionKind::JsConstructor { ref class, .. } => class, }; raw_ident(&format!("__widl_f_{}_{}", rust_name, ns)) @@ -239,3 +243,56 @@ where shim, }) } + +pub fn create_basic_method( + arguments: &[webidl::ast::Argument], + name: Option<&String>, + return_type: &webidl::ast::ReturnType, + self_name: &str, + kind: backend::ast::MethodKind, +) -> Option { + let name = match name { + None => { + warn!("Operations without a name are unsupported"); + return None; + } + Some(ref name) => name, + }; + + let kind = backend::ast::ImportFunctionKind::Method { + class: self_name.to_string(), + ty: ident_ty(rust_ident(self_name)), + kind, + }; + + let ret = match return_type { + webidl::ast::ReturnType::Void => None, + webidl::ast::ReturnType::NonVoid(ty) => match webidl_ty_to_syn_ty(ty, TypePosition::Return) + { + None => { + warn!("Operation's return type is not yet supported: {:?}", ty); + return None; + } + Some(ty) => Some(ty), + }, + }; + + create_function( + &name, + arguments + .iter() + .map(|arg| (&*arg.name, &*arg.type_, arg.variadic)), + kind, + ret, + Vec::new(), + ) +} + +pub fn wrap_import_function(function: backend::ast::ImportFunction) -> backend::ast::Import { + backend::ast::Import { + module: None, + version: None, + js_namespace: None, + kind: backend::ast::ImportKind::Function(function), + } +} diff --git a/tests/all/webidl/simple.rs b/tests/all/webidl/simple.rs index 767f25d6..a14b45e6 100644 --- a/tests/all/webidl/simple.rs +++ b/tests/all/webidl/simple.rs @@ -176,3 +176,59 @@ fn named_constructor() { ) .test(); } + +#[test] +fn static_method() { + project() + .file( + "foo.webidl", + r#" + interface Foo { + [Pure] + static double swap(double value); + }; + "#, + ) + .file( + "foo.ts", + r#" + export class Foo { + private static value: number = 0; + static swap(value: number): number { + const res = Foo.value; + Foo.value = value; + return res; + } + } + "#, + ) + .file( + "src/lib.rs", + r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + pub mod foo; + + use foo::Foo; + + #[wasm_bindgen] + pub fn test() { + let tmp = Foo::swap(3.14159) == 0.; + assert!(tmp); + let tmp = Foo::swap(2.71828) == 3.14159; + assert!(tmp); + let tmp = Foo::swap(2.71828) != 3.14159; + assert!(tmp); + let tmp = Foo::swap(3.14159) == 2.71828; + assert!(tmp); + let tmp = Foo::swap(3.14159) != 2.71828; + assert!(tmp); + } + "#, + ) + .test(); +}