diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 7e6942303..d8989e35d 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -21,6 +21,10 @@ pub struct Program { pub consts: Vec, /// rust submodules pub modules: Vec, + /// "dictionaries", generated for WebIDL, which are basically just "typed + /// objects" in the sense that they represent a JS object with a particular + /// shape in JIT parlance. + pub dictionaries: Vec, } /// A rust to js interface. Allows interaction with rust objects/functions @@ -253,6 +257,21 @@ pub struct Module { pub imports: Vec, } +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +#[derive(Clone)] +pub struct Dictionary { + pub name: Ident, + pub fields: Vec, +} + +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +#[derive(Clone)] +pub struct DictionaryField { + pub name: Ident, + pub required: bool, + pub ty: syn::Type, +} + impl Program { pub(crate) fn shared(&self) -> Result { Ok(shared::Program { diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 7d8865143..45711ba3f 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -74,6 +74,9 @@ impl TryToTokens for ast::Program { errors.push(e); } } + for d in self.dictionaries.iter() { + d.to_tokens(tokens); + } Diagnostic::from_vec(errors)?; @@ -1135,6 +1138,153 @@ impl<'a> TryToTokens for ast::Module { } } +impl ToTokens for ast::Dictionary { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = &self.name; + let mut methods = TokenStream::new(); + for field in self.fields.iter() { + field.to_tokens(&mut methods); + } + let required_names = &self.fields.iter() + .filter(|f| f.required) + .map(|f| &f.name) + .collect::>(); + let required_types = &self.fields.iter() + .filter(|f| f.required) + .map(|f| &f.ty) + .collect::>(); + let required_names2 = required_names; + let required_names3 = required_names; + + let const_name = Ident::new(&format!("_CONST_{}", name), Span::call_site()); + (quote! { + #[derive(Clone, Debug)] + #[repr(transparent)] + pub struct #name { + obj: ::js_sys::Object, + } + + impl #name { + pub fn new(#(#required_names: #required_types),*) -> #name { + let mut _ret = #name { obj: ::js_sys::Object::new() }; + #(_ret.#required_names2(#required_names3);)* + return _ret + } + + #methods + } + + #[allow(bad_style)] + const #const_name: () = { + use js_sys::Object; + use wasm_bindgen::describe::WasmDescribe; + use wasm_bindgen::convert::*; + use wasm_bindgen::{JsValue, JsCast}; + use wasm_bindgen::__rt::core::mem::ManuallyDrop; + + // interop w/ JsValue + impl From<#name> for JsValue { + fn from(val: #name) -> JsValue { + val.obj.into() + } + } + impl AsRef for #name { + fn as_ref(&self) -> &JsValue { self.obj.as_ref() } + } + impl AsMut for #name { + fn as_mut(&mut self) -> &mut JsValue { self.obj.as_mut() } + } + + // Boundary conversion impls + impl WasmDescribe for #name { + fn describe() { + Object::describe(); + } + } + + impl IntoWasmAbi for #name { + type Abi = ::Abi; + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + self.obj.into_abi(extra) + } + } + + impl<'a> IntoWasmAbi for &'a #name { + type Abi = <&'a Object as IntoWasmAbi>::Abi; + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + (&self.obj).into_abi(extra) + } + } + + impl FromWasmAbi for #name { + type Abi = ::Abi; + unsafe fn from_abi(abi: Self::Abi, extra: &mut Stack) -> Self { + #name { obj: Object::from_abi(abi, extra) } + } + } + + impl OptionIntoWasmAbi for #name { + fn none() -> Self::Abi { Object::none() } + } + impl<'a> OptionIntoWasmAbi for &'a #name { + fn none() -> Self::Abi { <&'a Object>::none() } + } + impl OptionFromWasmAbi for #name { + fn is_none(abi: &Self::Abi) -> bool { Object::is_none(abi) } + } + + impl RefFromWasmAbi for #name { + type Abi = ::Abi; + type Anchor = ManuallyDrop<#name>; + + unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor { + let tmp = ::ref_from_abi(js, extra); + ManuallyDrop::new(#name { + obj: ManuallyDrop::into_inner(tmp), + }) + } + } + + impl JsCast for #name { + fn instanceof(val: &JsValue) -> bool { + Object::instanceof(val) + } + + fn unchecked_from_js(val: JsValue) -> Self { + #name { obj: Object::unchecked_from_js(val) } + } + + fn unchecked_from_js_ref(val: &JsValue) -> &Self { + unsafe { &*(val as *const JsValue as *const #name) } + } + + fn unchecked_from_js_mut(val: &mut JsValue) -> &mut Self { + unsafe { &mut *(val as *mut JsValue as *mut #name) } + } + } + }; + }).to_tokens(tokens); + } +} + +impl ToTokens for ast::DictionaryField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = &self.name; + let ty = &self.ty; + (quote! { + pub fn #name(&mut self, val: #ty) -> &mut Self { + use wasm_bindgen::JsValue; + ::js_sys::Reflect::set( + self.obj.as_ref(), + &JsValue::from(stringify!(#name)), + &JsValue::from(val), + ); + self + } + }).to_tokens(tokens); + } +} + /// 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/backend/src/defined.rs b/crates/backend/src/defined.rs index 82f42c09d..a7f6359e4 100644 --- a/crates/backend/src/defined.rs +++ b/crates/backend/src/defined.rs @@ -84,6 +84,7 @@ impl ImportedTypes for ast::Program { { self.imports.imported_types(f); self.consts.imported_types(f); + self.dictionaries.imported_types(f); } } @@ -298,21 +299,44 @@ impl ImportedTypes for ast::Const { } } +impl ImportedTypes for ast::Dictionary { + fn imported_types(&self, f: &mut F) + where + F: FnMut(&Ident, ImportedTypeKind), + { + f(&self.name, ImportedTypeKind::Definition); + for field in self.fields.iter() { + field.imported_types(f); + } + } +} + +impl ImportedTypes for ast::DictionaryField { + fn imported_types(&self, f: &mut F) + where + F: FnMut(&Ident, ImportedTypeKind), + { + self.ty.imported_types(f); + } +} + /// Remove any methods, statics, &c, that reference types that are *not* /// defined. pub trait RemoveUndefinedImports { - fn remove_undefined_imports(&mut self, is_defined: &F) + fn remove_undefined_imports(&mut self, is_defined: &F) -> bool where F: Fn(&Ident) -> bool; } impl RemoveUndefinedImports for ast::Program { - fn remove_undefined_imports(&mut self, is_defined: &F) + fn remove_undefined_imports(&mut self, is_defined: &F) -> bool where F: Fn(&Ident) -> bool, { - self.imports.remove_undefined_imports(is_defined); - self.consts.remove_undefined_imports(is_defined); + let a = self.imports.remove_undefined_imports(is_defined); + let b = self.consts.remove_undefined_imports(is_defined); + let c = self.dictionaries.remove_undefined_imports(is_defined); + a || b || c } } @@ -320,10 +344,11 @@ impl RemoveUndefinedImports for Vec where T: ImportedTypeReferences, { - fn remove_undefined_imports(&mut self, is_defined: &F) + fn remove_undefined_imports(&mut self, is_defined: &F) -> bool where F: Fn(&Ident) -> bool, { + let before = self.len(); self.retain(|x| { let mut all_defined = true; x.imported_type_references(&mut |id| { @@ -336,5 +361,6 @@ where }); all_defined }); + before != self.len() } } diff --git a/crates/webidl-tests/Cargo.toml b/crates/webidl-tests/Cargo.toml index 99dd82ed6..8c5b2ede8 100644 --- a/crates/webidl-tests/Cargo.toml +++ b/crates/webidl-tests/Cargo.toml @@ -10,6 +10,7 @@ path = 'lib.rs' [build-dependencies] wasm-bindgen-webidl = { path = '../webidl' } +env_logger = "0.5" [dev-dependencies] js-sys = { path = '../js-sys' } @@ -19,4 +20,3 @@ wasm-bindgen-test = { path = '../test' } [[test]] name = 'wasm' path = 'main.rs' - diff --git a/crates/webidl-tests/build.rs b/crates/webidl-tests/build.rs index 909fc8cd1..609efba1b 100644 --- a/crates/webidl-tests/build.rs +++ b/crates/webidl-tests/build.rs @@ -1,4 +1,5 @@ extern crate wasm_bindgen_webidl; +extern crate env_logger; use std::env; use std::fs; @@ -6,6 +7,7 @@ use std::path::PathBuf; use std::process::Command; fn main() { + env_logger::init(); let idls = fs::read_dir(".") .unwrap() .map(|f| f.unwrap().path()) diff --git a/crates/webidl-tests/dictionary.js b/crates/webidl-tests/dictionary.js new file mode 100644 index 000000000..46b2127af --- /dev/null +++ b/crates/webidl-tests/dictionary.js @@ -0,0 +1,22 @@ +const assert = require('assert'); + +global.assert_dict_c = function(c) { + assert.strictEqual(c.a, 1); + assert.strictEqual(c.b, 2); + assert.strictEqual(c.c, 3); + assert.strictEqual(c.d, 4); + assert.strictEqual(c.e, 5); + assert.strictEqual(c.f, 6); + assert.strictEqual(c.g, 7); + assert.strictEqual(c.h, 8); +}; + +global.mk_dict_a = function() { + return {}; +}; + +global.assert_dict_required = function(c) { + assert.strictEqual(c.a, 3); + assert.strictEqual(c.b, "a"); + assert.strictEqual(c.c, 4); +}; diff --git a/crates/webidl-tests/dictionary.rs b/crates/webidl-tests/dictionary.rs new file mode 100644 index 000000000..cf26d5eaf --- /dev/null +++ b/crates/webidl-tests/dictionary.rs @@ -0,0 +1,54 @@ +use wasm_bindgen_test::*; +use wasm_bindgen::prelude::*; + +include!(concat!(env!("OUT_DIR"), "/dictionary.rs")); + +#[wasm_bindgen] +extern { + fn assert_dict_c(c: &C); + #[wasm_bindgen(js_name = assert_dict_c)] + fn assert_dict_c2(c: C); + #[wasm_bindgen(js_name = assert_dict_c)] + fn assert_dict_c3(c: Option<&C>); + #[wasm_bindgen(js_name = assert_dict_c)] + fn assert_dict_c4(c: Option); + fn mk_dict_a() -> A; + #[wasm_bindgen(js_name = mk_dict_a)] + fn mk_dict_a2() -> Option; + fn assert_dict_required(r: &Required); +} + +#[wasm_bindgen_test] +fn smoke() { + A::new().c(1).g(2).h(3).d(4); + B::new().c(1).g(2).h(3).d(4).a(5).b(6); + + let mut c = C::new(); + c.a(1).b(2).c(3).d(4).e(5).f(6).g(7).h(8); + assert_dict_c(&c); + assert_dict_c2(c.clone()); + assert_dict_c3(Some(&c)); + assert_dict_c4(Some(c)); +} + +#[wasm_bindgen_test] +fn get_dict() { + mk_dict_a(); + assert!(mk_dict_a2().is_some()); +} + +#[wasm_bindgen_test] +fn casing() { + CamelCaseMe::new().snake_case_me(3); +} + +#[wasm_bindgen_test] +fn many_types() { + ManyTypes::new() + .a("a"); +} + +#[wasm_bindgen_test] +fn required() { + assert_dict_required(Required::new(3, "a").c(4)); +} diff --git a/crates/webidl-tests/dictionary.webidl b/crates/webidl-tests/dictionary.webidl new file mode 100644 index 000000000..75944104c --- /dev/null +++ b/crates/webidl-tests/dictionary.webidl @@ -0,0 +1,47 @@ +// example from https://heycam.github.io/webidl/#idl-dictionaries +dictionary B : A { + long b; + long a; +}; + +dictionary A { + long c; + long g; +}; + +dictionary C : B { + long e; + long f; +}; + +partial dictionary A { + long h; + long d; +}; + +// case needs changing +dictionary camel_case_me { + long snakeCaseMe; +}; + +dictionary ManyTypes { + DOMString a; + octet n1; + byte n2; + unsigned short n3; + short n4; + unsigned long n5; + long n6; + // TODO: needs fixing + // OtherDict c; +}; + +dictionary OtherDict { + long a; +}; + +dictionary Required { + required DOMString b; + required long a; + long c; +}; diff --git a/crates/webidl-tests/main.rs b/crates/webidl-tests/main.rs index 0bcae6b86..caedcb7b6 100644 --- a/crates/webidl-tests/main.rs +++ b/crates/webidl-tests/main.rs @@ -11,3 +11,4 @@ pub mod enums; pub mod namespace; pub mod simple; pub mod throws; +pub mod dictionary; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 41884ff51..ad93f5752 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -9,6 +9,7 @@ use std::collections::{BTreeMap, BTreeSet}; +use weedle::{DictionaryDefinition, PartialDictionaryDefinition}; use weedle::argument::Argument; use weedle::attribute::ExtendedAttribute; use weedle::interface::{StringifierOrStatic, Special}; @@ -24,13 +25,13 @@ use util::camel_case_ident; #[derive(Default)] pub(crate) struct FirstPassRecord<'src> { pub(crate) interfaces: BTreeMap<&'src str, InterfaceData<'src>>, - pub(crate) dictionaries: BTreeSet<&'src str>, pub(crate) enums: BTreeSet<&'src str>, /// The mixins, mapping their name to the webidl ast node for the mixin. pub(crate) mixins: BTreeMap<&'src str, MixinData<'src>>, pub(crate) typedefs: BTreeMap<&'src str, &'src weedle::types::Type<'src>>, pub(crate) namespaces: BTreeMap<&'src str, NamespaceData<'src>>, pub(crate) includes: BTreeMap<&'src str, BTreeSet<&'src str>>, + pub(crate) dictionaries: BTreeMap<&'src str, DictionaryData<'src>>, } /// We need to collect interface data during the first pass, to be used later. @@ -61,6 +62,13 @@ pub(crate) struct NamespaceData<'src> { pub(crate) operations: BTreeMap, OperationData<'src>>, } +#[derive(Default)] +pub(crate) struct DictionaryData<'src> { + /// Whether only partial namespaces were encountered + pub(crate) partials: Vec<&'src PartialDictionaryDefinition<'src>>, + pub(crate) definition: Option<&'src DictionaryDefinition<'src>>, +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) enum OperationId<'src> { Constructor, @@ -99,6 +107,7 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { match self { Dictionary(dictionary) => dictionary.first_pass(record, ()), + PartialDictionary(dictionary) => dictionary.first_pass(record, ()), Enum(enum_) => enum_.first_pass(record, ()), IncludesStatement(includes) => includes.first_pass(record, ()), Interface(interface) => interface.first_pass(record, ()), @@ -118,14 +127,19 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> { impl<'src> FirstPass<'src, ()> for weedle::DictionaryDefinition<'src> { fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { - if util::is_chrome_only(&self.attributes) { - return Ok(()); - } - - if !record.dictionaries.insert(self.identifier.0) { - info!("Encountered multiple dictionary declarations: {}", self.identifier.0); - } + record.dictionaries.entry(self.identifier.0) + .or_default() + .definition = Some(self); + Ok(()) + } +} +impl<'src> FirstPass<'src, ()> for weedle::PartialDictionaryDefinition<'src> { + fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> { + record.dictionaries.entry(self.identifier.0) + .or_default() + .partials + .push(self); Ok(()) } } @@ -153,7 +167,7 @@ impl<'src> FirstPass<'src, ()> for weedle::IncludesStatementDefinition<'src> { record .includes .entry(self.lhs_identifier.0) - .or_insert_with(Default::default) + .or_default() .insert(self.rhs_identifier.0); Ok(()) @@ -372,7 +386,7 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceMixinDefinition<'src>{ let mixin_data = record .mixins .entry(self.identifier.0) - .or_insert_with(Default::default); + .or_default(); mixin_data.partial = false; mixin_data.members.extend(&self.members.body); } diff --git a/crates/webidl/src/idl_type.rs b/crates/webidl/src/idl_type.rs index ecfe4f540..c1dfa8b05 100644 --- a/crates/webidl/src/idl_type.rs +++ b/crates/webidl/src/idl_type.rs @@ -285,7 +285,7 @@ impl<'a> ToIdlType<'a> for Identifier<'a> { idl_type.to_idl_type(record) } else if record.interfaces.contains_key(self.0) { Some(IdlType::Interface(self.0)) - } else if record.dictionaries.contains(self.0) { + } else if record.dictionaries.contains_key(self.0) { Some(IdlType::Dictionary(self.0)) } else if record.enums.contains(self.0) { Some(IdlType::Enum(self.0)) @@ -467,7 +467,8 @@ impl<'a> IdlType<'a> { IdlType::Float32Array => Some(array("f32", pos)), IdlType::Float64Array => Some(array("f64", pos)), - IdlType::Interface(name) => { + IdlType::Interface(name) | + IdlType::Dictionary(name) => { let ty = ident_ty(rust_ident(camel_case_ident(name).as_str())); if pos == TypePosition::Argument { Some(shared_ref(ty)) @@ -475,7 +476,6 @@ impl<'a> IdlType<'a> { Some(ty) } }, - IdlType::Dictionary(name) => Some(ident_ty(rust_ident(camel_case_ident(name).as_str()))), IdlType::Enum(name) => Some(ident_ty(rust_ident(camel_case_ident(name).as_str()))), IdlType::Nullable(idl_type) => Some(option_ty(idl_type.to_syn_type(pos)?)), diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 22b4d0f7a..7937cc20c 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -35,14 +35,17 @@ use std::io::{self, Read}; use std::iter::FromIterator; use std::path::Path; +use backend::ast; use backend::TryToTokens; use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports}; +use backend::defined::ImportedTypeReferences; use backend::util::{ident_ty, rust_ident, raw_ident, wrap_import_function}; use failure::ResultExt; use heck::{ShoutySnakeCase, SnakeCase}; use proc_macro2::{Ident, Span}; use weedle::argument::Argument; use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList}; +use weedle::dictionary::DictionaryMember; use first_pass::{FirstPass, FirstPassRecord, OperationId}; use util::{public, webidl_const_v_to_backend_const_v, TypePosition, camel_case_ident, mdn_doc}; @@ -113,7 +116,11 @@ pub fn compile(webidl_source: &str) -> Result { /// Run codegen on the AST to generate rust code. fn compile_ast(mut ast: backend::ast::Program) -> String { - let mut defined = BTreeSet::from_iter( + // Iteratively prune all entries from the AST which reference undefined + // fields. Each pass may remove definitions of types and so we need to + // reexecute this pass to see if we need to keep removing types until we + // reach a steady state. + let builtin = BTreeSet::from_iter( vec![ "str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option", @@ -121,10 +128,15 @@ fn compile_ast(mut ast: backend::ast::Program) -> String { ].into_iter() .map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())), ); - ast.imported_type_definitions(&mut |id| { - defined.insert(id.clone()); - }); - ast.remove_undefined_imports(&|id| defined.contains(id)); + loop { + let mut defined = builtin.clone(); + ast.imported_type_definitions(&mut |id| { + defined.insert(id.clone()); + }); + if !ast.remove_undefined_imports(&|id| defined.contains(id)) { + break + } + } let mut tokens = proc_macro2::TokenStream::new(); if let Err(e) = ast.try_to_tokens(&mut tokens) { @@ -179,6 +191,7 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> { | weedle::Definition::InterfaceMixin(_) | weedle::Definition::PartialInterfaceMixin(_) | weedle::Definition::IncludesStatement(..) + | weedle::Definition::PartialDictionary(..) | weedle::Definition::PartialNamespace(..)=> { // handled in the first pass } @@ -188,6 +201,9 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> { weedle::Definition::Namespace(namespace) => { namespace.webidl_parse(program, first_pass, ())? } + weedle::Definition::Dictionary(dict) => { + dict.webidl_parse(program, first_pass, ())? + } // TODO weedle::Definition::Callback(..) => { @@ -196,12 +212,6 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> { weedle::Definition::CallbackInterface(..) => { warn!("Unsupported WebIDL CallbackInterface definition: {:?}", self) } - weedle::Definition::Dictionary(..) => { - warn!("Unsupported WebIDL Dictionary definition: {:?}", self) - } - weedle::Definition::PartialDictionary(..) => { - warn!("Unsupported WebIDL PartialDictionary definition: {:?}", self) - } } Ok(()) } @@ -355,8 +365,8 @@ impl<'src> WebidlParse<'src, &'src weedle::InterfaceDefinition<'src>> for Extend overloaded, same_argument_names, &match first_pass.convert_arguments(arguments) { + Some(arguments) => arguments, None => return, - Some(arguments) => arguments }, IdlType::Interface(interface.identifier.0), kind, @@ -950,3 +960,114 @@ impl<'src> WebidlParse<'src, (&'src str, &'src mut backend::ast::Module)> for we Ok(()) } } + +// tons more data for what's going on here at +// https://www.w3.org/TR/WebIDL-1/#idl-dictionaries +impl<'src> WebidlParse<'src, ()> for weedle::DictionaryDefinition<'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 mut fields = Vec::new(); + if !push_members(first_pass, self.identifier.0, &mut fields) { + return Ok(()) + } + + program.dictionaries.push(ast::Dictionary { + name: rust_ident(&camel_case_ident(self.identifier.0)), + fields, + }); + + return Ok(()); + + fn push_members<'src>( + data: &FirstPassRecord<'src>, + dict: &'src str, + dst: &mut Vec, + ) -> bool { + let dict_data = &data.dictionaries[&dict]; + let definition = dict_data.definition.unwrap(); + + // > The order of the dictionary members on a given dictionary is + // > such that inherited dictionary members are ordered before + // > non-inherited members ... + if let Some(parent) = &definition.inheritance { + if !push_members(data, parent.identifier.0, dst) { + return false + } + } + + // > ... and the dictionary members on the one dictionary + // > definition (including any partial dictionary definitions) are + // > ordered lexicographically by the Unicode codepoints that + // > comprise their identifiers. + let start = dst.len(); + let members = definition.members.body.iter(); + let partials = dict_data.partials.iter().flat_map(|d| &d.members.body); + for member in members.chain(partials) { + match mkfield(data, member) { + Some(f) => dst.push(f), + None => { + warn!( + "unsupported dictionary field {:?}", + (dict, member.identifier.0), + ); + // If this is required then we can't support the + // dictionary at all, but if it's not required we can + // avoid generating bindings for the field and keep + // going otherwise. + if member.required.is_some() { + return false + } + } + } + } + // Note that this sort isn't *quite* right in that it is sorting + // based on snake case instead of the original casing which could + // produce inconsistent results, but should work well enough for + // now! + dst[start..].sort_by_key(|f| f.name.clone()); + + return true + } + + fn mkfield<'src>( + data: &FirstPassRecord<'src>, + field: &'src DictionaryMember<'src>, + ) -> Option { + // use argument position now as we're just binding setters + let ty = field.type_.to_idl_type(data)?.to_syn_type(TypePosition::Argument)?; + + // Slice types aren't supported because they don't implement + // `Into` + if let syn::Type::Reference(ty) = &ty { + match &*ty.elem { + syn::Type::Slice(_) => return None, + _ => {} + } + } + + // Similarly i64/u64 aren't supported because they don't + // implement `Into` + let mut any_64bit = false; + ty.imported_type_references(&mut |i| { + any_64bit = any_64bit || i == "u64" || i == "i64"; + }); + if any_64bit { + return None + } + + Some(ast::DictionaryField { + required: field.required.is_some(), + name: rust_ident(&field.identifier.0.to_snake_case()), + ty, + }) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a42901558..6d0d33f1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -350,6 +350,21 @@ impl From for JsValue { } } +impl<'a, T> From<&'a T> for JsValue where T: JsCast { + fn from(s: &'a T) -> JsValue { + s.as_ref().clone() + } +} + +impl From> for JsValue where JsValue: From { + fn from(s: Option) -> JsValue { + match s { + Some(s) => s.into(), + None => JsValue::undefined(), + } + } +} + impl JsCast for JsValue { // everything is a `JsValue`! fn instanceof(_val: &JsValue) -> bool { true }