Alex Crichton ad34fa29d8 Update with list IR from walrus
This commit updates `wasm-bindgen` to the latest version of `walrus`
which transforms all internal IR representations to a list-based IR
instead of a tree-based IR. This isn't a major change other than
cosmetic for `wasm-bindgen` itself, but involves a lot of changes to the
threads/anyref passes.

This commit also updates our CI configuration to actually run all the
anyref tests on CI. This is done by downloading a nightly build of
node.js which is theorized to continue to be there for awhile until the
full support makes its way into releases.
2019-08-13 11:17:55 -07:00

1718 lines
65 KiB
Rust

//! The polyfill for the WebIDL bindings proposal in wasm-bindgen.
//!
//! This module contains the polyfill (or at least the current state of and as
//! closely as we can match) the WebIDL bindings proposal. The module exports
//! one main function, `process`, which takes a `walrus::Module`. This module is
//! expected to have two items:
//!
//! * First it contains all of the raw wasm-bindgen modules emitted by the Rust
//! compiler. These raw custom sections are extracted, removed, decoded, and
//! handled here. They contain information such as what's exported where,
//! what's imported, comments, etc.
//! * Second, the `descriptors.rs` pass must have run previously to execute all
//! the descriptor functions in the wasm module. Through the synthesized
//! custom section there we learn the type information of all
//! functions/imports/exports in the module.
//!
//! The output of this function is then a new `walrus::Module` with the previous
//! custom sections all removed and two new ones inserted. One is the webidl
//! bindings custom section (or at least a close approximate) and the second is
//! an auxiliary section for wasm-bindgen itself. The goal is for this auxiliary
//! section to eventually be empty or inconsequential, allowing us to emit
//! something that doesn't even need a JS shim one day. For now we're still
//! pretty far away from that, so we'll settle for using webidl bindings as
//! aggressively as possible!
use crate::decode;
use crate::descriptor::{Descriptor, Function};
use crate::descriptors::WasmBindgenDescriptorsSection;
use crate::intrinsic::Intrinsic;
use failure::{bail, format_err, Error};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::str;
use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId};
use wasm_bindgen_shared::struct_function_export_name;
use wasm_webidl_bindings::ast;
const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__";
mod bindings;
mod incoming;
mod outgoing;
pub use self::incoming::NonstandardIncoming;
pub use self::outgoing::NonstandardOutgoing;
/// A nonstandard wasm-bindgen-specific WebIDL custom section.
///
/// This nonstandard section is intended to convey all information that
/// wasm-bindgen itself needs to know about binding functions. This means that
/// it basically uses `NonstandardIncoming` instead of
/// `IncomingBindingExpression` and such. It's also in a bit easier to work with
/// format than the official WebIDL bindings custom section.
///
/// Note that this is intended to be consumed during generation of JS shims and
/// bindings. There it can be transformed, however, into an actual WebIDL
/// binding section using all of the values it has internally.
#[derive(Default, Debug)]
pub struct NonstandardWebidlSection {
/// Store of all WebIDL types. Used currently to store all function types
/// specified in `Bindings`. This is intended to be passed through verbatim
/// to a final WebIDL bindings section.
pub types: ast::WebidlTypes,
/// A mapping from all bound exported functions to the binding that we have
/// listed for them. This is the master list of every binding that will be
/// bound and have a shim generated for it in the wasm module.
pub exports: HashMap<ExportId, Binding>,
/// Similar to `exports` above, except for imports. This will describe all
/// imports from the wasm module to indicate what the JS shim is expected to
/// do.
pub imports: HashMap<ImportId, Binding>,
/// For closures and such we'll be calling entries in the function table
/// with rich arguments (just like we call exports) so to do that we
/// describe all the elem indices that we need to modify here as well.
///
/// This is a list of pairs where the first element in the list is the
/// element index in the function table being described and the `Binding`
/// describes the signature that it's supposed to have.
///
/// The index within this table itself is then used to call actually
/// transformed functions.
pub elems: Vec<(u32, Binding)>,
}
pub type NonstandardWebidlSectionId = TypedCustomSectionId<NonstandardWebidlSection>;
/// A non-standard wasm-bindgen-specifi WebIDL binding. This is meant to vaguely
/// resemble a `FuctionBinding` in the official WebIDL bindings proposal, or at
/// least make it very easy to manufacture an official value from this one.
#[derive(Debug, Clone)]
pub struct Binding {
/// The WebAssembly type that the function is expected to have. Note that
/// this may not match the actual bound function's type! That's because this
/// type includes `anyref` but the Rust compiler never emits anyref. This
/// is, however, used for the `anyref` pass to know what to transform to
/// `anyref`.
pub wasm_ty: walrus::TypeId,
/// The WebIDL type of this binding, which is an index into the webidl
/// binding section's `types` field.
pub webidl_ty: ast::WebidlFunctionId,
/// A list of incoming bindings. For exports this is the list of arguments,
/// and for imports this is the return value.
pub incoming: Vec<NonstandardIncoming>,
/// A list of outgoing bindings. For exports this is the return value and
/// for imports this is the list of arguments.
pub outgoing: Vec<NonstandardOutgoing>,
/// An unfortunate necessity of today's implementation. Ideally WebIDL
/// bindings are used with multi-value support in wasm everywhere, but today
/// few engines support multi-value and LLVM certainly doesn't. Aggregates
/// are then always returned through an out-ptr, so this indicates that if
/// an out-ptr is present what wasm types are being transmitted through it.
pub return_via_outptr: Option<Vec<walrus::ValType>>,
}
impl Binding {
/// Does this binding's wasm function signature have any `anyref`s?
pub fn contains_anyref(&self, module: &walrus::Module) -> bool {
let ty = module.types.get(self.wasm_ty);
ty.params()
.iter()
.chain(ty.results())
.any(|ty| *ty == walrus::ValType::Anyref)
}
}
/// A synthetic custom section which is not standardized, never will be, and
/// cannot be serialized or parsed. This is synthesized from all of the
/// compiler-emitted wasm-bindgen sections and then immediately removed to be
/// processed in the JS generation pass.
#[derive(Default, Debug)]
pub struct WasmBindgenAux {
/// Extra typescript annotations that should be appended to the generated
/// TypeScript file. This is provided via a custom attribute in Rust code.
pub extra_typescript: String,
/// A map from identifier to the contents of each local module defined via
/// the `#[wasm_bindgen(module = "/foo.js")]` import options.
pub local_modules: HashMap<String, String>,
/// A map from unique crate identifier to the list of inline JS snippets for
/// that crate identifier.
pub snippets: HashMap<String, Vec<String>>,
/// A list of all `package.json` files that are intended to be included in
/// the final build.
pub package_jsons: HashSet<PathBuf>,
/// A map from exported function id to where it's expected to be exported
/// to.
pub export_map: HashMap<ExportId, AuxExport>,
/// A map from imported function id to what it's expected to import.
pub import_map: HashMap<ImportId, AuxImport>,
/// Small bits of metadata about imports.
pub imports_with_catch: HashSet<ImportId>,
pub imports_with_variadic: HashSet<ImportId>,
pub imports_with_assert_no_shim: HashSet<ImportId>,
/// Auxiliary information to go into JS/TypeScript bindings describing the
/// exported enums from Rust.
pub enums: Vec<AuxEnum>,
/// Auxiliary information to go into JS/TypeScript bindings describing the
/// exported structs from Rust and their fields they've got exported.
pub structs: Vec<AuxStruct>,
}
pub type WasmBindgenAuxId = TypedCustomSectionId<WasmBindgenAux>;
#[derive(Debug)]
pub struct AuxExport {
/// When generating errors about this export, a helpful name to remember it
/// by.
pub debug_name: String,
/// Comments parsed in Rust and forwarded here to show up in JS bindings.
pub comments: String,
/// Argument names in Rust forwarded here to configure the names that show
/// up in TypeScript bindings.
pub arg_names: Option<Vec<String>>,
/// What kind of function this is and where it shows up
pub kind: AuxExportKind,
}
/// All possible kinds of exports from a wasm module.
///
/// This `enum` says where to place an exported wasm function. For example it
/// may want to get hooked up to a JS class, or it may want to be exported as a
/// free function (etc).
///
/// TODO: it feels like this should not really be here per se. We probably want
/// to either construct the JS object itself from within wasm or somehow move
/// more of this information into some other section. Really what this is is
/// sort of an "export map" saying how to wire up all the free functions from
/// the wasm module into the output expected JS module. All our functions here
/// currently take integer parameters and require a JS wrapper, but ideally
/// we'd change them one day to taking/receiving `anyref` which then use some
/// sort of webidl import to customize behavior or something like that. In any
/// case this doesn't feel quite right in terms of priviledge separation, so
/// we'll want to work on this. For now though it works.
#[derive(Debug)]
pub enum AuxExportKind {
/// A free function that's just listed on the exported module
Function(String),
/// A function that's used to create an instane of a class. The function
/// actually return just an integer which is put on an JS object currently.
Constructor(String),
/// This function is intended to be a getter for a field on a class. The
/// first argument is the internal pointer and the returned value is
/// expected to be the field.
Getter { class: String, field: String },
/// This function is intended to be a setter for a field on a class. The
/// first argument is the internal pointer and the second argument is
/// expected to be the field's new value.
Setter { class: String, field: String },
/// This is a free function (ish) but scoped inside of a class name.
StaticFunction { class: String, name: String },
/// This is a member function of a class where the first parameter is the
/// implicit integer stored in the class instance.
Method {
class: String,
name: String,
/// Whether or not this is calling a by-value method in Rust and should
/// clear the internal pointer in JS automatically.
consumed: bool,
},
}
#[derive(Debug)]
pub struct AuxEnum {
/// The name of this enum
pub name: String,
/// The copied Rust comments to forward to JS
pub comments: String,
/// A list of variants with their name and value
pub variants: Vec<(String, u32)>,
}
#[derive(Debug)]
pub struct AuxStruct {
/// The name of this struct
pub name: String,
/// The copied Rust comments to forward to JS
pub comments: String,
}
/// All possible types of imports that can be imported by a wasm module.
///
/// This `enum` is intended to map out what an imported value is. For example
/// this contains a ton of shims and various ways you can call a function. The
/// base variant here is `Value` which simply means "hook this up to the import"
/// and the signatures will match up.
///
/// Note that this is *not* the same as the webidl bindings section. This is
/// intended to be coupled with that to map out what actually gets hooked up to
/// an import in the wasm module. The two work in tandem.
///
/// Some of these items here are native to JS (like `Value`, indexing
/// operations, etc). Others are shims generated by wasm-bindgen (like `Closure`
/// or `Instanceof`).
#[derive(Debug)]
pub enum AuxImport {
/// This import is expected to simply be whatever is the value that's
/// imported
Value(AuxValue),
/// This import is expected to be a function that takes an `anyref` and
/// returns a `bool`. It's expected that it tests if the argument is an
/// instance of (using `instanceof`) the name specified.
///
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
/// of import here?
Instanceof(JsImport),
/// This import is expected to be a shim that returns the JS value named by
/// `JsImport`.
Static(JsImport),
/// This import is intended to manufacture a JS closure with the given
/// signature and then return that back to Rust.
Closure {
mutable: bool, // whether or not this was a `FnMut` closure
dtor: u32, // table element index of the destructor function
binding_idx: u32,
nargs: usize,
},
/// This import is expected to be a shim that simply calls the `foo` method
/// on the first object, passing along all other parameters and returning
/// the resulting value.
StructuralMethod(String),
/// This import is a "structural getter" which simply returns the `.field`
/// value of the first argument as an object.
///
/// e.g. `function(x) { return x.foo; }`
StructuralGetter(String),
/// This import is a "structural getter" which simply returns the `.field`
/// value of the specified class
///
/// e.g. `function() { return TheClass.foo; }`
StructuralClassGetter(JsImport, String),
/// This import is a "structural setter" which simply sets the `.field`
/// value of the first argument to the second argument.
///
/// e.g. `function(x, y) { x.foo = y; }`
StructuralSetter(String),
/// This import is a "structural setter" which simply sets the `.field`
/// value of the specified class to the first argument of the function.
///
/// e.g. `function(x) { TheClass.foo = x; }`
StructuralClassSetter(JsImport, String),
/// This import is expected to be a shim that is an indexing getter of the
/// JS class here, where the first argument of the function is the field to
/// look up. The return value is the value of the field.
///
/// e.g. `function(x) { return TheClass[x]; }`
///
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
/// of import here?
IndexingGetterOfClass(JsImport),
/// This import is expected to be a shim that is an indexing getter of the
/// first argument interpreted as an object where the field to look up is
/// the second argument.
///
/// e.g. `function(x, y) { return x[y]; }`
///
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
/// of import here?
IndexingGetterOfObject,
/// This import is expected to be a shim that is an indexing setter of the
/// JS class here, where the first argument of the function is the field to
/// set and the second is the value to set it to.
///
/// e.g. `function(x, y) { TheClass[x] = y; }`
///
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
/// of import here?
IndexingSetterOfClass(JsImport),
/// This import is expected to be a shim that is an indexing setter of the
/// first argument interpreted as an object where the next two fields are
/// the field to set and the value to set it to.
///
/// e.g. `function(x, y, z) { x[y] = z; }`
///
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
/// of import here?
IndexingSetterOfObject,
/// This import is expected to be a shim that is an indexing deleter of the
/// JS class here, where the first argument of the function is the field to
/// delete.
///
/// e.g. `function(x) { delete TheClass[x]; }`
///
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
/// of import here?
IndexingDeleterOfClass(JsImport),
/// This import is expected to be a shim that is an indexing deleter of the
/// first argument interpreted as an object where the second argument is
/// the field to delete.
///
/// e.g. `function(x, y) { delete x[y]; }`
///
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
/// of import here?
IndexingDeleterOfObject,
/// This import is a generated shim which will wrap the provided pointer in
/// a JS object corresponding to the Class name given here. The class name
/// is one that is exported from the Rust/wasm.
///
/// TODO: sort of like the export map below we should ideally create the
/// `anyref` from within Rust itself and then return it directly rather than
/// requiring an intrinsic here to do so.
WrapInExportedClass(String),
/// This is an intrinsic function expected to be implemented with a JS glue
/// shim. Each intrinsic has its own expected signature and implementation.
Intrinsic(Intrinsic),
}
/// Values that can be imported verbatim to hook up to an import.
#[derive(Debug)]
pub enum AuxValue {
/// A bare JS value, no transformations, just put it in the slot.
Bare(JsImport),
/// A getter function for the class listed for the field, acquired using
/// `getOwnPropertyDescriptor`.
Getter(JsImport, String),
/// Like `Getter`, but accesses a field of a class instead of an instance
/// of the class.
ClassGetter(JsImport, String),
/// Like `Getter`, except the `set` property.
Setter(JsImport, String),
/// Like `Setter`, but for class fields instead of instance fields.
ClassSetter(JsImport, String),
}
/// What can actually be imported and typically a value in each of the variants
/// above of `AuxImport`
///
/// A `JsImport` is intended to indicate what exactly is being imported for a
/// particular operation.
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub struct JsImport {
/// The base of whatever is being imported, either from a module, the global
/// namespace, or similar.
pub name: JsImportName,
/// Various field accesses (like `.foo.bar.baz`) to hang off the `name`
/// above.
pub fields: Vec<String>,
}
/// Return value of `determine_import` which is where we look at an imported
/// function AST and figure out where it's actually being imported from
/// (performing some validation checks and whatnot).
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub enum JsImportName {
/// An item is imported from the global scope. The `name` is what's
/// imported.
Global { name: String },
/// Same as `Global`, except the `name` is imported via an ESM import from
/// the specified `module` path.
Module { module: String, name: String },
/// Same as `Module`, except we're importing from a local module defined in
/// a local JS snippet.
LocalModule { module: String, name: String },
/// Same as `Module`, except we're importing from an `inline_js` attribute
InlineJs {
unique_crate_identifier: String,
snippet_idx_in_crate: usize,
name: String,
},
/// A global import which may have a number of vendor prefixes associated
/// with it, like `webkitAudioPrefix`. The `name` is the name to test
/// whether it's prefixed.
VendorPrefixed { name: String, prefixes: Vec<String> },
}
struct Context<'a> {
start_found: bool,
module: &'a mut Module,
bindings: NonstandardWebidlSection,
aux: WasmBindgenAux,
function_exports: HashMap<String, (ExportId, FunctionId)>,
function_imports: HashMap<String, (ImportId, FunctionId)>,
vendor_prefixes: HashMap<String, Vec<String>>,
unique_crate_identifier: &'a str,
descriptors: HashMap<String, Descriptor>,
anyref_enabled: bool,
support_start: bool,
}
pub fn process(
module: &mut Module,
anyref_enabled: bool,
support_start: bool,
) -> Result<(NonstandardWebidlSectionId, WasmBindgenAuxId), Error> {
let mut storage = Vec::new();
let programs = extract_programs(module, &mut storage)?;
let mut cx = Context {
bindings: Default::default(),
aux: Default::default(),
function_exports: Default::default(),
function_imports: Default::default(),
vendor_prefixes: Default::default(),
descriptors: Default::default(),
unique_crate_identifier: "",
module,
start_found: false,
anyref_enabled,
support_start,
};
cx.init()?;
for program in programs {
cx.program(program)?;
}
if let Some(standard) = cx.module.customs.delete_typed::<ast::WebidlBindings>() {
cx.standard(&standard)?;
}
cx.verify()?;
let bindings = cx.module.customs.add(cx.bindings);
let aux = cx.module.customs.add(cx.aux);
Ok((bindings, aux))
}
impl<'a> Context<'a> {
fn init(&mut self) -> Result<(), Error> {
// Make a map from string name to ids of all exports
for export in self.module.exports.iter() {
if let walrus::ExportItem::Function(f) = export.item {
self.function_exports
.insert(export.name.clone(), (export.id(), f));
}
}
// Make a map from string name to ids of all imports from our
// placeholder module name which we'll want to be sure that we've got a
// location listed of what to import there for each item.
let mut intrinsics = Vec::new();
for import in self.module.imports.iter() {
if import.module != PLACEHOLDER_MODULE {
continue;
}
if let walrus::ImportKind::Function(f) = import.kind {
self.function_imports
.insert(import.name.clone(), (import.id(), f));
if let Some(intrinsic) = Intrinsic::from_symbol(&import.name) {
intrinsics.push((import.id(), intrinsic));
}
}
}
for (id, intrinsic) in intrinsics {
self.bind_intrinsic(id, intrinsic)?;
}
self.inject_anyref_initialization()?;
if let Some(custom) = self
.module
.customs
.delete_typed::<WasmBindgenDescriptorsSection>()
{
let WasmBindgenDescriptorsSection {
descriptors,
closure_imports,
} = *custom;
// Store all the executed descriptors in our own field so we have
// access to them while processing programs.
self.descriptors.extend(descriptors);
// Register all the injected closure imports as that they're expected
// to manufacture a particular type of closure.
//
// First we register the imported function shim which returns a
// `JsValue` for the closure. We manufacture this signature's
// binding since it's not listed anywhere.
//
// Next we register the corresponding table element's binding in
// the webidl bindings section. This binding will later be used to
// generate a shim (if necessary) for the table element.
//
// Finally we store all this metadata in the import map which we've
// learned so when a binding for the import is generated we can
// generate all the appropriate shims.
for (id, descriptor) in closure_imports {
let binding = Function {
shim_idx: 0,
arguments: vec![Descriptor::I32; 3],
ret: Descriptor::Anyref,
};
bindings::register_import(
self.module,
&mut self.bindings,
id,
binding,
ast::WebidlFunctionKind::Static,
)?;
// Synthesize the two integer pointers we pass through which
// aren't present in the signature but are present in the wasm
// signature.
let mut function = descriptor.function.clone();
let nargs = function.arguments.len();
function.arguments.insert(0, Descriptor::I32);
function.arguments.insert(0, Descriptor::I32);
let binding_idx = bindings::register_table_element(
self.module,
&mut self.bindings,
descriptor.shim_idx,
function,
)?;
self.aux.import_map.insert(
id,
AuxImport::Closure {
dtor: descriptor.dtor_idx,
mutable: descriptor.mutable,
binding_idx,
nargs,
},
);
}
}
Ok(())
}
// Ensure that the `start` function for this module calls the
// `__wbindgen_init_anyref_table` function. This'll ensure that all
// instances of this module have the initial slots of the anyref table
// initialized correctly.
fn inject_anyref_initialization(&mut self) -> Result<(), Error> {
if !self.anyref_enabled {
return Ok(());
}
// Make sure to export the `anyref` table for the JS bindings since it
// will need to be initialized. If it doesn't exist though then the
// module must not use it, so we skip it.
let table = self.module.tables.iter().find(|t| match t.kind {
walrus::TableKind::Anyref(_) => true,
_ => false,
});
let table = match table {
Some(t) => t.id(),
None => return Ok(()),
};
self.module.exports.add("__wbg_anyref_table", table);
let ty = self.module.types.add(&[], &[]);
let (import, import_id) =
self.module
.add_import_func(PLACEHOLDER_MODULE, "__wbindgen_init_anyref_table", ty);
self.module.start = Some(match self.module.start {
Some(prev_start) => {
let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]);
builder.func_body().call(import).call(prev_start);
builder.finish(Vec::new(), &mut self.module.funcs)
}
None => import,
});
self.bind_intrinsic(import_id, Intrinsic::InitAnyrefTable)?;
Ok(())
}
fn bind_intrinsic(&mut self, id: ImportId, intrinsic: Intrinsic) -> Result<(), Error> {
bindings::register_import(
self.module,
&mut self.bindings,
id,
intrinsic.binding(),
ast::WebidlFunctionKind::Static,
)?;
self.aux
.import_map
.insert(id, AuxImport::Intrinsic(intrinsic));
Ok(())
}
fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> {
self.unique_crate_identifier = program.unique_crate_identifier;
let decode::Program {
exports,
enums,
imports,
structs,
typescript_custom_sections,
local_modules,
inline_js,
unique_crate_identifier,
package_json,
} = program;
for module in local_modules {
// All local modules we find should be unique, but the same module
// may have showed up in a few different blocks. If that's the case
// all the same identifiers should have the same contents.
if let Some(prev) = self
.aux
.local_modules
.insert(module.identifier.to_string(), module.contents.to_string())
{
assert_eq!(prev, module.contents);
}
}
if let Some(s) = package_json {
self.aux.package_jsons.insert(s.into());
}
for export in exports {
self.export(export)?;
}
// Register vendor prefixes for all types before we walk over all the
// imports to ensure that if a vendor prefix is listed somewhere it'll
// apply to all the imports.
for import in imports.iter() {
if let decode::ImportKind::Type(ty) = &import.kind {
if ty.vendor_prefixes.len() == 0 {
continue;
}
self.vendor_prefixes
.entry(ty.name.to_string())
.or_insert(Vec::new())
.extend(ty.vendor_prefixes.iter().map(|s| s.to_string()));
}
}
for import in imports {
self.import(import)?;
}
for enum_ in enums {
self.enum_(enum_)?;
}
for struct_ in structs {
self.struct_(struct_)?;
}
for section in typescript_custom_sections {
self.aux.extra_typescript.push_str(section);
self.aux.extra_typescript.push_str("\n\n");
}
self.aux
.snippets
.entry(unique_crate_identifier.to_string())
.or_insert(Vec::new())
.extend(inline_js.iter().map(|s| s.to_string()));
Ok(())
}
fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> {
let wasm_name = match &export.class {
Some(class) => struct_function_export_name(class, export.function.name),
None => export.function.name.to_string(),
};
let mut descriptor = match self.descriptors.remove(&wasm_name) {
None => return Ok(()),
Some(d) => d.unwrap_function(),
};
let (export_id, id) = self.function_exports[&wasm_name];
if export.start {
self.add_start_function(id)?;
}
let kind = match export.class {
Some(class) => {
let class = class.to_string();
match export.method_kind {
decode::MethodKind::Constructor => AuxExportKind::Constructor(class),
decode::MethodKind::Operation(op) => match op.kind {
decode::OperationKind::Getter(f) => {
descriptor.arguments.insert(0, Descriptor::I32);
AuxExportKind::Getter {
class,
field: f.to_string(),
}
}
decode::OperationKind::Setter(f) => {
descriptor.arguments.insert(0, Descriptor::I32);
AuxExportKind::Setter {
class,
field: f.to_string(),
}
}
_ if op.is_static => AuxExportKind::StaticFunction {
class,
name: export.function.name.to_string(),
},
_ => {
descriptor.arguments.insert(0, Descriptor::I32);
AuxExportKind::Method {
class,
name: export.function.name.to_string(),
consumed: export.consumed,
}
}
},
}
}
None => AuxExportKind::Function(export.function.name.to_string()),
};
self.aux.export_map.insert(
export_id,
AuxExport {
debug_name: wasm_name,
comments: concatenate_comments(&export.comments),
arg_names: Some(export.function.arg_names),
kind,
},
);
bindings::register_export(self.module, &mut self.bindings, export_id, descriptor)?;
Ok(())
}
fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> {
if self.start_found {
bail!("cannot specify two `start` functions");
}
self.start_found = true;
// Skip this when we're generating tests
if !self.support_start {
return Ok(());
}
let prev_start = match self.module.start {
Some(f) => f,
None => {
self.module.start = Some(id);
return Ok(());
}
};
// Note that we call the previous start function, if any, first. This is
// because the start function currently only shows up when it's injected
// through thread/anyref transforms. These injected start functions need
// to happen before user code, so we always schedule them first.
let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]);
builder.func_body().call(prev_start).call(id);
let new_start = builder.finish(Vec::new(), &mut self.module.funcs);
self.module.start = Some(new_start);
Ok(())
}
fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
match &import.kind {
decode::ImportKind::Function(f) => self.import_function(&import, f),
decode::ImportKind::Static(s) => self.import_static(&import, s),
decode::ImportKind::Type(t) => self.import_type(&import, t),
decode::ImportKind::Enum(_) => Ok(()),
}
}
fn import_function(
&mut self,
import: &decode::Import<'_>,
function: &decode::ImportFunction<'_>,
) -> Result<(), Error> {
let decode::ImportFunction {
shim,
catch,
variadic,
method,
structural,
function,
assert_no_shim,
} = function;
let (import_id, _id) = match self.function_imports.get(*shim) {
Some(pair) => *pair,
None => return Ok(()),
};
let descriptor = match self.descriptors.remove(*shim) {
None => return Ok(()),
Some(d) => d.unwrap_function(),
};
// Record this for later as it affects JS binding generation, but note
// that this doesn't affect the WebIDL interface at all.
if *variadic {
self.aux.imports_with_variadic.insert(import_id);
}
if *catch {
self.aux.imports_with_catch.insert(import_id);
}
if *assert_no_shim {
self.aux.imports_with_assert_no_shim.insert(import_id);
}
// Perform two functions here. First we're saving off our WebIDL
// bindings signature, indicating what we think our import is going to
// be. Next we're saving off other metadata indicating where this item
// is going to be imported from. The `import_map` table will record, for
// each import, what is getting hooked up to that slot of the import
// table to the WebAssembly instance.
let import = match method {
Some(data) => {
let class = self.determine_import(import, &data.class)?;
match &data.kind {
// NB: `structural` is ignored for constructors since the
// js type isn't expected to change anyway.
decode::MethodKind::Constructor => {
bindings::register_import(
self.module,
&mut self.bindings,
import_id,
descriptor,
ast::WebidlFunctionKind::Constructor,
)?;
AuxImport::Value(AuxValue::Bare(class))
}
decode::MethodKind::Operation(op) => {
let (import, method) =
self.determine_import_op(class, function, *structural, op)?;
let kind = if method {
let kind = ast::WebidlFunctionKindMethod {
// TODO: what should this actually be?
ty: ast::WebidlScalarType::Any.into(),
};
ast::WebidlFunctionKind::Method(kind)
} else {
ast::WebidlFunctionKind::Static
};
bindings::register_import(
self.module,
&mut self.bindings,
import_id,
descriptor,
kind,
)?;
import
}
}
}
// NB: `structural` is ignored for free functions since it's
// expected that the binding isn't changing anyway.
None => {
bindings::register_import(
self.module,
&mut self.bindings,
import_id,
descriptor,
ast::WebidlFunctionKind::Static,
)?;
let name = self.determine_import(import, function.name)?;
AuxImport::Value(AuxValue::Bare(name))
}
};
self.aux.import_map.insert(import_id, import);
Ok(())
}
/// The `bool` returned indicates whether the imported value should be
/// invoked as a method (first arg is implicitly `this`) or if the imported
/// value is a simple function-like shim
fn determine_import_op(
&mut self,
mut class: JsImport,
function: &decode::Function<'_>,
structural: bool,
op: &decode::Operation<'_>,
) -> Result<(AuxImport, bool), Error> {
match op.kind {
decode::OperationKind::Regular => {
if op.is_static {
class.fields.push(function.name.to_string());
Ok((AuxImport::Value(AuxValue::Bare(class)), false))
} else if structural {
Ok((
AuxImport::StructuralMethod(function.name.to_string()),
false,
))
} else {
class.fields.push("prototype".to_string());
class.fields.push(function.name.to_string());
Ok((AuxImport::Value(AuxValue::Bare(class)), true))
}
}
decode::OperationKind::Getter(field) => {
if structural {
if op.is_static {
Ok((
AuxImport::StructuralClassGetter(class, field.to_string()),
false,
))
} else {
Ok((AuxImport::StructuralGetter(field.to_string()), false))
}
} else {
let val = if op.is_static {
AuxValue::ClassGetter(class, field.to_string())
} else {
AuxValue::Getter(class, field.to_string())
};
Ok((AuxImport::Value(val), true))
}
}
decode::OperationKind::Setter(field) => {
if structural {
if op.is_static {
Ok((
AuxImport::StructuralClassSetter(class, field.to_string()),
false,
))
} else {
Ok((AuxImport::StructuralSetter(field.to_string()), false))
}
} else {
let val = if op.is_static {
AuxValue::ClassSetter(class, field.to_string())
} else {
AuxValue::Setter(class, field.to_string())
};
Ok((AuxImport::Value(val), true))
}
}
decode::OperationKind::IndexingGetter => {
if !structural {
bail!("indexing getters must always be structural");
}
if op.is_static {
Ok((AuxImport::IndexingGetterOfClass(class), false))
} else {
Ok((AuxImport::IndexingGetterOfObject, false))
}
}
decode::OperationKind::IndexingSetter => {
if !structural {
bail!("indexing setters must always be structural");
}
if op.is_static {
Ok((AuxImport::IndexingSetterOfClass(class), false))
} else {
Ok((AuxImport::IndexingSetterOfObject, false))
}
}
decode::OperationKind::IndexingDeleter => {
if !structural {
bail!("indexing deleters must always be structural");
}
if op.is_static {
Ok((AuxImport::IndexingDeleterOfClass(class), false))
} else {
Ok((AuxImport::IndexingDeleterOfObject, false))
}
}
}
}
fn import_static(
&mut self,
import: &decode::Import<'_>,
static_: &decode::ImportStatic<'_>,
) -> Result<(), Error> {
let (import_id, _id) = match self.function_imports.get(static_.shim) {
Some(pair) => *pair,
None => return Ok(()),
};
// Register the signature of this imported shim
bindings::register_import(
self.module,
&mut self.bindings,
import_id,
Function {
arguments: Vec::new(),
shim_idx: 0,
ret: Descriptor::Anyref,
},
ast::WebidlFunctionKind::Static,
)?;
// And then save off that this function is is an instanceof shim for an
// imported item.
let import = self.determine_import(import, &static_.name)?;
self.aux
.import_map
.insert(import_id, AuxImport::Static(import));
Ok(())
}
fn import_type(
&mut self,
import: &decode::Import<'_>,
type_: &decode::ImportType<'_>,
) -> Result<(), Error> {
let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) {
Some(pair) => *pair,
None => return Ok(()),
};
// Register the signature of this imported shim
bindings::register_import(
self.module,
&mut self.bindings,
import_id,
Function {
arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))],
shim_idx: 0,
ret: Descriptor::Boolean,
},
ast::WebidlFunctionKind::Static,
)?;
// And then save off that this function is is an instanceof shim for an
// imported item.
let import = self.determine_import(import, &type_.name)?;
self.aux
.import_map
.insert(import_id, AuxImport::Instanceof(import));
Ok(())
}
fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> {
let aux = AuxEnum {
name: enum_.name.to_string(),
comments: concatenate_comments(&enum_.comments),
variants: enum_
.variants
.iter()
.map(|v| (v.name.to_string(), v.value))
.collect(),
};
self.aux.enums.push(aux);
Ok(())
}
fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> {
for field in struct_.fields {
let getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name);
let setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name);
let descriptor = match self.descriptors.remove(&getter) {
None => continue,
Some(d) => d,
};
// Register a webidl transformation for the getter
let (getter_id, _) = self.function_exports[&getter];
let getter_descriptor = Function {
arguments: vec![Descriptor::I32],
shim_idx: 0,
ret: descriptor.clone(),
};
bindings::register_export(
self.module,
&mut self.bindings,
getter_id,
getter_descriptor,
)?;
self.aux.export_map.insert(
getter_id,
AuxExport {
debug_name: format!("getter for `{}::{}`", struct_.name, field.name),
arg_names: None,
comments: concatenate_comments(&field.comments),
kind: AuxExportKind::Getter {
class: struct_.name.to_string(),
field: field.name.to_string(),
},
},
);
// If present, register information for the setter as well.
if field.readonly {
continue;
}
let (setter_id, _) = self.function_exports[&setter];
let setter_descriptor = Function {
arguments: vec![Descriptor::I32, descriptor],
shim_idx: 0,
ret: Descriptor::Unit,
};
bindings::register_export(
self.module,
&mut self.bindings,
setter_id,
setter_descriptor,
)?;
self.aux.export_map.insert(
setter_id,
AuxExport {
debug_name: format!("setter for `{}::{}`", struct_.name, field.name),
arg_names: None,
comments: concatenate_comments(&field.comments),
kind: AuxExportKind::Setter {
class: struct_.name.to_string(),
field: field.name.to_string(),
},
},
);
}
let aux = AuxStruct {
name: struct_.name.to_string(),
comments: concatenate_comments(&struct_.comments),
};
self.aux.structs.push(aux);
let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name);
if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) {
self.aux.import_map.insert(
*import_id,
AuxImport::WrapInExportedClass(struct_.name.to_string()),
);
let binding = Function {
shim_idx: 0,
arguments: vec![Descriptor::I32],
ret: Descriptor::Anyref,
};
bindings::register_import(
self.module,
&mut self.bindings,
*import_id,
binding,
ast::WebidlFunctionKind::Static,
)?;
}
Ok(())
}
fn determine_import(&self, import: &decode::Import<'_>, item: &str) -> Result<JsImport, Error> {
let is_local_snippet = match import.module {
decode::ImportModule::Named(s) => self.aux.local_modules.contains_key(s),
decode::ImportModule::RawNamed(_) => false,
decode::ImportModule::Inline(_) => true,
decode::ImportModule::None => false,
};
// Similar to `--target no-modules`, only allow vendor prefixes
// basically for web apis, shouldn't be necessary for things like npm
// packages or other imported items.
let vendor_prefixes = self.vendor_prefixes.get(item);
if let Some(vendor_prefixes) = vendor_prefixes {
assert!(vendor_prefixes.len() > 0);
if is_local_snippet {
bail!(
"local JS snippets do not support vendor prefixes for \
the import of `{}` with a polyfill of `{}`",
item,
&vendor_prefixes[0]
);
}
if let decode::ImportModule::Named(module) = &import.module {
bail!(
"import of `{}` from `{}` has a polyfill of `{}` listed, but
vendor prefixes aren't supported when importing from modules",
item,
module,
&vendor_prefixes[0],
);
}
if let Some(ns) = &import.js_namespace {
bail!(
"import of `{}` through js namespace `{}` isn't supported \
right now when it lists a polyfill",
item,
ns
);
}
return Ok(JsImport {
name: JsImportName::VendorPrefixed {
name: item.to_string(),
prefixes: vendor_prefixes.clone(),
},
fields: Vec::new(),
});
}
let (name, fields) = match import.js_namespace {
Some(ns) => (ns, vec![item.to_string()]),
None => (item, Vec::new()),
};
let name = match import.module {
decode::ImportModule::Named(module) if is_local_snippet => JsImportName::LocalModule {
module: module.to_string(),
name: name.to_string(),
},
decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => {
JsImportName::Module {
module: module.to_string(),
name: name.to_string(),
}
}
decode::ImportModule::Inline(idx) => {
let offset = self
.aux
.snippets
.get(self.unique_crate_identifier)
.map(|s| s.len())
.unwrap_or(0);
JsImportName::InlineJs {
unique_crate_identifier: self.unique_crate_identifier.to_string(),
snippet_idx_in_crate: idx as usize + offset,
name: name.to_string(),
}
}
decode::ImportModule::None => JsImportName::Global {
name: name.to_string(),
},
};
Ok(JsImport { name, fields })
}
/// Processes bindings from a standard WebIDL bindings custom section.
///
/// No module coming out of the Rust compiler will have one of these, but
/// eventually there's going to be other producers of the WebIDL bindings
/// custom section as well. This functionality is intended to allow
/// `wasm-bindgen`-the-CLI-tool to act as a polyfill for those modules as
/// well as Rust modules.
///
/// Here a standard `WebidlBindings` custom section is taken and we process
/// that into our own internal data structures to ensure that we have a
/// binding listed for all the described bindings.
///
/// In other words, this is a glorified conversion from the "official"
/// WebIDL bindings custom section into the wasm-bindgen internal
/// representation.
fn standard(&mut self, std: &ast::WebidlBindings) -> Result<(), Error> {
for (_id, bind) in std.binds.iter() {
let binding = self.standard_binding(std, bind)?;
let func = self.module.funcs.get(bind.func);
match &func.kind {
walrus::FunctionKind::Import(i) => {
let id = i.import;
self.standard_import(binding, id)?;
}
walrus::FunctionKind::Local(_) => {
let export = self
.module
.exports
.iter()
.find(|e| match e.item {
walrus::ExportItem::Function(f) => f == bind.func,
_ => false,
})
.ok_or_else(|| format_err!("missing export function for webidl binding"))?;
let id = export.id();
self.standard_export(binding, id)?;
}
walrus::FunctionKind::Uninitialized(_) => unreachable!(),
}
}
Ok(())
}
/// Creates a wasm-bindgen-internal `Binding` from an official `Bind`
/// structure specified in the upstream binary format.
///
/// This will largely just copy some things into our own arenas but also
/// processes the list of binding expressions into our own representations.
fn standard_binding(
&mut self,
std: &ast::WebidlBindings,
bind: &ast::Bind,
) -> Result<Binding, Error> {
let binding: &ast::FunctionBinding = std
.bindings
.get(bind.binding)
.ok_or_else(|| format_err!("bad binding id"))?;
let (wasm_ty, webidl_ty, incoming, outgoing) = match binding {
ast::FunctionBinding::Export(e) => (
e.wasm_ty,
e.webidl_ty,
e.params.bindings.as_slice(),
e.result.bindings.as_slice(),
),
ast::FunctionBinding::Import(e) => (
e.wasm_ty,
e.webidl_ty,
e.result.bindings.as_slice(),
e.params.bindings.as_slice(),
),
};
let webidl_ty = copy_ty(&mut self.bindings.types, webidl_ty, &std.types);
let webidl_ty = match webidl_ty {
ast::WebidlTypeRef::Id(id) => <ast::WebidlFunction as ast::WebidlTypeId>::wrap(id),
_ => bail!("invalid webidl type listed"),
};
return Ok(Binding {
wasm_ty,
webidl_ty,
incoming: incoming
.iter()
.cloned()
.map(NonstandardIncoming::Standard)
.collect(),
outgoing: outgoing
.iter()
.cloned()
.map(NonstandardOutgoing::Standard)
.collect(),
return_via_outptr: None,
});
/// Recursively clones `ty` into` dst` where it originally indexes
/// values in `src`, returning a new type ref which indexes inside of
/// `dst`.
fn copy_ty(
dst: &mut ast::WebidlTypes,
ty: ast::WebidlTypeRef,
src: &ast::WebidlTypes,
) -> ast::WebidlTypeRef {
let id = match ty {
ast::WebidlTypeRef::Id(id) => id,
ast::WebidlTypeRef::Scalar(_) => return ty,
};
let ty: &ast::WebidlCompoundType = src.get(id).unwrap();
match ty {
ast::WebidlCompoundType::Function(f) => {
let params = f
.params
.iter()
.map(|param| copy_ty(dst, *param, src))
.collect();
let result = f.result.map(|ty| copy_ty(dst, ty, src));
dst.insert(ast::WebidlFunction {
kind: f.kind.clone(),
params,
result,
})
.into()
}
_ => unimplemented!(),
}
}
}
/// Registers that `id` has a `binding` which was read from a standard
/// webidl bindings section, so the source of `id` is its actual module/name
/// listed in the wasm module.
fn standard_import(&mut self, binding: Binding, id: walrus::ImportId) -> Result<(), Error> {
let import = self.module.imports.get(id);
let js = JsImport {
name: JsImportName::Module {
module: import.module.clone(),
name: import.name.clone(),
},
fields: Vec::new(),
};
let value = AuxValue::Bare(js);
assert!(self
.aux
.import_map
.insert(id, AuxImport::Value(value))
.is_none());
assert!(self.bindings.imports.insert(id, binding).is_none());
Ok(())
}
/// Registers that `id` has a `binding` and comes from a standard webidl
/// bindings section so it doesn't have any documentation or debug names we
/// can work with.
fn standard_export(&mut self, binding: Binding, id: walrus::ExportId) -> Result<(), Error> {
let export = self.module.exports.get(id);
let kind = AuxExportKind::Function(export.name.clone());
let export = AuxExport {
debug_name: format!("standard export {:?}", id),
comments: String::new(),
arg_names: None,
kind,
};
assert!(self.aux.export_map.insert(id, export).is_none());
assert!(self.bindings.exports.insert(id, binding).is_none());
Ok(())
}
/// Perform a small verification pass over the module to perform some
/// internal sanity checks.
fn verify(&self) -> Result<(), Error> {
let mut imports_counted = 0;
for import in self.module.imports.iter() {
if import.module != PLACEHOLDER_MODULE {
continue;
}
match import.kind {
walrus::ImportKind::Function(_) => {}
_ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE),
}
// Ensure that everything imported from the `__wbindgen_placeholder__`
// module has a location listed as to where it's expected to be
// imported from.
if !self.aux.import_map.contains_key(&import.id()) {
bail!(
"import of `{}` doesn't have an import map item listed",
import.name
);
}
// Also make sure there's a binding listed for it.
if !self.bindings.imports.contains_key(&import.id()) {
bail!("import of `{}` doesn't have a binding listed", import.name);
}
imports_counted += 1;
}
// Make sure there's no extraneous bindings that weren't actually
// imported in the module.
if self.aux.import_map.len() != imports_counted {
bail!("import map is larger than the number of imports");
}
if self.bindings.imports.len() != imports_counted {
bail!("import binding map is larger than the number of imports");
}
// Make sure the export map and export bindings map contain the same
// number of entries.
for id in self.bindings.exports.keys() {
if !self.aux.export_map.contains_key(id) {
bail!("bindings map has an entry that the export map does not");
}
}
if self.bindings.exports.len() != self.aux.export_map.len() {
bail!("export map and export bindings map have different sizes");
}
Ok(())
}
}
impl walrus::CustomSection for NonstandardWebidlSection {
fn name(&self) -> &str {
"webidl custom section"
}
fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> {
panic!("shouldn't emit custom sections just yet");
}
}
impl walrus::CustomSection for WasmBindgenAux {
fn name(&self) -> &str {
"wasm-bindgen custom section"
}
fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> {
panic!("shouldn't emit custom sections just yet");
}
}
fn extract_programs<'a>(
module: &mut Module,
program_storage: &'a mut Vec<Vec<u8>>,
) -> Result<Vec<decode::Program<'a>>, Error> {
let my_version = wasm_bindgen_shared::version();
assert!(program_storage.is_empty());
while let Some(raw) = module.customs.remove_raw("__wasm_bindgen_unstable") {
log::debug!(
"custom section '{}' looks like a wasm bindgen section",
raw.name
);
program_storage.push(raw.data);
}
let mut ret = Vec::new();
for program in program_storage.iter() {
let mut payload = &program[..];
while let Some(data) = get_remaining(&mut payload) {
// Historical versions of wasm-bindgen have used JSON as the custom
// data section format. Newer versions, however, are using a custom
// serialization protocol that looks much more like the wasm spec.
//
// We, however, want a sanity check to ensure that if we're running
// against the wrong wasm-bindgen we get a nicer error than an
// internal decode error. To that end we continue to verify a tiny
// bit of json at the beginning of each blob before moving to the
// next blob. This should keep us compatible with older wasm-bindgen
// instances as well as forward-compatible for now.
//
// Note, though, that as `wasm-pack` picks up steam it's hoped we
// can just delete this entirely. The `wasm-pack` project already
// manages versions for us, so we in theory should need this check
// less and less over time.
if let Some(their_version) = verify_schema_matches(data)? {
bail!(
"
it looks like the Rust project used to create this wasm file was linked against
a different version of wasm-bindgen than this binary:
rust wasm file: {}
this binary: {}
Currently the bindgen format is unstable enough that these two version must
exactly match, so it's required that these two version are kept in sync by
either updating the wasm-bindgen dependency or this binary. You should be able
to update the wasm-bindgen dependency with:
cargo update -p wasm-bindgen
or you can update the binary with
cargo install -f wasm-bindgen-cli
if this warning fails to go away though and you're not sure what to do feel free
to open an issue at https://github.com/rustwasm/wasm-bindgen/issues!
",
their_version,
my_version,
);
}
let next = get_remaining(&mut payload).unwrap();
log::debug!("found a program of length {}", next.len());
ret.push(<decode::Program as decode::Decode>::decode_all(next));
}
}
Ok(ret)
}
fn get_remaining<'a>(data: &mut &'a [u8]) -> Option<&'a [u8]> {
if data.len() == 0 {
return None;
}
let len = ((data[0] as usize) << 0)
| ((data[1] as usize) << 8)
| ((data[2] as usize) << 16)
| ((data[3] as usize) << 24);
let (a, b) = data[4..].split_at(len);
*data = b;
Some(a)
}
fn verify_schema_matches<'a>(data: &'a [u8]) -> Result<Option<&'a str>, Error> {
macro_rules! bad {
() => {
bail!("failed to decode what looked like wasm-bindgen data")
};
}
let data = match str::from_utf8(data) {
Ok(s) => s,
Err(_) => bad!(),
};
log::debug!("found version specifier {}", data);
if !data.starts_with("{") || !data.ends_with("}") {
bad!()
}
let needle = "\"schema_version\":\"";
let rest = match data.find(needle) {
Some(i) => &data[i + needle.len()..],
None => bad!(),
};
let their_schema_version = match rest.find("\"") {
Some(i) => &rest[..i],
None => bad!(),
};
if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION {
return Ok(None);
}
let needle = "\"version\":\"";
let rest = match data.find(needle) {
Some(i) => &data[i + needle.len()..],
None => bad!(),
};
let their_version = match rest.find("\"") {
Some(i) => &rest[..i],
None => bad!(),
};
Ok(Some(their_version))
}
fn concatenate_comments(comments: &[&str]) -> String {
comments
.iter()
.map(|s| s.trim_matches('"'))
.collect::<Vec<_>>()
.join("\n")
}
/// Do we need to generate JS glue shims for these incoming bindings?
pub fn incoming_do_not_require_glue(
exprs: &[NonstandardIncoming],
from_webidl_tys: &[ast::WebidlTypeRef],
to_wasm_tys: &[walrus::ValType],
) -> bool {
exprs.len() == from_webidl_tys.len()
&& exprs.len() == to_wasm_tys.len()
&& exprs
.iter()
.zip(from_webidl_tys)
.zip(to_wasm_tys)
.enumerate()
.all(|(i, ((expr, from_webidl_ty), to_wasm_ty))| match expr {
NonstandardIncoming::Standard(e) => e.is_expressible_in_js_without_webidl_bindings(
*from_webidl_ty,
*to_wasm_ty,
i as u32,
),
_ => false,
})
}
/// Do we need to generate JS glue shims for these outgoing bindings?
pub fn outgoing_do_not_require_glue(
exprs: &[NonstandardOutgoing],
from_wasm_tys: &[walrus::ValType],
to_webidl_tys: &[ast::WebidlTypeRef],
) -> bool {
exprs.len() == from_wasm_tys.len()
&& exprs.len() == to_webidl_tys.len()
&& exprs
.iter()
.zip(from_wasm_tys)
.zip(to_webidl_tys)
.enumerate()
.all(|(i, ((expr, from_wasm_ty), to_webidl_ty))| match expr {
NonstandardOutgoing::Standard(e) => e.is_expressible_in_js_without_webidl_bindings(
*from_wasm_ty,
*to_webidl_ty,
i as u32,
),
_ => false,
})
}