Implement support for WebIDL dictionaries

This commit adds support for generating bindings for dictionaries defined in
WebIDL. Dictionaries are associative arrays which are simply objects in JS with
named keys and some values. In Rust given a dictionary like:

    dictionary Foo {
        long field;
    };

we'll generate a struct like:

    pub struct Foo {
        obj: js_sys::Object,
    }

    impl Foo {
        pub fn new() -> Foo { /* make a blank object */ }

        pub fn field(&mut self, val: i32) -> &mut Self {
            // set the field using `js_sys::Reflect`
        }
    }

    // plus a bunch of AsRef, From, and wasm abi impls

At the same time this adds support for partial dictionaries and dictionary
inheritance. All dictionary fields are optional by default and hence only have
builder-style setters, but dictionaries can also have required fields. Required
fields are exposed as arguments to the `new` constructor.

Closes #241
This commit is contained in:
Alex Crichton 2018-08-14 10:16:18 -07:00
parent 13fe2b4aca
commit d6e48195b3
13 changed files with 502 additions and 31 deletions

View File

@ -21,6 +21,10 @@ pub struct Program {
pub consts: Vec<Const>,
/// rust submodules
pub modules: Vec<Module>,
/// "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<Dictionary>,
}
/// A rust to js interface. Allows interaction with rust objects/functions
@ -253,6 +257,21 @@ pub struct Module {
pub imports: Vec<Import>,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)]
pub struct Dictionary {
pub name: Ident,
pub fields: Vec<DictionaryField>,
}
#[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<shared::Program, Diagnostic> {
Ok(shared::Program {

View File

@ -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::<Vec<_>>();
let required_types = &self.fields.iter()
.filter(|f| f.required)
.map(|f| &f.ty)
.collect::<Vec<_>>();
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<JsValue> for #name {
fn as_ref(&self) -> &JsValue { self.obj.as_ref() }
}
impl AsMut<JsValue> 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 = <Object as IntoWasmAbi>::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 = <Object as FromWasmAbi>::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 = <Object as RefFromWasmAbi>::Abi;
type Anchor = ManuallyDrop<#name>;
unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor {
let tmp = <Object as RefFromWasmAbi>::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);

View File

@ -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<F>(&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<F>(&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<F>(&mut self, is_defined: &F)
fn remove_undefined_imports<F>(&mut self, is_defined: &F) -> bool
where
F: Fn(&Ident) -> bool;
}
impl RemoveUndefinedImports for ast::Program {
fn remove_undefined_imports<F>(&mut self, is_defined: &F)
fn remove_undefined_imports<F>(&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<T> RemoveUndefinedImports for Vec<T>
where
T: ImportedTypeReferences,
{
fn remove_undefined_imports<F>(&mut self, is_defined: &F)
fn remove_undefined_imports<F>(&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()
}
}

View File

@ -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'

View File

@ -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())

View File

@ -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);
};

View File

@ -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<C>);
fn mk_dict_a() -> A;
#[wasm_bindgen(js_name = mk_dict_a)]
fn mk_dict_a2() -> Option<A>;
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));
}

47
crates/webidl-tests/dictionary.webidl vendored Normal file
View File

@ -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;
};

View File

@ -11,3 +11,4 @@ pub mod enums;
pub mod namespace;
pub mod simple;
pub mod throws;
pub mod dictionary;

View File

@ -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<OperationId<'src>, 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);
}

View File

@ -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)?)),

View File

@ -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<String> {
/// 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(())
}
@ -353,8 +363,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,
@ -944,3 +954,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<ast::DictionaryField>,
) -> 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<ast::DictionaryField> {
// 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<JsValue>`
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<JsValue>`
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,
})
}
}
}

View File

@ -350,6 +350,21 @@ impl From<bool> for JsValue {
}
}
impl<'a, T> From<&'a T> for JsValue where T: JsCast {
fn from(s: &'a T) -> JsValue {
s.as_ref().clone()
}
}
impl<T> From<Option<T>> for JsValue where JsValue: From<T> {
fn from(s: Option<T>) -> JsValue {
match s {
Some(s) => s.into(),
None => JsValue::undefined(),
}
}
}
impl JsCast for JsValue {
// everything is a `JsValue`!
fn instanceof(_val: &JsValue) -> bool { true }