Dramatically improving the build time of web-sys (#2012)

* Pre-generating web-sys

* Fixing build errors

* Minor refactor for the unit tests

* Changing to generate #[wasm_bindgen} annotations

* Fixing code generation

* Adding in main bin to wasm-bindgen-webidl

* Fixing more problems

* Adding in support for unstable APIs

* Fixing bug with code generation

* More code generation fixes

* Improving the webidl program

* Removing unnecessary cfg from the generated code

* Splitting doc comments onto separate lines

* Improving the generation for unstable features

* Adding in support for string values in enums

* Now runs rustfmt on the mod.rs file

* Fixing codegen for constructors

* Fixing webidl-tests

* Fixing build errors

* Another fix for build errors

* Renaming typescript_name to typescript_type

* Adding in docs for typescript_type

* Adding in CI script to verify that web-sys is up to date

* Fixing CI script

* Fixing CI script

* Don't suppress git diff output

* Remove duplicate definitions of `Location`

Looks to be a preexisting bug in wasm-bindgen?

* Regenerate webidl

* Try to get the git diff command right

* Handle named constructors in WebIDL

* Remove stray rustfmt.toml

* Add back NamedConstructorBar definition in tests

* Run stable rustfmt over everything

* Don't run Cargo in a build script

Instead refactor things so webidl-tests can use the Rust-code-generation
as a library in a build script. Also fixes `cargo fmt` in the
repository.

* Fixup generated code

* Running web-sys checks on stable

* Improving the code generation a little

* Running rustfmt

Co-authored-by: Alex Crichton <alex@alexcrichton.com>
This commit is contained in:
Pauan
2020-03-03 00:39:36 +01:00
committed by GitHub
parent eb04cf2dda
commit 3f4acc453b
1344 changed files with 142082 additions and 2883 deletions

View File

@ -0,0 +1,86 @@
use lazy_static::lazy_static;
use std::collections::BTreeSet;
use std::iter::FromIterator;
lazy_static! {
pub(crate) static ref BUILTIN_IDENTS: BTreeSet<&'static str> = BTreeSet::from_iter(vec![
"str",
"char",
"bool",
"JsValue",
"u8",
"i8",
"u16",
"i16",
"u32",
"i32",
"u64",
"i64",
"usize",
"isize",
"f32",
"f64",
"Result",
"String",
"Vec",
"Option",
"Array",
"ArrayBuffer",
"Object",
"Promise",
"Function",
"Clamped",
]);
// whitelist a few names that have known polyfills
pub(crate) static ref POLYFILL_INTERFACES: BTreeSet<&'static str> = BTreeSet::from_iter(vec![
"AudioContext",
"OfflineAudioContext",
]);
pub(crate) static ref IMMUTABLE_SLICE_WHITELIST: BTreeSet<&'static str> = BTreeSet::from_iter(vec![
// WebGlRenderingContext, WebGl2RenderingContext
"uniform1fv",
"uniform2fv",
"uniform3fv",
"uniform4fv",
"uniform1iv",
"uniform2iv",
"uniform3iv",
"uniform4iv",
"uniformMatrix2fv",
"uniformMatrix3fv",
"uniformMatrix4fv",
"uniformMatrix2x3fv",
"uniformMatrix2x4fv",
"uniformMatrix3x2fv",
"uniformMatrix3x4fv",
"uniformMatrix4x2fv",
"uniformMatrix4x3fv",
"vertexAttrib1fv",
"vertexAttrib2fv",
"vertexAttrib3fv",
"vertexAttrib4fv",
"bufferData",
"bufferSubData",
"texImage2D",
"texSubImage2D",
"compressedTexImage2D",
// WebGl2RenderingContext
"uniform1uiv",
"uniform2uiv",
"uniform3uiv",
"uniform4uiv",
"texImage3D",
"texSubImage3D",
"compressedTexImage3D",
"clearBufferfv",
"clearBufferiv",
"clearBufferuiv",
// WebSocket
"send",
// TODO: Add another type's functions here. Leave a comment header with the type name
]);
}

View File

@ -10,7 +10,6 @@
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet};
use proc_macro2::Ident;
use weedle;
use weedle::argument::Argument;
use weedle::attribute::*;
@ -28,7 +27,6 @@ use crate::{
/// Collection of constructs that may use partial.
#[derive(Default)]
pub(crate) struct FirstPassRecord<'src> {
pub(crate) builtin_idents: BTreeSet<Ident>,
pub(crate) interfaces: BTreeMap<&'src str, InterfaceData<'src>>,
pub(crate) enums: BTreeMap<&'src str, EnumData<'src>>,
/// The mixins, mapping their name to the webidl ast node for the mixin.
@ -39,7 +37,6 @@ pub(crate) struct FirstPassRecord<'src> {
pub(crate) dictionaries: BTreeMap<&'src str, DictionaryData<'src>>,
pub(crate) callbacks: BTreeSet<&'src str>,
pub(crate) callback_interfaces: BTreeMap<&'src str, CallbackInterfaceData<'src>>,
pub(crate) immutable_slice_whitelist: BTreeSet<&'static str>,
}
pub(crate) struct AttributeInterfaceData<'src> {
@ -104,7 +101,8 @@ pub(crate) struct CallbackInterfaceData<'src> {
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub(crate) enum OperationId<'src> {
Constructor(IgnoreTraits<&'src str>),
Constructor,
NamedConstructor(IgnoreTraits<&'src str>),
/// The name of a function in crates/web-sys/webidls/enabled/*.webidl
///
/// ex: Operation(Some("vertexAttrib1fv"))
@ -392,7 +390,7 @@ fn process_interface_attribute<'src>(
record,
FirstPassOperationType::Interface,
self_name,
&[OperationId::Constructor(IgnoreTraits(self_name))],
&[OperationId::Constructor],
&list.args.body.list,
&return_ty,
&None,
@ -404,7 +402,7 @@ fn process_interface_attribute<'src>(
record,
FirstPassOperationType::Interface,
self_name,
&[OperationId::Constructor(IgnoreTraits(self_name))],
&[OperationId::Constructor],
&[],
&return_ty,
&None,
@ -416,7 +414,7 @@ fn process_interface_attribute<'src>(
record,
FirstPassOperationType::Interface,
self_name,
&[OperationId::Constructor(IgnoreTraits(
&[OperationId::NamedConstructor(IgnoreTraits(
list.rhs_identifier.0,
))],
&list.args.body.list,

View File

@ -0,0 +1,887 @@
use proc_macro2::Literal;
use proc_macro2::TokenStream;
use quote::quote;
use std::collections::BTreeSet;
use syn::{Ident, Type};
use wasm_bindgen_backend::util::{raw_ident, rust_ident};
use crate::constants::{BUILTIN_IDENTS, POLYFILL_INTERFACES};
use crate::traverse::TraverseType;
use crate::util::{get_cfg_features, mdn_doc, required_doc_string, snake_case_ident};
use crate::Options;
fn add_features(features: &mut BTreeSet<String>, ty: &impl TraverseType) {
ty.traverse_type(&mut |ident| {
let ident = ident.to_string();
if !BUILTIN_IDENTS.contains(ident.as_str()) {
features.insert(ident);
}
});
}
fn get_features_doc(options: &Options, name: String) -> Option<String> {
let mut features = BTreeSet::new();
features.insert(name);
required_doc_string(options, &features)
}
fn comment(mut comment: String, features: &Option<String>) -> TokenStream {
if let Some(s) = features {
comment.push_str(s);
}
let lines = comment.lines().map(|doc| quote!( #[doc = #doc] ));
quote! {
#(#lines)*
}
}
fn maybe_unstable_attr(unstable: bool) -> Option<proc_macro2::TokenStream> {
if unstable {
Some(quote! {
#[cfg(web_sys_unstable_apis)]
})
} else {
None
}
}
fn maybe_unstable_docs(unstable: bool) -> Option<proc_macro2::TokenStream> {
if unstable {
Some(quote! {
#[doc = ""]
#[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"]
#[doc = "[described in the `wasm-bindgen` guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html)*"]
})
} else {
None
}
}
fn generate_arguments(arguments: &[(Ident, Type)], variadic: bool) -> Vec<TokenStream> {
arguments
.into_iter()
.enumerate()
.map(|(i, (name, ty))| {
if variadic && i + 1 == arguments.len() {
quote!( #name: &::js_sys::Array )
} else {
quote!( #name: #ty )
}
})
.collect::<Vec<_>>()
}
fn generate_variadic(variadic: bool) -> Option<TokenStream> {
if variadic {
Some(quote!(variadic,))
} else {
None
}
}
pub struct EnumVariant {
pub name: Ident,
pub value: String,
}
impl EnumVariant {
fn generate(&self) -> TokenStream {
let EnumVariant { name, value } = self;
quote!( #name = #value )
}
}
pub struct Enum {
pub name: Ident,
pub variants: Vec<EnumVariant>,
pub unstable: bool,
}
impl Enum {
pub fn generate(&self, options: &Options) -> TokenStream {
let Enum {
name,
variants,
unstable,
} = self;
let unstable_attr = maybe_unstable_attr(*unstable);
let unstable_docs = maybe_unstable_docs(*unstable);
let doc_comment = comment(
format!("The `{}` enum.", name),
&get_features_doc(options, name.to_string()),
);
let variants = variants
.into_iter()
.map(|variant| variant.generate())
.collect::<Vec<_>>();
quote! {
#![allow(unused_imports)]
use wasm_bindgen::prelude::*;
#unstable_attr
#[wasm_bindgen]
#doc_comment
#unstable_docs
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum #name {
#(#variants),*
}
}
}
}
pub enum InterfaceAttributeKind {
Getter,
Setter,
}
pub struct InterfaceAttribute {
pub js_name: String,
pub ty: Type,
pub is_static: bool,
pub structural: bool,
pub catch: bool,
pub kind: InterfaceAttributeKind,
pub unstable: bool,
}
impl InterfaceAttribute {
fn generate(
&self,
options: &Options,
parent_name: &Ident,
parent_js_name: &str,
parents: &[Ident],
) -> TokenStream {
let InterfaceAttribute {
js_name,
ty,
is_static,
structural,
catch,
kind,
unstable,
} = self;
let unstable_attr = maybe_unstable_attr(*unstable);
let unstable_docs = maybe_unstable_docs(*unstable);
let mdn_docs = mdn_doc(parent_js_name, Some(js_name));
let mut features = BTreeSet::new();
add_features(&mut features, ty);
for parent in parents {
features.remove(&parent.to_string());
}
features.remove(&parent_name.to_string());
let cfg_features = get_cfg_features(options, &features);
features.insert(parent_name.to_string());
let doc_comment = required_doc_string(options, &features);
let structural = if *structural {
quote!(structural,)
} else {
quote!(final,)
};
let (method, this) = if *is_static {
(quote!( static_method_of = #parent_name, ), None)
} else {
(quote!(method,), Some(quote!( this: &#parent_name, )))
};
let (prefix, attr, def) = match kind {
InterfaceAttributeKind::Getter => {
let name = rust_ident(&snake_case_ident(js_name));
let ty = if *catch {
quote!( Result<#ty, JsValue> )
} else {
quote!( #ty )
};
(
"Getter",
quote!(getter,),
quote!( pub fn #name(#this) -> #ty; ),
)
}
InterfaceAttributeKind::Setter => {
let name = rust_ident(&format!("set_{}", snake_case_ident(js_name)));
let ret_ty = if *catch {
Some(quote!( -> Result<(), JsValue> ))
} else {
None
};
(
"Setter",
quote!(setter,),
quote!( pub fn #name(#this value: #ty) #ret_ty; ),
)
}
};
let catch = if *catch { Some(quote!(catch,)) } else { None };
let doc_comment = comment(
format!(
"{} for the `{}` field of this object.\n\n{}",
prefix, js_name, mdn_docs
),
&doc_comment,
);
let js_name = raw_ident(js_name);
quote! {
#unstable_attr
#cfg_features
#[wasm_bindgen(
#structural
#catch
#method
#attr
js_class = #parent_js_name,
js_name = #js_name
)]
#doc_comment
#unstable_docs
#def
}
}
}
#[derive(Debug, Clone)]
pub enum InterfaceMethodKind {
Constructor(Option<String>),
Regular,
IndexingGetter,
IndexingSetter,
IndexingDeleter,
}
pub struct InterfaceMethod {
pub name: Ident,
pub js_name: String,
pub arguments: Vec<(Ident, Type)>,
pub ret_ty: Option<Type>,
pub kind: InterfaceMethodKind,
pub is_static: bool,
pub structural: bool,
pub catch: bool,
pub variadic: bool,
pub unstable: bool,
}
impl InterfaceMethod {
fn generate(
&self,
options: &Options,
parent_name: &Ident,
parent_js_name: String,
parents: &[Ident],
) -> TokenStream {
let InterfaceMethod {
name,
js_name,
arguments,
ret_ty,
kind,
is_static,
structural,
catch,
variadic,
unstable,
} = self;
let unstable_attr = maybe_unstable_attr(*unstable);
let unstable_docs = maybe_unstable_docs(*unstable);
let mut is_constructor = false;
let mut extra_args = vec![quote!( js_class = #parent_js_name )];
let doc_comment = match kind {
InterfaceMethodKind::Constructor(name) => {
is_constructor = true;
if let Some(name) = name {
extra_args[0] = quote!( js_class = #name );
}
format!(
"The `new {}(..)` constructor, creating a new \
instance of `{0}`.\n\n{}",
parent_name,
mdn_doc(&parent_js_name, Some(&parent_js_name))
)
}
InterfaceMethodKind::Regular => {
{
let js_name = raw_ident(js_name);
extra_args.push(quote!( js_name = #js_name ));
}
format!(
"The `{}()` method.\n\n{}",
js_name,
mdn_doc(&parent_js_name, Some(js_name))
)
}
InterfaceMethodKind::IndexingGetter => {
extra_args.push(quote!(indexing_getter));
format!("Indexing getter.\n\n")
}
InterfaceMethodKind::IndexingSetter => {
extra_args.push(quote!(indexing_setter));
format!("Indexing setter.\n\n")
}
InterfaceMethodKind::IndexingDeleter => {
extra_args.push(quote!(indexing_deleter));
format!("Indexing deleter.\n\n")
}
};
let mut features = BTreeSet::new();
for (_, ty) in arguments.iter() {
add_features(&mut features, ty);
}
if let Some(ty) = ret_ty {
add_features(&mut features, ty);
}
for parent in parents {
features.remove(&parent.to_string());
}
features.remove(&parent_name.to_string());
let cfg_features = get_cfg_features(options, &features);
features.insert(parent_name.to_string());
let doc_comment = comment(doc_comment, &required_doc_string(options, &features));
let ret = ret_ty.as_ref().map(|ret| quote!( #ret ));
let ret = if *catch {
let ret = ret.unwrap_or_else(|| quote!(()));
Some(quote!( Result<#ret, JsValue> ))
} else {
ret
};
let ret = ret.as_ref().map(|ret| quote!( -> #ret ));
let catch = if *catch { Some(quote!(catch,)) } else { None };
let (method, this) = if is_constructor {
assert!(!is_static);
(quote!(constructor,), None)
} else if *is_static {
(quote!( static_method_of = #parent_name, ), None)
} else {
let structural = if *structural {
quote!(structural)
} else {
quote!(final)
};
(
quote!( method, #structural, ),
Some(quote!( this: &#parent_name, )),
)
};
let arguments = generate_arguments(arguments, *variadic);
let variadic = generate_variadic(*variadic);
quote! {
#unstable_attr
#cfg_features
#[wasm_bindgen(
#catch
#method
#variadic
#(#extra_args),*
)]
#doc_comment
#unstable_docs
pub fn #name(#this #(#arguments),*) #ret;
}
}
}
pub enum InterfaceConstValue {
BooleanLiteral(bool),
FloatLiteral(f64),
SignedIntegerLiteral(i64),
UnsignedIntegerLiteral(u64),
}
impl InterfaceConstValue {
fn generate(&self) -> TokenStream {
use InterfaceConstValue::*;
match self {
BooleanLiteral(false) => quote!(false),
BooleanLiteral(true) => quote!(true),
// the actual type is unknown because of typedefs
// so we cannot use std::fxx::INFINITY
// but we can use type inference
FloatLiteral(f) if f.is_infinite() && f.is_sign_positive() => quote!(1.0 / 0.0),
FloatLiteral(f) if f.is_infinite() && f.is_sign_negative() => quote!(-1.0 / 0.0),
FloatLiteral(f) if f.is_nan() => quote!(0.0 / 0.0),
// again no suffix
// panics on +-inf, nan
FloatLiteral(f) => {
let f = Literal::f64_suffixed(*f);
quote!(#f)
}
SignedIntegerLiteral(i) => {
let i = Literal::i64_suffixed(*i);
quote!(#i)
}
UnsignedIntegerLiteral(i) => {
let i = Literal::u64_suffixed(*i);
quote!(#i)
}
}
}
}
pub struct InterfaceConst {
pub name: Ident,
pub js_name: String,
pub ty: syn::Type,
pub value: InterfaceConstValue,
pub unstable: bool,
}
impl InterfaceConst {
fn generate(
&self,
options: &Options,
parent_name: &Ident,
parent_js_name: &str,
) -> TokenStream {
let name = &self.name;
let ty = &self.ty;
let js_name = &self.js_name;
let value = self.value.generate();
let unstable = self.unstable;
let unstable_attr = maybe_unstable_attr(unstable);
let unstable_docs = maybe_unstable_docs(unstable);
let doc_comment = comment(
format!("The `{}.{}` const.", parent_js_name, js_name),
&get_features_doc(options, parent_name.to_string()),
);
quote! {
#unstable_attr
#doc_comment
#unstable_docs
pub const #name: #ty = #value as #ty;
}
}
}
pub struct Interface {
pub name: Ident,
pub js_name: String,
pub deprecated: Option<String>,
pub has_interface: bool,
pub parents: Vec<Ident>,
pub consts: Vec<InterfaceConst>,
pub attributes: Vec<InterfaceAttribute>,
pub methods: Vec<InterfaceMethod>,
pub unstable: bool,
}
impl Interface {
pub fn generate(&self, options: &Options) -> TokenStream {
let Interface {
name,
js_name,
deprecated,
has_interface,
parents,
consts,
attributes,
methods,
unstable,
} = self;
let unstable_attr = maybe_unstable_attr(*unstable);
let unstable_docs = maybe_unstable_docs(*unstable);
let doc_comment = comment(
format!("The `{}` class.\n\n{}", name, mdn_doc(js_name, None)),
&get_features_doc(options, name.to_string()),
);
let deprecated = deprecated
.as_ref()
.map(|msg| quote!( #[deprecated(note = #msg)] ));
let is_type_of = if *has_interface {
None
} else {
Some(quote!(is_type_of = |_| false,))
};
let prefixes = if POLYFILL_INTERFACES.contains(js_name.as_str()) {
Some(quote!(vendor_prefix = webkit,))
} else {
None
};
let extends = parents
.into_iter()
.map(|x| quote!( extends = #x, ))
.collect::<Vec<_>>();
let consts = consts
.into_iter()
.map(|x| x.generate(options, &name, js_name))
.collect::<Vec<_>>();
let consts = if consts.is_empty() {
None
} else {
Some(quote! {
#unstable_attr
impl #name {
#(#deprecated #consts)*
}
})
};
let attributes = attributes
.into_iter()
.map(|x| x.generate(options, &name, js_name, &parents))
.collect::<Vec<_>>();
let methods = methods
.into_iter()
.map(|x| x.generate(options, &name, js_name.to_string(), &parents))
.collect::<Vec<_>>();
let js_ident = raw_ident(js_name);
quote! {
#![allow(unused_imports)]
use super::*;
use wasm_bindgen::prelude::*;
#unstable_attr
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(
#is_type_of
#prefixes
#(#extends)*
extends = ::js_sys::Object,
js_name = #js_ident,
typescript_type = #js_name
)]
#[derive(Debug, Clone, PartialEq, Eq)]
#doc_comment
#unstable_docs
#deprecated
pub type #name;
#(#deprecated #attributes)*
#(#deprecated #methods)*
}
#consts
}
}
}
pub struct DictionaryField {
pub name: Ident,
pub js_name: String,
pub ty: Type,
pub required: bool,
}
impl DictionaryField {
fn generate_rust(&self, options: &Options, parent_name: String, unstable: bool) -> TokenStream {
let DictionaryField {
name,
js_name,
ty,
required: _,
} = self;
let unstable_attr = maybe_unstable_attr(unstable);
let unstable_docs = maybe_unstable_docs(unstable);
let mut features = BTreeSet::new();
add_features(&mut features, ty);
features.remove(&parent_name);
let cfg_features = get_cfg_features(options, &features);
features.insert(parent_name);
let doc_comment = comment(
format!("Change the `{}` field of this object.", js_name),
&required_doc_string(options, &features),
);
quote! {
#unstable_attr
#cfg_features
#doc_comment
#unstable_docs
pub fn #name(&mut self, val: #ty) -> &mut Self {
use wasm_bindgen::JsValue;
let r = ::js_sys::Reflect::set(
self.as_ref(),
&JsValue::from(#js_name),
&JsValue::from(val),
);
debug_assert!(r.is_ok(), "setting properties should never fail on our dictionary objects");
let _ = r;
self
}
}
}
}
pub struct Dictionary {
pub name: Ident,
pub js_name: String,
pub fields: Vec<DictionaryField>,
pub unstable: bool,
}
impl Dictionary {
pub fn generate(&self, options: &Options) -> TokenStream {
let Dictionary {
name,
js_name,
fields,
unstable,
} = self;
let unstable_attr = maybe_unstable_attr(*unstable);
let unstable_docs = maybe_unstable_docs(*unstable);
let js_name = raw_ident(js_name);
let mut required_features = BTreeSet::new();
let mut required_args = vec![];
let mut required_calls = vec![];
for field in fields.iter() {
if field.required {
let name = &field.name;
let ty = &field.ty;
required_args.push(quote!( #name: #ty ));
required_calls.push(quote!( ret.#name(#name); ));
add_features(&mut required_features, &field.ty);
}
}
required_features.remove(&name.to_string());
let cfg_features = get_cfg_features(options, &required_features);
required_features.insert(name.to_string());
let doc_comment = comment(
format!("The `{}` dictionary.", name),
&get_features_doc(options, name.to_string()),
);
let ctor_doc_comment = comment(
format!("Construct a new `{}`.", name),
&required_doc_string(options, &required_features),
);
let fields = fields
.into_iter()
.map(|field| field.generate_rust(options, name.to_string(), *unstable))
.collect::<Vec<_>>();
quote! {
#![allow(unused_imports)]
use super::*;
use wasm_bindgen::prelude::*;
#unstable_attr
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = ::js_sys::Object, js_name = #js_name)]
#[derive(Debug, Clone, PartialEq, Eq)]
#doc_comment
#unstable_docs
pub type #name;
}
#unstable_attr
impl #name {
#cfg_features
#ctor_doc_comment
#unstable_docs
pub fn new(#(#required_args),*) -> Self {
#[allow(unused_mut)]
let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new());
#(#required_calls)*
ret
}
#(#fields)*
}
}
}
}
pub struct Function {
pub name: Ident,
pub js_name: String,
pub arguments: Vec<(Ident, Type)>,
pub ret_ty: Option<Type>,
pub catch: bool,
pub variadic: bool,
pub unstable: bool,
}
impl Function {
fn generate(
&self,
options: &Options,
parent_name: &Ident,
parent_js_name: String,
) -> TokenStream {
let Function {
name,
js_name,
arguments,
ret_ty,
catch,
variadic,
unstable,
} = self;
let unstable_attr = maybe_unstable_attr(*unstable);
let unstable_docs = maybe_unstable_docs(*unstable);
let js_namespace = raw_ident(&parent_js_name);
let doc_comment = format!(
"The `{}.{}()` function.\n\n{}",
parent_js_name,
js_name,
mdn_doc(&parent_js_name, Some(&js_name))
);
let mut features = BTreeSet::new();
for (_, ty) in arguments.iter() {
add_features(&mut features, ty);
}
if let Some(ty) = ret_ty {
add_features(&mut features, ty);
}
features.remove(&parent_name.to_string());
let cfg_features = get_cfg_features(options, &features);
features.insert(parent_name.to_string());
let doc_comment = comment(doc_comment, &required_doc_string(options, &features));
let ret = ret_ty.as_ref().map(|ret| quote!( #ret ));
let ret = if *catch {
let ret = ret.unwrap_or_else(|| quote!(()));
Some(quote!( Result<#ret, JsValue> ))
} else {
ret
};
let ret = ret.as_ref().map(|ret| quote!( -> #ret ));
let catch = if *catch { Some(quote!(catch,)) } else { None };
let arguments = generate_arguments(arguments, *variadic);
let variadic = generate_variadic(*variadic);
let js_name = raw_ident(js_name);
quote! {
#unstable_attr
#cfg_features
#[wasm_bindgen(
#catch
#variadic
js_namespace = #js_namespace,
js_name = #js_name
)]
#doc_comment
#unstable_docs
pub fn #name(#(#arguments),*) #ret;
}
}
}
pub struct Namespace {
pub name: Ident,
pub js_name: String,
pub functions: Vec<Function>,
}
impl Namespace {
pub fn generate(&self, options: &Options) -> TokenStream {
let Namespace {
name,
js_name,
functions,
} = self;
let functions = functions
.into_iter()
.map(|x| x.generate(options, &name, js_name.to_string()))
.collect::<Vec<_>>();
quote! {
pub mod #name {
#![allow(unused_imports)]
use super::super::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#(#functions)*
}
}
}
}
}

View File

@ -408,6 +408,11 @@ terms_to_idl_type_maybe_immutable! {
Uint8ClampedArray => Uint8ClampedArray
}
#[derive(Debug, Clone)]
pub enum TypeError {
CannotConvert,
}
impl<'a> IdlType<'a> {
/// Generates a snake case type name.
pub(crate) fn push_snake_case_name(&self, dst: &mut String) {
@ -490,7 +495,7 @@ impl<'a> IdlType<'a> {
}
/// Converts to syn type if possible.
pub(crate) fn to_syn_type(&self, pos: TypePosition) -> Option<syn::Type> {
pub(crate) fn to_syn_type(&self, pos: TypePosition) -> Result<Option<syn::Type>, TypeError> {
let anyref = |ty| {
Some(match pos {
TypePosition::Argument => shared_ref(ty, false),
@ -507,13 +512,13 @@ impl<'a> IdlType<'a> {
anyref(leading_colon_path_ty(path))
};
match self {
IdlType::Boolean => Some(ident_ty(raw_ident("bool"))),
IdlType::Byte => Some(ident_ty(raw_ident("i8"))),
IdlType::Octet => Some(ident_ty(raw_ident("u8"))),
IdlType::Short => Some(ident_ty(raw_ident("i16"))),
IdlType::UnsignedShort => Some(ident_ty(raw_ident("u16"))),
IdlType::Long => Some(ident_ty(raw_ident("i32"))),
IdlType::UnsignedLong => Some(ident_ty(raw_ident("u32"))),
IdlType::Boolean => Ok(Some(ident_ty(raw_ident("bool")))),
IdlType::Byte => Ok(Some(ident_ty(raw_ident("i8")))),
IdlType::Octet => Ok(Some(ident_ty(raw_ident("u8")))),
IdlType::Short => Ok(Some(ident_ty(raw_ident("i16")))),
IdlType::UnsignedShort => Ok(Some(ident_ty(raw_ident("u16")))),
IdlType::Long => Ok(Some(ident_ty(raw_ident("i32")))),
IdlType::UnsignedLong => Ok(Some(ident_ty(raw_ident("u32")))),
// Technically these are 64-bit numbers, but we're binding web
// APIs that don't actually have return the corresponding 64-bit
@ -527,74 +532,81 @@ impl<'a> IdlType<'a> {
//
// Perhaps one day we'll bind to u64/i64 here, but we need `BigInt`
// to see more usage!
IdlType::LongLong | IdlType::UnsignedLongLong => Some(ident_ty(raw_ident("f64"))),
IdlType::LongLong | IdlType::UnsignedLongLong => Ok(Some(ident_ty(raw_ident("f64")))),
IdlType::Float => Some(ident_ty(raw_ident("f32"))),
IdlType::UnrestrictedFloat => Some(ident_ty(raw_ident("f32"))),
IdlType::Double => Some(ident_ty(raw_ident("f64"))),
IdlType::UnrestrictedDouble => Some(ident_ty(raw_ident("f64"))),
IdlType::Float => Ok(Some(ident_ty(raw_ident("f32")))),
IdlType::UnrestrictedFloat => Ok(Some(ident_ty(raw_ident("f32")))),
IdlType::Double => Ok(Some(ident_ty(raw_ident("f64")))),
IdlType::UnrestrictedDouble => Ok(Some(ident_ty(raw_ident("f64")))),
IdlType::DomString | IdlType::ByteString | IdlType::UsvString => match pos {
TypePosition::Argument => Some(shared_ref(ident_ty(raw_ident("str")), false)),
TypePosition::Return => Some(ident_ty(raw_ident("String"))),
TypePosition::Argument => Ok(Some(shared_ref(ident_ty(raw_ident("str")), false))),
TypePosition::Return => Ok(Some(ident_ty(raw_ident("String")))),
},
IdlType::Object => js_sys("Object"),
IdlType::Symbol => None,
IdlType::Error => None,
IdlType::Object => Ok(js_sys("Object")),
IdlType::Symbol => Err(TypeError::CannotConvert),
IdlType::Error => Err(TypeError::CannotConvert),
IdlType::ArrayBuffer => js_sys("ArrayBuffer"),
IdlType::DataView => None,
IdlType::Int8Array { immutable } => Some(array("i8", pos, *immutable)),
IdlType::Uint8Array { immutable } => Some(array("u8", pos, *immutable)),
IdlType::Uint8ClampedArray { immutable } => Some(clamped(array("u8", pos, *immutable))),
IdlType::Int16Array { immutable } => Some(array("i16", pos, *immutable)),
IdlType::Uint16Array { immutable } => Some(array("u16", pos, *immutable)),
IdlType::Int32Array { immutable } => Some(array("i32", pos, *immutable)),
IdlType::Uint32Array { immutable } => Some(array("u32", pos, *immutable)),
IdlType::Float32Array { immutable } => Some(array("f32", pos, *immutable)),
IdlType::Float64Array { immutable } => Some(array("f64", pos, *immutable)),
IdlType::ArrayBuffer => Ok(js_sys("ArrayBuffer")),
IdlType::DataView => Err(TypeError::CannotConvert),
IdlType::Int8Array { immutable } => Ok(Some(array("i8", pos, *immutable))),
IdlType::Uint8Array { immutable } => Ok(Some(array("u8", pos, *immutable))),
IdlType::Uint8ClampedArray { immutable } => {
Ok(Some(clamped(array("u8", pos, *immutable))))
}
IdlType::Int16Array { immutable } => Ok(Some(array("i16", pos, *immutable))),
IdlType::Uint16Array { immutable } => Ok(Some(array("u16", pos, *immutable))),
IdlType::Int32Array { immutable } => Ok(Some(array("i32", pos, *immutable))),
IdlType::Uint32Array { immutable } => Ok(Some(array("u32", pos, *immutable))),
IdlType::Float32Array { immutable } => Ok(Some(array("f32", pos, *immutable))),
IdlType::Float64Array { immutable } => Ok(Some(array("f64", pos, *immutable))),
IdlType::ArrayBufferView { .. } | IdlType::BufferSource { .. } => js_sys("Object"),
IdlType::ArrayBufferView { .. } | IdlType::BufferSource { .. } => Ok(js_sys("Object")),
IdlType::Interface(name)
| IdlType::Dictionary(name)
| IdlType::CallbackInterface { name, .. } => {
let ty = ident_ty(rust_ident(camel_case_ident(name).as_str()));
anyref(ty)
Ok(anyref(ty))
}
IdlType::Enum(name) => Some(ident_ty(rust_ident(camel_case_ident(name).as_str()))),
IdlType::Enum(name) => Ok(Some(ident_ty(rust_ident(camel_case_ident(name).as_str())))),
IdlType::Nullable(idl_type) => {
let inner = idl_type.to_syn_type(pos)?;
// TODO: this is a bit of a hack, but `Option<JsValue>` isn't
// supported right now. As a result if we see `JsValue` for our
// inner type, leave that as the same when we create a nullable
// version of that. That way `any?` just becomes `JsValue` and
// it's up to users to dispatch and/or create instances
// appropriately.
if let syn::Type::Path(path) = &inner {
if path.qself.is_none()
&& path
.path
.segments
.last()
.map(|p| p.ident == "JsValue")
.unwrap_or(false)
{
return Some(inner.clone());
}
}
match inner {
Some(inner) => {
// TODO: this is a bit of a hack, but `Option<JsValue>` isn't
// supported right now. As a result if we see `JsValue` for our
// inner type, leave that as the same when we create a nullable
// version of that. That way `any?` just becomes `JsValue` and
// it's up to users to dispatch and/or create instances
// appropriately.
if let syn::Type::Path(path) = &inner {
if path.qself.is_none()
&& path
.path
.segments
.last()
.map(|p| p.ident == "JsValue")
.unwrap_or(false)
{
return Ok(Some(inner.clone()));
}
}
Some(option_ty(inner))
Ok(Some(option_ty(inner)))
}
None => Ok(None),
}
}
IdlType::FrozenArray(_idl_type) => None,
IdlType::FrozenArray(_idl_type) => Err(TypeError::CannotConvert),
// webidl sequences must always be returned as javascript `Array`s. They may accept
// anything implementing the @@iterable interface.
IdlType::Sequence(_idl_type) => match pos {
TypePosition::Argument => js_value,
TypePosition::Return => js_sys("Array"),
TypePosition::Argument => Ok(js_value),
TypePosition::Return => Ok(js_sys("Array")),
},
IdlType::Promise(_idl_type) => js_sys("Promise"),
IdlType::Record(_idl_type_from, _idl_type_to) => None,
IdlType::Promise(_idl_type) => Ok(js_sys("Promise")),
IdlType::Record(_idl_type_from, _idl_type_to) => Err(TypeError::CannotConvert),
IdlType::Union(idl_types) => {
// Note that most union types have already been expanded to
// their components via `flatten`. Unions in a return position
@ -629,10 +641,10 @@ impl<'a> IdlType<'a> {
}
}
IdlType::Any => js_value,
IdlType::Void => None,
IdlType::Callback => js_sys("Function"),
IdlType::UnknownInterface(_) => None,
IdlType::Any => Ok(js_value),
IdlType::Void => Ok(None),
IdlType::Callback => Ok(js_sys("Function")),
IdlType::UnknownInterface(_) => Err(TypeError::CannotConvert),
}
}

File diff suppressed because it is too large Load Diff

41
crates/webidl/src/main.rs Normal file
View File

@ -0,0 +1,41 @@
use anyhow::{Context, Result};
use std::fs;
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(
name = "wasm-bindgen-webidl",
about = "Converts WebIDL into wasm-bindgen compatible code."
)]
struct Opt {
#[structopt(parse(from_os_str))]
input_dir: PathBuf,
#[structopt(parse(from_os_str))]
output_dir: PathBuf,
#[structopt(long)]
no_features: bool,
}
fn main() -> Result<()> {
env_logger::init();
let opt = Opt::from_args();
let features = !opt.no_features;
let generated_features = wasm_bindgen_webidl::generate(
&opt.input_dir,
&opt.output_dir,
wasm_bindgen_webidl::Options { features },
)?;
if features {
fs::write(&"features", generated_features)
.context("writing features to current directory")?;
}
Ok(())
}

View File

@ -0,0 +1,203 @@
use syn::{Ident, Type};
pub trait TraverseType {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident);
}
impl TraverseType for Type {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
match self {
Type::Array(x) => x.traverse_type(f),
Type::BareFn(x) => x.traverse_type(f),
Type::Group(x) => x.traverse_type(f),
Type::Paren(x) => x.traverse_type(f),
Type::Path(x) => x.traverse_type(f),
Type::Ptr(x) => x.traverse_type(f),
Type::Reference(x) => x.traverse_type(f),
Type::Slice(x) => x.traverse_type(f),
Type::Tuple(x) => x.traverse_type(f),
_ => {}
}
}
}
impl TraverseType for syn::TypeArray {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
self.elem.traverse_type(f);
}
}
impl TraverseType for syn::TypeBareFn {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
for input in self.inputs.iter() {
input.ty.traverse_type(f);
}
self.output.traverse_type(f);
}
}
impl TraverseType for syn::ReturnType {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
match self {
Self::Default => {}
Self::Type(_, ty) => {
ty.traverse_type(f);
}
}
}
}
impl TraverseType for syn::TypeGroup {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
self.elem.traverse_type(f);
}
}
impl TraverseType for syn::TypeParen {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
self.elem.traverse_type(f);
}
}
impl TraverseType for syn::TypePath {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
if let Some(qself) = self.qself.as_ref() {
qself.traverse_type(f);
}
if let Some(last) = self.path.segments.last() {
f(&last.ident);
last.arguments.traverse_type(f);
}
}
}
impl TraverseType for syn::QSelf {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
self.ty.traverse_type(f);
}
}
impl TraverseType for syn::PathArguments {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
match self {
Self::None => {}
Self::AngleBracketed(x) => x.traverse_type(f),
Self::Parenthesized(x) => x.traverse_type(f),
}
}
}
impl TraverseType for syn::AngleBracketedGenericArguments {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
for ty in self.args.iter() {
ty.traverse_type(f);
}
}
}
impl TraverseType for syn::GenericArgument {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
match self {
Self::Type(x) => x.traverse_type(f),
Self::Binding(x) => x.traverse_type(f),
_ => {}
}
}
}
impl TraverseType for syn::Binding {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
self.ty.traverse_type(f);
}
}
impl TraverseType for syn::ParenthesizedGenericArguments {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
for ty in self.inputs.iter() {
ty.traverse_type(f);
}
self.output.traverse_type(f);
}
}
impl TraverseType for syn::TypePtr {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
self.elem.traverse_type(f);
}
}
impl TraverseType for syn::TypeReference {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
self.elem.traverse_type(f);
}
}
impl TraverseType for syn::TypeTuple {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
for ty in self.elems.iter() {
ty.traverse_type(f);
}
}
}
impl TraverseType for syn::TypeSlice {
fn traverse_type<F>(&self, f: &mut F)
where
F: FnMut(&Ident),
{
self.elem.traverse_type(f);
}
}

View File

@ -1,17 +1,21 @@
use std::collections::BTreeSet;
use std::iter::FromIterator;
use std::ptr;
use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
use proc_macro2::{Ident, Span};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn;
use wasm_bindgen_backend::ast;
use wasm_bindgen_backend::util::{ident_ty, leading_colon_path_ty, raw_ident, rust_ident};
use wasm_bindgen_backend::util::{ident_ty, raw_ident, rust_ident};
use weedle;
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList, IdentifierOrString};
use weedle::literal::{ConstValue, FloatLit, IntegerLit};
use crate::constants::IMMUTABLE_SLICE_WHITELIST;
use crate::first_pass::{FirstPassRecord, OperationData, OperationId, Signature};
use crate::generator::{InterfaceConstValue, InterfaceMethod, InterfaceMethodKind};
use crate::idl_type::{IdlType, ToIdlType};
use crate::Options;
/// For variadic operations an overload with a `js_sys::Array` argument is generated alongside with
/// `operation_name_0`, `operation_name_1`, `operation_name_2`, ..., `operation_name_n` overloads
@ -81,16 +85,18 @@ pub(crate) fn array(base_ty: &str, pos: TypePosition, immutable: bool) -> syn::T
}
/// Map a webidl const value to the correct wasm-bindgen const value
pub fn webidl_const_v_to_backend_const_v(v: &ConstValue) -> ast::ConstValue {
pub fn webidl_const_v_to_backend_const_v(v: &ConstValue) -> InterfaceConstValue {
use std::f64::{INFINITY, NAN, NEG_INFINITY};
match *v {
ConstValue::Boolean(b) => ast::ConstValue::BooleanLiteral(b.0),
ConstValue::Float(FloatLit::NegInfinity(_)) => ast::ConstValue::FloatLiteral(NEG_INFINITY),
ConstValue::Float(FloatLit::Infinity(_)) => ast::ConstValue::FloatLiteral(INFINITY),
ConstValue::Float(FloatLit::NaN(_)) => ast::ConstValue::FloatLiteral(NAN),
ConstValue::Boolean(b) => InterfaceConstValue::BooleanLiteral(b.0),
ConstValue::Float(FloatLit::NegInfinity(_)) => {
InterfaceConstValue::FloatLiteral(NEG_INFINITY)
}
ConstValue::Float(FloatLit::Infinity(_)) => InterfaceConstValue::FloatLiteral(INFINITY),
ConstValue::Float(FloatLit::NaN(_)) => InterfaceConstValue::FloatLiteral(NAN),
ConstValue::Float(FloatLit::Value(s)) => {
ast::ConstValue::FloatLiteral(s.0.parse().unwrap())
InterfaceConstValue::FloatLiteral(s.0.parse().unwrap())
}
ConstValue::Integer(lit) => {
let mklit = |orig_text: &str, base: u32, offset: usize| {
@ -100,7 +106,7 @@ pub fn webidl_const_v_to_backend_const_v(v: &ConstValue) -> ast::ConstValue {
(false, orig_text)
};
if text == "0" {
return ast::ConstValue::SignedIntegerLiteral(0);
return InterfaceConstValue::SignedIntegerLiteral(0);
}
let text = &text[offset..];
let n = u64::from_str_radix(text, base)
@ -111,9 +117,9 @@ pub fn webidl_const_v_to_backend_const_v(v: &ConstValue) -> ast::ConstValue {
} else {
n.wrapping_neg() as i64
};
ast::ConstValue::SignedIntegerLiteral(n)
InterfaceConstValue::SignedIntegerLiteral(n)
} else {
ast::ConstValue::UnsignedIntegerLiteral(n)
InterfaceConstValue::UnsignedIntegerLiteral(n)
}
};
match lit {
@ -122,55 +128,10 @@ pub fn webidl_const_v_to_backend_const_v(v: &ConstValue) -> ast::ConstValue {
IntegerLit::Dec(h) => mklit(h.0, 10, 0),
}
}
ConstValue::Null(_) => ast::ConstValue::Null,
ConstValue::Null(_) => unimplemented!(),
}
}
/// From `ident` and `Ty`, create `ident: Ty` for use in e.g. `fn(ident: Ty)`.
fn simple_fn_arg(ident: Ident, ty: syn::Type) -> syn::PatType {
syn::PatType {
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: Vec::new(),
by_ref: None,
ident,
mutability: None,
subpat: None,
})),
colon_token: Default::default(),
ty: Box::new(ty),
attrs: Vec::new(),
}
}
/// Create `()`.
fn unit_ty() -> syn::Type {
syn::Type::Tuple(syn::TypeTuple {
paren_token: Default::default(),
elems: syn::punctuated::Punctuated::new(),
})
}
/// From `T` create `Result<T, wasm_bindgen::JsValue>`.
fn result_ty(t: syn::Type) -> syn::Type {
let js_value = leading_colon_path_ty(vec![rust_ident("wasm_bindgen"), rust_ident("JsValue")]);
let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Default::default(),
args: FromIterator::from_iter(vec![
syn::GenericArgument::Type(t),
syn::GenericArgument::Type(js_value),
]),
gt_token: Default::default(),
});
let ident = raw_ident("Result");
let seg = syn::PathSegment { ident, arguments };
let path: syn::Path = seg.into();
let ty = syn::TypePath { qself: None, path };
ty.into()
}
/// From `T` create `[T]`.
pub(crate) fn slice_ty(t: syn::Type) -> syn::Type {
syn::TypeSlice {
@ -220,208 +181,15 @@ pub enum TypePosition {
}
impl<'src> FirstPassRecord<'src> {
pub fn create_one_function<'a>(
&self,
js_name: &str,
rust_name: &str,
idl_arguments: impl Iterator<Item = (&'a str, &'a IdlType<'src>)>,
ret: &IdlType<'src>,
kind: ast::ImportFunctionKind,
structural: bool,
catch: bool,
variadic: bool,
doc_comment: Option<String>,
unstable_api: bool,
) -> Option<ast::ImportFunction>
where
'src: 'a,
{
// Convert all of the arguments from their IDL type to a `syn` type,
// ready to pass to the backend.
//
// Note that for non-static methods we add a `&self` type placeholder,
// but this type isn't actually used so it's just here for show mostly.
let mut arguments = if let &ast::ImportFunctionKind::Method {
ref ty,
kind:
ast::MethodKind::Operation(ast::Operation {
is_static: false, ..
}),
..
} = &kind
{
let mut res = Vec::with_capacity(idl_arguments.size_hint().0 + 1);
res.push(simple_fn_arg(
raw_ident("self_"),
shared_ref(ty.clone(), false),
));
res
} else {
Vec::with_capacity(idl_arguments.size_hint().0)
};
let idl_arguments: Vec<_> = idl_arguments.collect();
let arguments_count = idl_arguments.len();
for (i, (argument_name, idl_type)) in idl_arguments.into_iter().enumerate() {
let syn_type = match idl_type.to_syn_type(TypePosition::Argument) {
Some(t) => t,
None => {
log::warn!(
"Unsupported argument type: {:?} on {:?}",
idl_type,
rust_name
);
return None;
}
};
let syn_type = if variadic && i == arguments_count - 1 {
let path = vec![rust_ident("js_sys"), rust_ident("Array")];
shared_ref(leading_colon_path_ty(path), false)
} else {
syn_type
};
let argument_name = rust_ident(&argument_name.to_snake_case());
arguments.push(simple_fn_arg(argument_name, syn_type));
}
// Convert the return type to a `syn` type, handling the `catch`
// attribute here to use a `Result` in Rust.
let ret = match ret {
IdlType::Void => None,
ret @ _ => match ret.to_syn_type(TypePosition::Return) {
Some(ret) => Some(ret),
None => {
log::warn!("Unsupported return type: {:?} on {:?}", ret, rust_name);
return None;
}
},
};
let js_ret = ret.clone();
let ret = if catch {
Some(ret.map_or_else(|| result_ty(unit_ty()), result_ty))
} else {
ret
};
Some(ast::ImportFunction {
function: ast::Function {
name: js_name.to_string(),
name_span: Span::call_site(),
renamed_via_js_name: false,
arguments,
ret: ret.clone(),
rust_attrs: vec![],
rust_vis: public(),
r#async: false,
},
rust_name: rust_ident(rust_name),
js_ret: js_ret.clone(),
variadic,
catch,
structural,
assert_no_shim: false,
shim: {
let ns = match kind {
ast::ImportFunctionKind::Normal => "",
ast::ImportFunctionKind::Method { ref class, .. } => class,
};
raw_ident(&format!("__widl_f_{}_{}", rust_name, ns))
},
kind,
doc_comment,
unstable_api,
})
}
/// Create a wasm-bindgen getter method, if possible.
pub fn create_getter(
&self,
name: &str,
ty: &weedle::types::Type<'src>,
self_name: &str,
is_static: bool,
attrs: &Option<ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
unstable_api: bool,
) -> Option<ast::ImportFunction> {
let kind = ast::OperationKind::Getter(Some(raw_ident(name)));
let kind = self.import_function_kind(self_name, is_static, kind);
let ret = ty.to_idl_type(self);
self.create_one_function(
&name,
&snake_case_ident(name),
None.into_iter(),
&ret,
kind,
is_structural(attrs.as_ref(), container_attrs),
throws(attrs),
false,
Some(format!(
"The `{}` getter\n\n{}",
name,
mdn_doc(self_name, Some(name))
)),
unstable_api,
)
}
/// Create a wasm-bindgen setter method, if possible.
pub fn create_setter(
&self,
name: &str,
field_ty: &weedle::types::Type<'src>,
self_name: &str,
is_static: bool,
attrs: &Option<ExtendedAttributeList>,
container_attrs: Option<&ExtendedAttributeList>,
unstable_api: bool,
) -> Option<ast::ImportFunction> {
let kind = ast::OperationKind::Setter(Some(raw_ident(name)));
let kind = self.import_function_kind(self_name, is_static, kind);
let field_ty = field_ty.to_idl_type(self);
self.create_one_function(
&name,
&format!("set_{}", name).to_snake_case(),
Some((name, &field_ty)).into_iter(),
&IdlType::Void,
kind,
is_structural(attrs.as_ref(), container_attrs),
throws(attrs),
false,
Some(format!(
"The `{}` setter\n\n{}",
name,
mdn_doc(self_name, Some(name))
)),
unstable_api,
)
}
pub fn import_function_kind(
&self,
self_name: &str,
is_static: bool,
operation_kind: ast::OperationKind,
) -> ast::ImportFunctionKind {
let operation = ast::Operation {
is_static,
kind: operation_kind,
};
let ty = ident_ty(rust_ident(camel_case_ident(&self_name).as_str()));
ast::ImportFunctionKind::Method {
class: self_name.to_string(),
ty,
kind: ast::MethodKind::Operation(operation),
}
}
pub fn create_imports(
&self,
container_attrs: Option<&ExtendedAttributeList<'src>>,
kind: ast::ImportFunctionKind,
id: &OperationId<'src>,
data: &OperationData<'src>,
unstable_api: bool,
) -> Vec<ast::ImportFunction> {
unstable: bool,
) -> Vec<InterfaceMethod> {
let is_static = data.is_static;
// First up, prune all signatures that reference unsupported arguments.
// We won't consider these until said arguments are implemented.
//
@ -499,7 +267,7 @@ impl<'src> FirstPassRecord<'src> {
}
}
let (name, force_structural, force_throws) = match id {
let (name, kind, force_structural, force_throws) = match id {
// Constructors aren't annotated with `[Throws]` extended attributes
// (how could they be, since they themselves are extended
// attributes?) so we must conservatively assume that they can
@ -512,15 +280,29 @@ impl<'src> FirstPassRecord<'src> {
// > value of a type corresponding to the interface the
// > `[Constructor]` extended attribute appears on, **or throw an
// > exception**.
OperationId::Constructor(_) => ("new", false, true),
OperationId::Operation(Some(s)) => (*s, false, false),
OperationId::Constructor => {
("new", InterfaceMethodKind::Constructor(None), false, true)
}
OperationId::NamedConstructor(n) => (
"new",
InterfaceMethodKind::Constructor(Some(n.0.to_string())),
false,
true,
),
OperationId::Operation(Some(s)) => (*s, InterfaceMethodKind::Regular, false, false),
OperationId::Operation(None) => {
log::warn!("unsupported unnamed operation");
return Vec::new();
}
OperationId::IndexingGetter => ("get", true, false),
OperationId::IndexingSetter => ("set", true, false),
OperationId::IndexingDeleter => ("delete", true, false),
OperationId::IndexingGetter => {
("get", InterfaceMethodKind::IndexingGetter, true, false)
}
OperationId::IndexingSetter => {
("set", InterfaceMethodKind::IndexingSetter, true, false)
}
OperationId::IndexingDeleter => {
("delete", InterfaceMethodKind::IndexingDeleter, true, false)
}
};
let mut ret = Vec::new();
@ -610,51 +392,81 @@ impl<'src> FirstPassRecord<'src> {
.last()
.map(|arg| arg.variadic)
.unwrap_or(false);
ret.extend(
self.create_one_function(
name,
&rust_name,
signature
.args
.iter()
.zip(&signature.orig.args)
.map(|(idl_type, orig_arg)| (orig_arg.name, idl_type)),
&ret_ty,
kind.clone(),
structural,
catch,
variadic,
None,
unstable_api,
),
fn idl_arguments<'a>(
args: impl Iterator<Item = (String, &'a IdlType<'a>)>,
) -> Option<Vec<(Ident, syn::Type)>> {
let mut output = vec![];
for (name, idl_type) in args {
let ty = match idl_type.to_syn_type(TypePosition::Argument) {
Ok(ty) => ty.unwrap(),
Err(_) => {
return None;
}
};
output.push((rust_ident(&snake_case_ident(&name[..])), ty));
}
Some(output)
}
let arguments = idl_arguments(
signature
.args
.iter()
.zip(&signature.orig.args)
.map(|(idl_type, orig_arg)| (orig_arg.name.to_string(), idl_type)),
);
if let Some(arguments) = arguments {
if let Ok(ret_ty) = ret_ty.to_syn_type(TypePosition::Return) {
ret.push(InterfaceMethod {
name: rust_ident(&rust_name),
js_name: name.to_string(),
arguments,
ret_ty,
kind: kind.clone(),
is_static,
structural,
catch,
variadic,
unstable,
});
}
}
if !variadic {
continue;
}
let last_idl_type = &signature.args[signature.args.len() - 1];
let last_name = signature.orig.args[signature.args.len() - 1].name;
for i in 0..=MAX_VARIADIC_ARGUMENTS_COUNT {
ret.extend(
self.create_one_function(
name,
&format!("{}_{}", rust_name, i),
signature.args[..signature.args.len() - 1]
.iter()
.zip(&signature.orig.args)
.map(|(idl_type, orig_arg)| (orig_arg.name.to_string(), idl_type))
.chain((1..=i).map(|j| (format!("{}_{}", last_name, j), last_idl_type)))
.collect::<Vec<_>>()
.iter()
.map(|(name, idl_type)| (&name[..], idl_type.clone())),
&ret_ty,
kind.clone(),
structural,
catch,
false,
None,
unstable_api,
),
let arguments = idl_arguments(
signature.args[..signature.args.len() - 1]
.iter()
.zip(&signature.orig.args)
.map(|(idl_type, orig_arg)| (orig_arg.name.to_string(), idl_type))
.chain((1..=i).map(|j| (format!("{}_{}", last_name, j), last_idl_type))),
);
if let Some(arguments) = arguments {
if let Ok(ret_ty) = ret_ty.to_syn_type(TypePosition::Return) {
ret.push(InterfaceMethod {
name: rust_ident(&format!("{}_{}", rust_name, i)),
js_name: name.to_string(),
arguments,
kind: kind.clone(),
ret_ty,
is_static,
structural,
catch,
variadic: false,
unstable,
});
}
}
}
}
return ret;
@ -676,7 +488,7 @@ impl<'src> FirstPassRecord<'src> {
_ => return idl_type,
};
if self.immutable_slice_whitelist.contains(op) {
if IMMUTABLE_SLICE_WHITELIST.contains(op) {
flag_slices_immutable(&mut idl_type)
}
@ -755,13 +567,6 @@ pub fn throws(attrs: &Option<ExtendedAttributeList>) -> bool {
has_named_attribute(attrs.as_ref(), "Throws")
}
/// Create a syn `pub` token
pub fn public() -> syn::Visibility {
syn::Visibility::Public(syn::VisPublic {
pub_token: Default::default(),
})
}
fn flag_slices_immutable(ty: &mut IdlType) {
match ty {
IdlType::Int8Array { immutable }
@ -792,3 +597,39 @@ fn flag_slices_immutable(ty: &mut IdlType) {
_ => {}
}
}
pub fn required_doc_string(options: &Options, features: &BTreeSet<String>) -> Option<String> {
if !options.features || features.len() == 0 {
return None;
}
let list = features
.iter()
.map(|ident| format!("`{}`", ident))
.collect::<Vec<_>>()
.join(", ");
Some(format!(
"\n\n*This API requires the following crate features \
to be activated: {}*",
list,
))
}
pub fn get_cfg_features(options: &Options, features: &BTreeSet<String>) -> Option<syn::Attribute> {
let len = features.len();
if !options.features || len == 0 {
None
} else {
let features = features
.into_iter()
.map(|feature| quote!( feature = #feature, ))
.collect::<TokenStream>();
// This is technically unneeded but it generates more idiomatic code
if len == 1 {
Some(syn::parse_quote!( #[cfg(#features)] ))
} else {
Some(syn::parse_quote!( #[cfg(all(#features))] ))
}
}
}