diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 6c439030..e24e783f 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -778,7 +778,7 @@ impl StructField { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Default)] pub struct BindgenAttrs { - attrs: Vec, + pub attrs: Vec, } impl BindgenAttrs { @@ -916,7 +916,7 @@ impl syn::synom::Synom for BindgenAttrs { } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] -enum BindgenAttr { +pub enum BindgenAttr { Catch, Constructor, Method, diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 67f206b7..a4039a42 100755 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -20,6 +20,7 @@ use proc_macro2::Ident; use quote::ToTokens; use std::fs; use std::io::{self, Read}; +use std::iter::FromIterator; use std::path::Path; /// Either `Ok(t)` or `Err(failure::Error)`. @@ -40,7 +41,7 @@ pub fn parse(webidl_source: &str) -> Result { let definitions = webidl::parse_string(webidl_source).context("parsing WebIDL source text")?; let mut program = backend::ast::Program::default(); - definitions.webidl_parse(&mut program)?; + definitions.webidl_parse(&mut program, ())?; Ok(program) } @@ -65,23 +66,31 @@ fn compile_ast(ast: &backend::ast::Program) -> String { tokens.to_string() } -trait WebidlParse { - fn webidl_parse(&self, program: &mut backend::ast::Program) -> Result<()>; +trait WebidlParse<'a> { + type Extra; + + fn webidl_parse(&self, program: &mut backend::ast::Program, extra: Self::Extra) -> Result<()>; } -impl WebidlParse for Vec { - fn webidl_parse(&self, program: &mut backend::ast::Program) -> Result<()> { +impl<'a> WebidlParse<'a> for Vec { + type Extra = (); + + fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { for def in self { - def.webidl_parse(program)?; + def.webidl_parse(program, ())?; } Ok(()) } } -impl WebidlParse for webidl::ast::Definition { - fn webidl_parse(&self, program: &mut backend::ast::Program) -> Result<()> { +impl<'a> WebidlParse<'a> for webidl::ast::Definition { + type Extra = (); + + fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { match *self { - webidl::ast::Definition::Interface(ref interface) => interface.webidl_parse(program), + webidl::ast::Definition::Interface(ref interface) => { + interface.webidl_parse(program, ()) + } // TODO webidl::ast::Definition::Callback(..) | webidl::ast::Definition::Dictionary(..) @@ -95,18 +104,24 @@ impl WebidlParse for webidl::ast::Definition { } } -impl WebidlParse for webidl::ast::Interface { - fn webidl_parse(&self, program: &mut backend::ast::Program) -> Result<()> { +impl<'a> WebidlParse<'a> for webidl::ast::Interface { + type Extra = (); + + fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { match *self { - webidl::ast::Interface::NonPartial(ref interface) => interface.webidl_parse(program), + webidl::ast::Interface::NonPartial(ref interface) => { + interface.webidl_parse(program, ()) + } // TODO webidl::ast::Interface::Callback(..) | webidl::ast::Interface::Partial(..) => Ok(()), } } } -impl WebidlParse for webidl::ast::NonPartialInterface { - fn webidl_parse(&self, program: &mut backend::ast::Program) -> Result<()> { +impl<'a> WebidlParse<'a> for webidl::ast::NonPartialInterface { + type Extra = (); + + fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { program.imports.push(backend::ast::Import { module: None, version: None, @@ -118,7 +133,234 @@ impl WebidlParse for webidl::ast::NonPartialInterface { name: Ident::new(&self.name, proc_macro2::Span::call_site()), }), }); - println!("{:#?}", self); + + for member in &self.members { + member.webidl_parse(program, &self.name)?; + } + + Ok(()) + } +} + +impl<'a> WebidlParse<'a> for webidl::ast::InterfaceMember { + type Extra = &'a str; + + fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { + match *self { + webidl::ast::InterfaceMember::Operation(ref op) => op.webidl_parse(program, self_name), + // TODO + webidl::ast::InterfaceMember::Attribute(_) + | webidl::ast::InterfaceMember::Const(_) + | webidl::ast::InterfaceMember::Iterable(_) + | webidl::ast::InterfaceMember::Maplike(_) + | webidl::ast::InterfaceMember::Setlike(_) => Ok(()), + } + } +} + +impl<'a> WebidlParse<'a> for webidl::ast::Operation { + type Extra = &'a str; + + 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), + // TODO + webidl::ast::Operation::Special(_) + | webidl::ast::Operation::Static(_) + | webidl::ast::Operation::Stringifier(_) => Ok(()), + } + } +} + +fn simple_path_ty(segments: I) -> syn::Type +where + I: IntoIterator, + I::Item: AsRef, +{ + let segments: Vec<_> = segments + .into_iter() + .map(|s| syn::PathSegment { + ident: syn::Ident::new(s.as_ref(), proc_macro2::Span::call_site()), + arguments: syn::PathArguments::None, + }) + .collect(); + + syn::TypePath { + qself: None, + path: syn::Path { + leading_colon: None, + segments: syn::punctuated::Punctuated::from_iter(segments), + }, + }.into() +} + +fn shared_ref(ty: syn::Type) -> syn::Type { + syn::TypeReference { + and_token: Default::default(), + lifetime: None, + mutability: None, + elem: Box::new(ty), + }.into() +} + +fn webidl_ty_to_syn_ty(ty: &webidl::ast::Type) -> Option { + Some(match ty.kind { + // `any` becomes `::wasm_bindgen::JsValue`. + webidl::ast::TypeKind::Any => simple_path_ty(&["wasm_bindgen", "JsValue"]), + + // A reference to a type by name becomes the same thing in the + // bindings. + webidl::ast::TypeKind::Identifier(ref id) => simple_path_ty(Some(id)), + + // Scalars. + webidl::ast::TypeKind::Boolean => simple_path_ty(Some("bool")), + webidl::ast::TypeKind::Byte => simple_path_ty(Some("i8")), + webidl::ast::TypeKind::Octet => simple_path_ty(Some("u8")), + webidl::ast::TypeKind::RestrictedDouble | webidl::ast::TypeKind::UnrestrictedDouble => { + simple_path_ty(Some("f64")) + } + webidl::ast::TypeKind::RestrictedFloat | webidl::ast::TypeKind::UnrestrictedFloat => { + simple_path_ty(Some("f32")) + } + webidl::ast::TypeKind::SignedLong => simple_path_ty(Some("i32")), + webidl::ast::TypeKind::SignedLongLong => simple_path_ty(Some("i64")), + webidl::ast::TypeKind::SignedShort => simple_path_ty(Some("i16")), + webidl::ast::TypeKind::UnsignedLong => simple_path_ty(Some("u32")), + webidl::ast::TypeKind::UnsignedLongLong => simple_path_ty(Some("u64")), + webidl::ast::TypeKind::UnsignedShort => simple_path_ty(Some("u16")), + + // Support for these types is not yet implemented, so skip + // generating any bindings for this function. + webidl::ast::TypeKind::ArrayBuffer + | webidl::ast::TypeKind::ByteString + | webidl::ast::TypeKind::DOMString + | webidl::ast::TypeKind::DataView + | webidl::ast::TypeKind::Error + | webidl::ast::TypeKind::Float32Array + | webidl::ast::TypeKind::Float64Array + | webidl::ast::TypeKind::FrozenArray(_) + | webidl::ast::TypeKind::Int16Array + | webidl::ast::TypeKind::Int32Array + | webidl::ast::TypeKind::Int8Array + | webidl::ast::TypeKind::Object + | webidl::ast::TypeKind::Promise(_) + | webidl::ast::TypeKind::Record(..) + | webidl::ast::TypeKind::Sequence(_) + | webidl::ast::TypeKind::Symbol + | webidl::ast::TypeKind::USVString + | webidl::ast::TypeKind::Uint16Array + | webidl::ast::TypeKind::Uint32Array + | webidl::ast::TypeKind::Uint8Array + | webidl::ast::TypeKind::Uint8ClampedArray + | webidl::ast::TypeKind::Union(_) => { + return None; + } + }) +} + +fn simple_fn_arg(ident: proc_macro2::Ident, ty: syn::Type) -> syn::FnArg { + syn::FnArg::Captured(syn::ArgCaptured { + pat: syn::Pat::Ident(syn::PatIdent { + by_ref: None, + mutability: None, + ident, + subpat: None, + }), + colon_token: Default::default(), + ty, + }) +} + +impl<'a> WebidlParse<'a> for webidl::ast::RegularOperation { + type Extra = &'a str; + + fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { + let fn_name = match self.name { + None => return Ok(()), + Some(ref name) => Ident::new(name, proc_macro2::Span::call_site()), + }; + + let (output, ret) = match self.return_type { + webidl::ast::ReturnType::Void => (syn::ReturnType::Default, None), + webidl::ast::ReturnType::NonVoid(ref ty) => match webidl_ty_to_syn_ty(ty) { + None => return Ok(()), + Some(ty) => ( + syn::ReturnType::Type(Default::default(), Box::new(ty.clone())), + Some(ty), + ), + }, + }; + + let mut inputs = Vec::with_capacity(self.arguments.len() + 1); + let mut arguments = Vec::with_capacity(self.arguments.len() + 1); + + let self_ty = simple_path_ty(Some(self_name)); + let self_ref_ty = shared_ref(self_ty.clone()); + inputs.push(simple_fn_arg( + proc_macro2::Ident::new("self_", proc_macro2::Span::call_site()), + self_ref_ty.clone(), + )); + arguments.push(self_ref_ty); + + for arg in &self.arguments { + if arg.optional || arg.variadic { + // We don't support optional or variadic functions yet; skip + // bindings for this this whole function. + return Ok(()); + } + + match webidl_ty_to_syn_ty(&arg.type_) { + None => return Ok(()), + Some(ty) => { + inputs.push(simple_fn_arg( + proc_macro2::Ident::new(&arg.name, proc_macro2::Span::call_site()), + ty.clone(), + )); + arguments.push(ty); + } + } + } + + let rust_name = fn_name.clone(); + let shim = proc_macro2::Ident::new( + &format!("__wbg_f_{}_{}_{}", fn_name, fn_name, self_name), + proc_macro2::Span::call_site(), + ); + + program.imports.push(backend::ast::Import { + module: None, + version: None, + js_namespace: None, + kind: backend::ast::ImportKind::Function(backend::ast::ImportFunction { + function: backend::ast::Function { + name: fn_name, + arguments, + ret, + opts: backend::ast::BindgenAttrs { + attrs: vec![backend::ast::BindgenAttr::Method], + }, + rust_attrs: vec![], + rust_decl: Box::new(syn::FnDecl { + fn_token: Default::default(), + generics: Default::default(), + paren_token: Default::default(), + inputs: syn::punctuated::Punctuated::from_iter(inputs), + variadic: None, + output, + }), + rust_vis: syn::Visibility::Public(syn::VisPublic { + pub_token: Default::default(), + }), + }, + rust_name, + kind: backend::ast::ImportFunctionKind::Method { + class: self_name.to_string(), + ty: self_ty, + }, + shim, + }), + }); + Ok(()) } } diff --git a/crates/webidl/tests/all/lib.rs b/crates/webidl/tests/all/lib.rs index 522af500..3e95915d 100644 --- a/crates/webidl/tests/all/lib.rs +++ b/crates/webidl/tests/all/lib.rs @@ -8,34 +8,4 @@ extern crate wasm_bindgen_webidl as wb_webidl; mod util; use util::*; -/// Tests for parsing WebIDL into an expected wasm-bindgen AST. -mod parse { - use super::*; - - assert_parse!(empty, backend::ast::Program::default()); - - assert_parse!( - Event, - backend::ast::Program { - exports: vec![], - imports: vec![backend::ast::Import { - module: None, - version: None, - js_namespace: None, - kind: backend::ast::ImportKind::Type(backend::ast::ImportType { - vis: syn::Visibility::Public(syn::VisPublic { - pub_token: Default::default(), - }), - name: syn::Ident::new("Event", proc_macro2::Span::call_site()), - }), - }], - enums: vec![], - structs: vec![], - } - ); -} - -/// Tests for compiling WebIDL into Rust bindings. -mod compile { - assert_compile!(Event); -} +assert_compile!(Event); diff --git a/crates/webidl/tests/all/util.rs b/crates/webidl/tests/all/util.rs index 06f6be1c..40befff8 100644 --- a/crates/webidl/tests/all/util.rs +++ b/crates/webidl/tests/all/util.rs @@ -1,31 +1,9 @@ -use backend; use diff; use std::io::{self, Write}; use std::process; use std::sync::{Once, ONCE_INIT}; use wb_webidl; -pub fn assert_parse(webidl: &str, expected: backend::ast::Program) { - let actual = wb_webidl::parse(webidl).expect("should parse the webidl source OK"); - assert_eq!(expected, actual); -} - -macro_rules! assert_parse { - ($test_name:ident, $expected_ast:expr) => { - #[test] - #[allow(non_snake_case)] - fn $test_name() { - let webidl_source = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/tests/fixtures/", - stringify!($test_name), - ".webidl" - )); - $crate::assert_parse(webidl_source, $expected_ast); - } - }; -} - fn rustfmt>(source: S) -> (String, String) { let source = source.into(); diff --git a/crates/webidl/tests/expected/Event.rs b/crates/webidl/tests/expected/Event.rs index 747f964b..5877bb5b 100644 --- a/crates/webidl/tests/expected/Event.rs +++ b/crates/webidl/tests/expected/Event.rs @@ -53,6 +53,124 @@ impl From for ::wasm_bindgen::JsValue { obj.obj } } +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn __wbindgen_describe___wbg_f_stopPropagation_stopPropagation_Event() { + use wasm_bindgen::describe::*; + inform(FUNCTION); + inform(1u32); + <&Event as WasmDescribe>::describe(); + inform(0); +} +impl Event { + #[allow(bad_style)] + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub extern "C" fn stopPropagation(&self) { + ::wasm_bindgen::__rt::link_this_library(); + #[wasm_import_module = "__wbindgen_placeholder__"] + extern "C" { + fn __wbg_f_stopPropagation_stopPropagation_Event( + self_: <&Event as ::wasm_bindgen::convert::IntoWasmAbi>::Abi, + ) -> (); + } + unsafe { + let _ret = { + let mut __stack = ::wasm_bindgen::convert::GlobalStack::new(); + let self_ = + <&Event as ::wasm_bindgen::convert::IntoWasmAbi>::into_abi(self, &mut __stack); + __wbg_f_stopPropagation_stopPropagation_Event(self_) + }; + () + } + } + #[allow(bad_style, unused_variables)] + #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] + pub extern "C" fn stopPropagation(&self) { + panic!( + "cannot call wasm-bindgen imported functions on \ + non-wasm targets" + ); + } +} +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn __wbindgen_describe___wbg_f_stopImmediatePropagation_stopImmediatePropagation_Event( +) { + use wasm_bindgen::describe::*; + inform(FUNCTION); + inform(1u32); + <&Event as WasmDescribe>::describe(); + inform(0); +} +impl Event { + #[allow(bad_style)] + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub extern "C" fn stopImmediatePropagation(&self) { + ::wasm_bindgen::__rt::link_this_library(); + #[wasm_import_module = "__wbindgen_placeholder__"] + extern "C" { + fn __wbg_f_stopImmediatePropagation_stopImmediatePropagation_Event( + self_: <&Event as ::wasm_bindgen::convert::IntoWasmAbi>::Abi, + ) -> (); + } + unsafe { + let _ret = { + let mut __stack = ::wasm_bindgen::convert::GlobalStack::new(); + let self_ = + <&Event as ::wasm_bindgen::convert::IntoWasmAbi>::into_abi(self, &mut __stack); + __wbg_f_stopImmediatePropagation_stopImmediatePropagation_Event(self_) + }; + () + } + } + #[allow(bad_style, unused_variables)] + #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] + pub extern "C" fn stopImmediatePropagation(&self) { + panic!( + "cannot call wasm-bindgen imported functions on \ + non-wasm targets" + ); + } +} +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn __wbindgen_describe___wbg_f_preventDefault_preventDefault_Event() { + use wasm_bindgen::describe::*; + inform(FUNCTION); + inform(1u32); + <&Event as WasmDescribe>::describe(); + inform(0); +} +impl Event { + #[allow(bad_style)] + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub extern "C" fn preventDefault(&self) { + ::wasm_bindgen::__rt::link_this_library(); + #[wasm_import_module = "__wbindgen_placeholder__"] + extern "C" { + fn __wbg_f_preventDefault_preventDefault_Event( + self_: <&Event as ::wasm_bindgen::convert::IntoWasmAbi>::Abi, + ) -> (); + } + unsafe { + let _ret = { + let mut __stack = ::wasm_bindgen::convert::GlobalStack::new(); + let self_ = + <&Event as ::wasm_bindgen::convert::IntoWasmAbi>::into_abi(self, &mut __stack); + __wbg_f_preventDefault_preventDefault_Event(self_) + }; + () + } + } + #[allow(bad_style, unused_variables)] + #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] + pub extern "C" fn preventDefault(&self) { + panic!( + "cannot call wasm-bindgen imported functions on \ + non-wasm targets" + ); + } +} #[allow(non_upper_case_globals)] #[wasm_custom_section = "__wasm_bindgen_unstable"] const __WASM_BINDGEN_GENERATED_wasm_bindgen_webidl_0_1_0_0 : [ u8 ; 180usize ] = * b"\xB0\0\0\0{\"exports\":[],\"enums\":[],\"imports\":[{\"module\":null,\"version\":null,\"js_namespace\":null,\"kind\":{\"kind\":\"type\"}}],\"structs\":[],\"version\":\"0.2.11 (3879f6f42)\",\"schema_version\":\"4\"}" ; diff --git a/crates/webidl/tests/expected/lib.rs b/crates/webidl/tests/expected/lib.rs index 521c2cad..41e2a7ea 100755 --- a/crates/webidl/tests/expected/lib.rs +++ b/crates/webidl/tests/expected/lib.rs @@ -3,4 +3,4 @@ extern crate wasm_bindgen; -mod Event; +pub mod Event;