mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-20 16:26:33 +00:00
Merge pull request #702 from alexcrichton/dictionaries
Implement support for WebIDL dictionaries
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
@ -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)?)),
|
||||
|
@ -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(())
|
||||
}
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user