Add support for emitting a Wasm Interface Types section

This commit adds support to `wasm-bindgen` to emit a WebAssembly module
that contains a WebAssembly Interface Types section. As of today there are no
native consumers of these WebAssembly modules, and the actual binary format
here is basically arbitrary (chosen by the `wasm-webidl-bindings` crate). The
intention is that we'll be following the [WebAssembly Interface
Types proposal][proposal] very closely and update here as necessary.

The main feature added in this PR is that a new experimental environment
variable, `WASM_INTERFACE_TYPES=1`, is recognized by the `wasm-bindgen`
CLI tool. When present the CLI tool will act differently than it does
today:

* The `anyref` feature will be implicitly enabled
* A WebAssembly interface types section will be emitted in the
  WebAssembly module
* For now, the WebAssembly module is strictly validated to require zero
  JS glue. This means that `wasm-bindgen` is producing a fully
  standalone WebAssembly module.

The last point here is one that will change before this functionality is
stabilized in `wasm-bindgen`. For now it reflects the major use case of
this feature which is to produce a standalone WebAssembly module with no
support JS glue, and to do that we need to verify properties like it's
not using JS global names, nonstandard binding expressions, etc. The
error messages here aren't the best but they at least fail compilation
at some point instead of silently producing weird wasm modules.

Eventually it's envisioned that a WebAssembly module will contain an
interface types section but *also* have JS glue so binding expressions
can be used when available but otherwise we'd still generate JS glue for
things like nonstandard expressions and accessing JS global values.

It should be noted that a major feature not implemented in
`wasm-bindgen` yet is the multi-value proposal for WebAssembly. This is
coming soon (as soon as we can) in `walrus` and later for a pass here,
but for now this means that returning multiple values (like a string
which has a pointer/length) is a bit of a hack. To enable this use case
a `wasm-bindgen`-specific-convention which will never be stabilized is
invented here by using binding expression to indicate "this return value
is actually returned through an out-ptr as the first argument list".
This is a gross hack and is guaranteed to be removed. Eventually we will
support multi-value and the wasm module emitted will simply use
multi-value and contain internal polyfills for Rust's ABI which returns
values through out-ptrs.

Overall this should make `wasm-bindgen` usable for playing around with
the WebIDL bindings proposal and helping us get a taste of what it looks
like to have entirely standalone WebAssembly modules running in multiple
environments, no extra fluff necessary!

[proposal]: https://github.com/webassembly/webidl-bindings
This commit is contained in:
Alex Crichton
2019-06-25 01:21:38 -07:00
parent bd7c90788a
commit c5dd572d9e
6 changed files with 630 additions and 73 deletions

View File

@ -6,7 +6,7 @@ use walrus::Module;
use wasm_bindgen_anyref_xform::Context;
use wasm_webidl_bindings::ast;
pub fn process(module: &mut Module) -> Result<(), Error> {
pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Error> {
let mut cfg = Context::default();
cfg.prepare(module)?;
let bindings = module
@ -45,18 +45,24 @@ pub fn process(module: &mut Module) -> Result<(), Error> {
cfg.run(module)?;
// 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 = module.tables.iter().find(|t| match t.kind {
walrus::TableKind::Anyref(_) => true,
_ => false,
});
let table = match table {
Some(t) => t.id(),
None => return Ok(()),
};
module.exports.add("__wbg_anyref_table", table);
// If our output is using WebAssembly interface types then our bindings will
// never use this table, so no need to export it. Otherwise it's highly
// likely in web/JS embeddings this will be used, so make sure we export it
// to avoid it getting gc'd accidentally.
if !wasm_interface_types {
// 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 = module.tables.iter().find(|t| match t.kind {
walrus::TableKind::Anyref(_) => true,
_ => false,
});
let table = match table {
Some(t) => t.id(),
None => return Ok(()),
};
module.exports.add("__wbg_anyref_table", table);
}
// Clean up now-unused intrinsics and shims and such
walrus::passes::gc::run(module);

View File

@ -50,6 +50,15 @@ macro_rules! intrinsics {
)*
}
}
/// Returns the symbol name of this intrinsic
pub fn name(&self) -> &'static str {
match self {
$(
Intrinsic::$name => $sym,
)*
}
}
}
};
}

View File

@ -2081,11 +2081,13 @@ impl<'a> Context<'a> {
&binding.outgoing,
wasm_ty.params(),
&webidl.params,
self.config.wasm_interface_types,
)
&& webidl::incoming_do_not_require_glue(
&binding.incoming,
&webidl.result.into_iter().collect::<Vec<_>>(),
wasm_ty.results(),
self.config.wasm_interface_types,
)
}

View File

@ -36,6 +36,7 @@ pub struct Bindgen {
// "ready to be instantiated on any thread"
threads: wasm_bindgen_threads_xform::Config,
anyref: bool,
wasm_interface_types: bool,
encode_into: EncodeInto,
}
@ -49,6 +50,7 @@ pub struct Output {
snippets: HashMap<String, Vec<String>>,
local_modules: HashMap<String, String>,
npm_dependencies: HashMap<String, (PathBuf, String)>,
wasm_interface_types: bool,
}
#[derive(Clone)]
@ -73,6 +75,8 @@ pub enum EncodeInto {
impl Bindgen {
pub fn new() -> Bindgen {
let anyref = env::var("WASM_BINDGEN_ANYREF").is_ok();
let wasm_interface_types = env::var("WASM_INTERFACE_TYPES").is_ok();
Bindgen {
input: Input::None,
out_name: None,
@ -88,7 +92,8 @@ impl Bindgen {
emit_start: true,
weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(),
threads: threads_config(),
anyref: env::var("WASM_BINDGEN_ANYREF").is_ok(),
anyref: anyref || wasm_interface_types,
wasm_interface_types,
encode_into: EncodeInto::Test,
}
}
@ -315,7 +320,7 @@ impl Bindgen {
// the webidl bindings proposal) as well as an auxiliary section for all
// sorts of miscellaneous information and features #[wasm_bindgen]
// supports that aren't covered by WebIDL bindings.
webidl::process(&mut module, self.anyref, self.emit_start)?;
webidl::process(&mut module, self.anyref, self.wasm_interface_types, self.emit_start)?;
// Now that we've got type information from the webidl processing pass,
// touch up the output of rustc to insert anyref shims where necessary.
@ -323,7 +328,7 @@ impl Bindgen {
// currently off-by-default since `anyref` is still in development in
// engines.
if self.anyref {
anyref::process(&mut module)?;
anyref::process(&mut module, self.wasm_interface_types)?;
}
let aux = module
@ -344,6 +349,11 @@ impl Bindgen {
(npm_dependencies, cx.finalize(stem)?)
};
if self.wasm_interface_types {
webidl::standard::add_section(&mut module, &aux, &bindings)
.with_context(|_| "failed to generate a standard wasm bindings custom section")?;
}
Ok(Output {
module,
stem: stem.to_string(),
@ -354,6 +364,7 @@ impl Bindgen {
ts,
mode: self.mode.clone(),
typescript: self.typescript,
wasm_interface_types: self.wasm_interface_types,
})
}
@ -506,6 +517,7 @@ fn unexported_unused_lld_things(module: &mut Module) {
impl Output {
pub fn js(&self) -> &str {
assert!(!self.wasm_interface_types);
&self.js
}
@ -518,6 +530,23 @@ impl Output {
}
fn _emit(&self, out_dir: &Path) -> Result<(), Error> {
let wasm_name = if self.wasm_interface_types {
self.stem.clone()
} else {
format!("{}_bg", self.stem)
};
let wasm_path = out_dir
.join(wasm_name)
.with_extension("wasm");
fs::create_dir_all(out_dir)?;
let wasm_bytes = self.module.emit_wasm()?;
fs::write(&wasm_path, wasm_bytes)
.with_context(|_| format!("failed to write `{}`", wasm_path.display()))?;
if self.wasm_interface_types {
return Ok(())
}
// Write out all local JS snippets to the final destination now that
// we've collected them from all the programs.
for (identifier, list) in self.snippets.iter() {
@ -554,7 +583,6 @@ impl Output {
} else {
"js"
};
fs::create_dir_all(out_dir)?;
let js_path = out_dir.join(&self.stem).with_extension(extension);
fs::write(&js_path, reset_indentation(&self.js))
.with_context(|_| format!("failed to write `{}`", js_path.display()))?;
@ -565,10 +593,6 @@ impl Output {
.with_context(|_| format!("failed to write `{}`", ts_path.display()))?;
}
let wasm_path = out_dir
.join(format!("{}_bg", self.stem))
.with_extension("wasm");
if self.mode.nodejs() {
let js_path = wasm_path.with_extension(extension);
let shim = self.generate_node_wasm_import(&self.module, &wasm_path);
@ -583,9 +607,6 @@ impl Output {
.with_context(|_| format!("failed to write `{}`", ts_path.display()))?;
}
let wasm_bytes = self.module.emit_wasm()?;
fs::write(&wasm_path, wasm_bytes)
.with_context(|_| format!("failed to write `{}`", wasm_path.display()))?;
Ok(())
}

View File

@ -41,6 +41,7 @@ const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__";
mod bindings;
mod incoming;
mod outgoing;
pub mod standard;
pub use self::incoming::NonstandardIncoming;
pub use self::outgoing::NonstandardOutgoing;
@ -474,12 +475,14 @@ struct Context<'a> {
unique_crate_identifier: &'a str,
descriptors: HashMap<String, Descriptor>,
anyref_enabled: bool,
wasm_interface_types: bool,
support_start: bool,
}
pub fn process(
module: &mut Module,
anyref_enabled: bool,
wasm_interface_types: bool,
support_start: bool,
) -> Result<(NonstandardWebidlSectionId, WasmBindgenAuxId), Error> {
let mut storage = Vec::new();
@ -496,6 +499,7 @@ pub fn process(
module,
start_found: false,
anyref_enabled,
wasm_interface_types,
support_start,
};
cx.init()?;
@ -619,8 +623,12 @@ impl<'a> Context<'a> {
// `__wbindgen_init_anyref_table` function. This'll ensure that all
// instances of this module have the initial slots of the anyref table
// initialized correctly.
//
// Note that this is disabled if WebAssembly interface types are enabled
// since that's a slightly different environment for now which doesn't have
// quite the same initialization.
fn inject_anyref_initialization(&mut self) -> Result<(), Error> {
if !self.anyref_enabled {
if !self.anyref_enabled || self.wasm_interface_types {
return Ok(());
}
@ -1345,26 +1353,49 @@ impl<'a> Context<'a> {
.bindings
.get(bind.binding)
.ok_or_else(|| format_err!("bad binding id"))?;
let mut return_via_outptr = None;
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(),
),
ast::FunctionBinding::Export(e) => {
// This `match` is weird, see the comment at the top of
// `standard.rs` for what it's doing.
let outgoing = match e.result.bindings.get(0) {
Some(ast::OutgoingBindingExpression::As(a)) if a.idx == u32::max_value() => {
return_via_outptr = Some(vec![walrus::ValType::I32, walrus::ValType::I32]);
&e.result.bindings[1..]
}
_ => &e.result.bindings[..],
};
(
e.wasm_ty,
e.webidl_ty,
e.params.bindings.as_slice(),
outgoing,
)
}
ast::FunctionBinding::Import(e) => {
// This `match` is weird, see the comment at the top of
// `standard.rs` for what it's doing.
let incoming = match e.result.bindings.get(0) {
Some(ast::IncomingBindingExpression::Get(g)) if g.idx == u32::max_value() => {
return_via_outptr = Some(vec![walrus::ValType::I32, walrus::ValType::I32]);
&e.result.bindings[1..]
}
_ => &e.result.bindings[..],
};
(
e.wasm_ty,
e.webidl_ty,
incoming,
e.params.bindings.as_slice(),
)
}
};
let webidl_ty = copy_ty(&mut self.bindings.types, webidl_ty, &std.types);
let webidl_ty = standard::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 {
Ok(Binding {
wasm_ty,
webidl_ty,
incoming: incoming
@ -1377,40 +1408,8 @@ impl<'a> Context<'a> {
.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!(),
}
}
return_via_outptr,
})
}
/// Registers that `id` has a `binding` which was read from a standard
@ -1662,7 +1661,23 @@ pub fn incoming_do_not_require_glue(
exprs: &[NonstandardIncoming],
from_webidl_tys: &[ast::WebidlTypeRef],
to_wasm_tys: &[walrus::ValType],
standard_webidl_enabled: bool,
) -> bool {
// If anything is nonstandard, then we're unconditionally going to need a JS
// shim because, well, it's not standard.
if exprs.iter().any(|e| match e {
NonstandardIncoming::Standard(_) => false,
_ => true,
}) {
return false;
}
// If everything is `Standard` and we've actually got WebIDL bindings fully
// enabled, then we don't require any glue at all!
if standard_webidl_enabled {
return true;
}
exprs.len() == from_webidl_tys.len()
&& exprs.len() == to_wasm_tys.len()
&& exprs
@ -1685,7 +1700,19 @@ pub fn outgoing_do_not_require_glue(
exprs: &[NonstandardOutgoing],
from_wasm_tys: &[walrus::ValType],
to_webidl_tys: &[ast::WebidlTypeRef],
standard_webidl_enabled: bool,
) -> bool {
// Same short-circuits as above.
if exprs.iter().any(|e| match e {
NonstandardOutgoing::Standard(_) => false,
_ => true,
}) {
return false;
}
if standard_webidl_enabled {
return true;
}
exprs.len() == from_wasm_tys.len()
&& exprs.len() == to_webidl_tys.len()
&& exprs

View File

@ -0,0 +1,492 @@
//! Support for generating a standard WebIDL custom section
//!
//! This module has all the necessary support for generating a full-fledged
//! standard WebIDL custom section as defined by the `wasm-webidl-bindings`
//! crate. This module also critically assumes that the WebAssembly module
//! being generated **must be standalone**. In this mode all sorts of features
//! supported by `#[wasm_bindgen]` aren't actually supported, such as closures,
//! imports of global js names, js getters/setters, exporting structs, etc.
//! These features may all eventually come to the standard bindings proposal,
//! but it will likely take some time. In the meantime this module simply focuses
//! on taking what's already a valid wasm module and letting it through with a
//! standard WebIDL custom section. All other modules generate an error during
//! this binding process.
//!
//! Note that when this function is called and used we're also not actually
//! generating any JS glue. Any JS glue currently generated is also invalid if
//! the module contains the wasm bindings section and it's actually respected.
// NB: Returning strings is weird
//
// This module has what is currently a pretty gross hack for dealing with
// returning strings. One of the banner features of WebIDL bindings is not
// requiring any language-specific glue to use wasm files and you get all sorts
// of types like integers and strings by default. Note that strings are a huge
// thing here.
//
// Dealing with *incoming* strings is easy enough in that the binding expression
// has an allocator function to call and it's filled in and passed as two
// pointers. Outgoing strings are a little harder, however, for two reasons:
//
// * One is that we need to return two values, which requires multi-value
// * Another is that someone's got to free the string at some point
//
// Rust/wasm-bindgen don't support multi-value, and WebIDL bindings as literally
// spec'd right this red hot second don't support freeing strings that we pass
// out. These both have obvious fixes (supporting multi-value and extending the
// bindings spec to support freeing) but we also want something working right
// this red-hot second.
//
// To get things working we employ a terrible hack where the first bindign
// expression of the result a function may indicate "use a thing that's way off
// in the ether" and that's actually a sentinel for "oh ignore everything else
// and the string is returned through an out-ptr as the first argument". This
// manifests in all sorts of special hacks all over the place to handle this,
// and it's a real bummer.
//
// This is in general just an explainer for the current state of things and
// also a preemptive apology for writing the code to handle this in so many
// places. I, like you, look forward to actually fixing this for real as the
// spec continues to evolve and we implement more in wasm-bindgen.
use crate::descriptor::VectorKind;
use crate::webidl::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName};
use crate::webidl::{NonstandardIncoming, NonstandardOutgoing};
use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux};
use failure::{bail, Error, ResultExt};
use walrus::Module;
use wasm_webidl_bindings::ast;
pub fn add_section(
module: &mut Module,
aux: &WasmBindgenAux,
nonstandard: &NonstandardWebidlSection,
) -> Result<(), Error> {
let mut section = ast::WebidlBindings::default();
let WasmBindgenAux {
extra_typescript: _, // ignore this even if it's specified
local_modules,
snippets,
package_jsons,
export_map,
import_map,
imports_with_catch,
imports_with_variadic,
imports_with_assert_no_shim: _, // not relevant for this purpose
enums,
structs,
} = aux;
for (export, binding) in nonstandard.exports.iter() {
// First up make sure this is something that's actually valid to export
// form a vanilla WebAssembly module with WebIDL bindings.
match &export_map[export].kind {
AuxExportKind::Function(_) => {}
AuxExportKind::Constructor(name) => {
bail!(
"cannot export `{}` constructor function when generating \
a standalone WebAssembly module with no JS glue",
name,
);
}
AuxExportKind::Getter { class, field } => {
bail!(
"cannot export `{}::{}` getter function when generating \
a standalone WebAssembly module with no JS glue",
class,
field,
);
}
AuxExportKind::Setter { class, field } => {
bail!(
"cannot export `{}::{}` setter function when generating \
a standalone WebAssembly module with no JS glue",
class,
field,
);
}
AuxExportKind::StaticFunction { class, name } => {
bail!(
"cannot export `{}::{}` static function when \
generating a standalone WebAssembly module with no \
JS glue",
class,
name
);
}
AuxExportKind::Method { class, name, .. } => {
bail!(
"cannot export `{}::{}` method when \
generating a standalone WebAssembly module with no \
JS glue",
class,
name
);
}
}
let name = &module.exports.get(*export).name;
let params = extract_incoming(&binding.incoming).with_context(|_| {
format!(
"failed to map arguments for export `{}` to standard \
binding expressions",
name
)
})?;
let mut result = extract_outgoing(&binding.outgoing).with_context(|_| {
format!(
"failed to map return value for export `{}` to standard \
binding expressions",
name
)
})?;
// see comment at top of this module about returning strings for what
// this is doing and why it's weird
if binding.return_via_outptr.is_some() {
result.insert(
0,
ast::OutgoingBindingExpressionAs {
idx: u32::max_value(),
ty: ast::WebidlScalarType::Long.into(),
}
.into(),
);
}
let binding = section.bindings.insert(ast::ExportBinding {
wasm_ty: binding.wasm_ty,
webidl_ty: copy_ty(
&mut section.types,
binding.webidl_ty.into(),
&nonstandard.types,
),
params: ast::IncomingBindingMap { bindings: params },
result: ast::OutgoingBindingMap { bindings: result },
});
let func = match module.exports.get(*export).item {
walrus::ExportItem::Function(f) => f,
_ => unreachable!(),
};
section.binds.insert(ast::Bind {
func,
binding: binding.into(),
});
}
for (import, binding) in nonstandard.imports.iter() {
check_standard_import(&import_map[import])?;
let (module_name, name) = {
let import = module.imports.get(*import);
(&import.module, &import.name)
};
let params = extract_outgoing(&binding.outgoing).with_context(|_| {
format!(
"failed to map arguments of import `{}::{}` to standard \
binding expressions",
module_name, name,
)
})?;
let mut result = extract_incoming(&binding.incoming).with_context(|_| {
format!(
"failed to map return value of import `{}::{}` to standard \
binding expressions",
module_name, name,
)
})?;
// see comment at top of this module about returning strings for what
// this is doing and why it's weird
if binding.return_via_outptr.is_some() {
result.insert(
0,
ast::IncomingBindingExpressionGet {
idx: u32::max_value(),
}
.into(),
);
}
let binding = section.bindings.insert(ast::ImportBinding {
wasm_ty: binding.wasm_ty,
webidl_ty: copy_ty(
&mut section.types,
binding.webidl_ty.into(),
&nonstandard.types,
),
params: ast::OutgoingBindingMap { bindings: params },
result: ast::IncomingBindingMap { bindings: result },
});
let func = match module.imports.get(*import).kind {
walrus::ImportKind::Function(f) => f,
_ => unreachable!(),
};
section.binds.insert(ast::Bind {
func,
binding: binding.into(),
});
}
if let Some((name, _)) = local_modules.iter().next() {
bail!(
"generating a bindings section is currently incompatible with \
local JS modules being specified as well, `{}` cannot be used \
since a standalone wasm file is being generated",
name,
);
}
if let Some((name, _)) = snippets.iter().filter(|(_, v)| !v.is_empty()).next() {
bail!(
"generating a bindings section is currently incompatible with \
local JS snippets being specified as well, `{}` cannot be used \
since a standalone wasm file is being generated",
name,
);
}
if let Some(path) = package_jsons.iter().next() {
bail!(
"generating a bindings section is currently incompatible with \
package.json being consumed as well, `{}` cannot be used \
since a standalone wasm file is being generated",
path.display(),
);
}
if let Some(import) = imports_with_catch.iter().next() {
let import = module.imports.get(*import);
bail!(
"generating a bindings section is currently incompatible with \
`#[wasm_bindgen(catch)]` on the `{}::{}` import because a \
a standalone wasm file is being generated",
import.module,
import.name,
);
}
if let Some(import) = imports_with_variadic.iter().next() {
let import = module.imports.get(*import);
bail!(
"generating a bindings section is currently incompatible with \
`#[wasm_bindgen(variadic)]` on the `{}::{}` import because a \
a standalone wasm file is being generated",
import.module,
import.name,
);
}
if let Some(enum_) = enums.iter().next() {
bail!(
"generating a bindings section is currently incompatible with \
exporting an `enum` from the wasm file, cannot export `{}`",
enum_.name,
);
}
if let Some(struct_) = structs.iter().next() {
bail!(
"generating a bindings section is currently incompatible with \
exporting a `struct` from the wasm file, cannot export `{}`",
struct_.name,
);
}
if nonstandard.elems.len() > 0 {
// Note that this is a pretty cryptic error message, but we in theory
// shouldn't ever hit this since closures always show up as some form
// of nonstandard binding which was previously checked.
bail!("generating a standalone wasm file requires no table element bindings");
}
module.customs.add(section);
Ok(())
}
fn extract_incoming(
nonstandard: &[NonstandardIncoming],
) -> Result<Vec<ast::IncomingBindingExpression>, Error> {
let mut exprs = Vec::new();
for expr in nonstandard {
let desc = match expr {
NonstandardIncoming::Standard(e) => {
exprs.push(e.clone());
continue;
}
NonstandardIncoming::Int64 { .. } => "64-bit integer",
NonstandardIncoming::AllocCopyInt64 { .. } => "64-bit integer array",
NonstandardIncoming::AllocCopyAnyrefArray { .. } => "array of JsValue",
NonstandardIncoming::MutableSlice { .. } => "mutable slice",
NonstandardIncoming::OptionSlice { .. } => "optional slice",
NonstandardIncoming::OptionVector { .. } => "optional vector",
NonstandardIncoming::OptionAnyref { .. } => "optional anyref",
NonstandardIncoming::OptionNative { .. } => "optional integer",
NonstandardIncoming::OptionU32Sentinel { .. } => "optional integer",
NonstandardIncoming::OptionBool { .. } => "optional bool",
NonstandardIncoming::OptionChar { .. } => "optional char",
NonstandardIncoming::OptionIntegerEnum { .. } => "optional enum",
NonstandardIncoming::OptionInt64 { .. } => "optional integer",
NonstandardIncoming::RustType { .. } => "native Rust type",
NonstandardIncoming::RustTypeRef { .. } => "reference to Rust type",
NonstandardIncoming::OptionRustType { .. } => "optional Rust type",
NonstandardIncoming::Char { .. } => "character",
NonstandardIncoming::BorrowedAnyref { .. } => "borrowed anyref",
};
bail!("cannot represent {} with a standard bindings expression", desc);
}
Ok(exprs)
}
fn extract_outgoing(
nonstandard: &[NonstandardOutgoing],
) -> Result<Vec<ast::OutgoingBindingExpression>, Error> {
let mut exprs = Vec::new();
for expr in nonstandard {
let desc = match expr {
NonstandardOutgoing::Standard(e) => {
exprs.push(e.clone());
continue;
}
// ... yeah ... let's just leak strings
// see comment at top of this module about returning strings for
// what this is doing and why it's weird
NonstandardOutgoing::Vector {
offset,
length,
kind: VectorKind::String,
} => {
exprs.push(
ast::OutgoingBindingExpressionUtf8Str {
offset: *offset,
length: *length,
ty: ast::WebidlScalarType::DomString.into(),
}
.into(),
);
continue;
}
NonstandardOutgoing::RustType { .. } => "rust type",
NonstandardOutgoing::Char { .. } => "character",
NonstandardOutgoing::Number64 { .. } => "64-bit integer",
NonstandardOutgoing::BorrowedAnyref { .. } => "borrowed anyref",
NonstandardOutgoing::Vector { .. } => "vector",
NonstandardOutgoing::CachedString { .. } => "cached string",
NonstandardOutgoing::View64 { .. } => "64-bit slice",
NonstandardOutgoing::ViewAnyref { .. } => "anyref slice",
NonstandardOutgoing::OptionVector { .. } => "optional vector",
NonstandardOutgoing::OptionSlice { .. } => "optional slice",
NonstandardOutgoing::OptionNative { .. } => "optional integer",
NonstandardOutgoing::OptionU32Sentinel { .. } => "optional integer",
NonstandardOutgoing::OptionBool { .. } => "optional boolean",
NonstandardOutgoing::OptionChar { .. } => "optional character",
NonstandardOutgoing::OptionIntegerEnum { .. } => "optional enum",
NonstandardOutgoing::OptionInt64 { .. } => "optional 64-bit integer",
NonstandardOutgoing::OptionRustType { .. } => "optional rust type",
NonstandardOutgoing::StackClosure { .. } => "closures",
};
bail!("cannot represent {} with a standard bindings expression", desc);
}
Ok(exprs)
}
/// Recursively clones `ty` into` dst` where it originally indexes values in
/// `src`, returning a new type ref which indexes inside of `dst`.
pub 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!(),
}
}
fn check_standard_import(import: &AuxImport) -> Result<(), Error> {
let desc_js = |js: &JsImport| {
let mut extra = String::new();
for field in js.fields.iter() {
extra.push_str(".");
extra.push_str(field);
}
match &js.name {
JsImportName::Global { name } | JsImportName::VendorPrefixed { name, .. } => {
format!("global `{}{}`", name, extra)
}
JsImportName::Module { module, name } => {
format!("`{}{}` from '{}'", name, extra, module)
}
JsImportName::LocalModule { module, name } => {
format!("`{}{}` from local module '{}'", name, extra, module)
}
JsImportName::InlineJs {
unique_crate_identifier,
name,
..
} => format!(
"`{}{}` from inline js in '{}'",
name, extra, unique_crate_identifier
),
}
};
let item = match import {
AuxImport::Value(AuxValue::Bare(js)) => {
if js.fields.len() == 0 {
if let JsImportName::Module { .. } = js.name {
return Ok(());
}
}
desc_js(js)
}
AuxImport::Value(AuxValue::Getter(js, name))
| AuxImport::Value(AuxValue::Setter(js, name))
| AuxImport::Value(AuxValue::ClassGetter(js, name))
| AuxImport::Value(AuxValue::ClassSetter(js, name)) => {
format!("field access of `{}` for {}", name, desc_js(js))
}
AuxImport::Instanceof(js) => format!("instance of check of {}", desc_js(js)),
AuxImport::Static(js) => format!("static js value {}", desc_js(js)),
AuxImport::StructuralMethod(name) => format!("structural method `{}`", name),
AuxImport::StructuralGetter(name)
| AuxImport::StructuralSetter(name)
| AuxImport::StructuralClassGetter(_, name)
| AuxImport::StructuralClassSetter(_, name) => {
format!("structural field access of `{}`", name)
}
AuxImport::IndexingDeleterOfClass(_)
| AuxImport::IndexingDeleterOfObject
| AuxImport::IndexingGetterOfClass(_)
| AuxImport::IndexingGetterOfObject
| AuxImport::IndexingSetterOfClass(_)
| AuxImport::IndexingSetterOfObject => format!("indexing getters/setters/deleters"),
AuxImport::WrapInExportedClass(name) => {
format!("wrapping a pointer in a `{}` js class wrapper", name)
}
AuxImport::Intrinsic(intrinsic) => {
format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name())
}
AuxImport::Closure { .. } => format!("creating a `Closure` wrapper"),
};
bail!(
"cannot generate a standalone WebAssembly module which \
contains an import of {} since it requires JS glue",
item
);
}