Merge pull request #702 from alexcrichton/dictionaries

Implement support for WebIDL dictionaries
This commit is contained in:
R. Andrew Ohana 2018-08-16 13:06:06 -07:00 committed by GitHub
commit 25b6f5d982
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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())),
);
loop {
let mut defined = builtin.clone();
ast.imported_type_definitions(&mut |id| {
defined.insert(id.clone());
});
ast.remove_undefined_imports(&|id| defined.contains(id));
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<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 }