Update WIT tooling used by Wasmtime (#5565)

* Update WIT tooling used by Wasmtime

This commit updates the WIT tooling, namely the wasm-tools family of
crates, with recent updates. Notably:

* bytecodealliance/wasm-tools#867
* bytecodealliance/wasm-tools#871

This updates index spaces in components and additionally bumps the
minimum required version of the component binary format to be consumed
by Wasmtime (because of the index space changes). Additionally WIT
tooling now fully supports `use`.

Note that WIT tooling doesn't, at this time, fully support packages and
depending on remotely defined WIT packages. Currently WIT still needs to
be vendored in the project. It's hoped that future work with `cargo
component` and possible integration here could make the story about
depending on remotely-defined WIT more ergonomic and streamlined.

* Fix `bindgen!` codegen tests

* Add a test for `use` paths an implement support

* Update to crates.io versions of wasm-tools

* Uncomment codegen tests
This commit is contained in:
Alex Crichton
2023-01-18 09:37:03 -06:00
committed by GitHub
parent 9b896d2a70
commit 247851234b
35 changed files with 1192 additions and 546 deletions

41
Cargo.lock generated
View File

@@ -3255,18 +3255,18 @@ checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
[[package]]
name = "wasm-encoder"
version = "0.20.0"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05632e0a66a6ed8cca593c24223aabd6262f256c3693ad9822c315285f010614"
checksum = "29ab2fe77b325731603297debb4573e002d06ae0aa1f4dc108585c81961e0609"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-mutate"
version = "0.2.13"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0e9666b36c1e7e88bf1e0d114c8a4fe7d4e32d3aa38c1e8e9d71a972b99764a"
checksum = "c2f8b34ecab2aadb3a974fc96d38a37780793b1a44f9e681ed68f7e69757ca90"
dependencies = [
"egg",
"log",
@@ -3278,9 +3278,9 @@ dependencies = [
[[package]]
name = "wasm-smith"
version = "0.11.10"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ca3f5a24b691771929ac6adf330972e8ecbfe48dcf8809d7bf83216c124cfd"
checksum = "00e76ca2ad5d10fdcd08d9af00ac02585a1e53a60020b0eda8874922f5d49bff"
dependencies = [
"arbitrary",
"flagset",
@@ -3330,9 +3330,9 @@ dependencies = [
[[package]]
name = "wasmparser"
version = "0.96.0"
version = "0.97.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adde01ade41ab9a5d10ec8ed0bb954238cf8625b5cd5a13093d6de2ad9c2be1a"
checksum = "b98123a0d2bacf9286239231b116cbd66c65d9b89793f7c9bba3a3ae7f1b15f3"
dependencies = [
"indexmap",
"url",
@@ -3349,9 +3349,9 @@ dependencies = [
[[package]]
name = "wasmprinter"
version = "0.2.45"
version = "0.2.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3045e1aa2cac847f4f94a1e25db9f084a947aeff47d9099fb9c5ccd16d335040"
checksum = "595cca929e47a7bec3c941b5a8e133f51b17e6d9dd8c82ab97902196f5a07b42"
dependencies = [
"anyhow",
"wasmparser",
@@ -3502,7 +3502,7 @@ dependencies = [
"wasmtime-wasi-crypto",
"wasmtime-wasi-nn",
"wasmtime-wast",
"wast 50.0.0",
"wast 51.0.0",
"wat",
"windows-sys",
]
@@ -3523,6 +3523,7 @@ dependencies = [
name = "wasmtime-component-macro"
version = "6.0.0"
dependencies = [
"anyhow",
"component-macro-test-helpers",
"proc-macro2",
"quote",
@@ -3775,7 +3776,7 @@ dependencies = [
"anyhow",
"log",
"wasmtime",
"wast 50.0.0",
"wast 51.0.0",
]
[[package]]
@@ -3810,9 +3811,9 @@ dependencies = [
[[package]]
name = "wast"
version = "50.0.0"
version = "51.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34"
checksum = "a1f621e6e9af96438d3e05f0699da5b1dae59f2df964a2982166aa9b03c5b599"
dependencies = [
"leb128",
"memchr",
@@ -3822,11 +3823,11 @@ dependencies = [
[[package]]
name = "wat"
version = "1.0.52"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15"
checksum = "5dd18c1168d7e8743d9b4f713c0203924f5dcc4a3983eb5e584de9614f9fccde"
dependencies = [
"wast 50.0.0",
"wast 51.0.0",
]
[[package]]
@@ -4032,15 +4033,17 @@ dependencies = [
[[package]]
name = "wit-parser"
version = "0.3.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703eb1d2f89ff2c52d50f7ff002735e423cea75f0a5dc5c8a4626c4c47cd9ca6"
checksum = "02cfa79275011530f37e0e164183c606bae1cdc466ea90bcd364d50605486a4d"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"pulldown-cmark",
"unicode-xid",
"url",
]
[[package]]

View File

@@ -158,14 +158,14 @@ winch-codegen = { path = "winch/codegen", version = "=0.4.0" }
target-lexicon = { version = "0.12.3", default-features = false, features = ["std"] }
anyhow = "1.0.22"
wasmparser = "0.96.0"
wat = "1.0.52"
wast = "50.0.0"
wasmprinter = "0.2.45"
wasm-encoder = "0.20.0"
wasm-smith = "0.11.10"
wasm-mutate = "0.2.13"
wit-parser = "0.3"
wasmparser = "0.97.0"
wat = "1.0.53"
wast = "51.0.0"
wasmprinter = "0.2.46"
wasm-encoder = "0.21.0"
wasm-smith = "0.11.11"
wasm-mutate = "0.2.14"
wit-parser = "0.4"
windows-sys = "0.42.0"
env_logger = "0.9"
rustix = "0.36.0"

View File

@@ -16,6 +16,7 @@ test = false
doctest = false
[dependencies]
anyhow = "1.0"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["extra-traits"] }

View File

@@ -3,14 +3,14 @@ use std::path::{Path, PathBuf};
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::{braced, token, Ident, Token};
use wasmtime_wit_bindgen::Opts;
use wit_parser::{Document, World};
use wasmtime_wit_bindgen::{Opts, TrappableError};
use wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};
#[derive(Default)]
pub struct Config {
opts: Opts, // ...
world: World,
files: Vec<String>,
opts: Opts,
resolve: Resolve,
world: WorldId,
files: Vec<PathBuf>,
}
pub fn expand(input: &Config) -> Result<TokenStream> {
@@ -21,18 +21,14 @@ pub fn expand(input: &Config) -> Result<TokenStream> {
));
}
let src = input.opts.generate(&input.world);
let src = input.opts.generate(&input.resolve, input.world);
let mut contents = src.parse::<TokenStream>().unwrap();
// Include a dummy `include_str!` for any files we read so rustc knows that
// we depend on the contents of those files.
let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap();
for file in input.files.iter() {
contents.extend(
format!(
"const _: &str = include_str!(r#\"{}\"#);\n",
Path::new(&cwd).join(file).display()
)
format!("const _: &str = include_str!(r#\"{}\"#);\n", file.display())
.parse::<TokenStream>()
.unwrap(),
);
@@ -41,73 +37,146 @@ pub fn expand(input: &Config) -> Result<TokenStream> {
Ok(contents)
}
enum Source {
Path(String),
Inline(String),
}
impl Parse for Config {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let call_site = Span::call_site();
let mut opts = Opts::default();
let mut world = None;
let mut ret = Config::default();
let mut source = None;
if input.peek(token::Brace) {
let document = if input.peek(token::Brace) {
let content;
syn::braced!(content in input);
let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
let mut document = None;
for field in fields.into_pairs() {
match field.into_value() {
Opt::Path(path) => {
if world.is_some() {
return Err(Error::new(path.span(), "cannot specify second world"));
Opt::Path(s) => {
if source.is_some() {
return Err(Error::new(s.span(), "cannot specify second source"));
}
world = Some(ret.parse(path)?);
source = Some(Source::Path(s.value()));
}
Opt::Inline(span, w) => {
if world.is_some() {
return Err(Error::new(span, "cannot specify second world"));
Opt::World(s) => {
if document.is_some() {
return Err(Error::new(s.span(), "cannot specify second document"));
}
world = Some(w);
document = Some(parse_doc(&s.value(), &mut world));
}
Opt::Tracing(val) => ret.opts.tracing = val,
Opt::Async(val) => ret.opts.async_ = val,
Opt::TrappableErrorType(val) => ret.opts.trappable_error_type = val,
Opt::Inline(s) => {
if source.is_some() {
return Err(Error::new(s.span(), "cannot specify second source"));
}
source = Some(Source::Inline(s.value()));
}
Opt::Tracing(val) => opts.tracing = val,
Opt::Async(val) => opts.async_ = val,
Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
}
}
match (document, &source) {
(Some(doc), _) => doc,
(None, Some(Source::Inline(_))) => "macro-input".to_string(),
_ => {
return Err(Error::new(
call_site,
"must specify a `world` to generate bindings for",
))
}
}
} else {
let s = input.parse::<syn::LitStr>()?;
world = Some(ret.parse(s)?);
let document = input.parse::<syn::LitStr>()?;
if input.parse::<Option<syn::token::In>>()?.is_some() {
source = Some(Source::Path(input.parse::<syn::LitStr>()?.value()));
}
ret.world = world.ok_or_else(|| {
Error::new(
call_site,
"must specify a `*.wit` file to generate bindings for",
)
parse_doc(&document.value(), &mut world)
};
let (resolve, pkg, files) =
parse_source(&source).map_err(|err| Error::new(call_site, format!("{err:?}")))?;
let doc = resolve.packages[pkg]
.documents
.get(&document)
.copied()
.ok_or_else(|| {
Error::new(call_site, format!("no document named `{document}` found"))
})?;
Ok(ret)
let world = match &world {
Some(name) => resolve.documents[doc]
.worlds
.get(name)
.copied()
.ok_or_else(|| Error::new(call_site, format!("no world named `{name}` found")))?,
None => resolve.documents[doc].default_world.ok_or_else(|| {
Error::new(call_site, format!("no default world found in `{document}`"))
})?,
};
Ok(Config {
opts,
resolve,
world,
files,
})
}
}
impl Config {
fn parse(&mut self, path: syn::LitStr) -> Result<World> {
let span = path.span();
let path = path.value();
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let path = manifest_dir.join(path);
self.files.push(path.to_str().unwrap().to_string());
World::parse_file(path).map_err(|e| Error::new(span, e))
fn parse_source(source: &Option<Source>) -> anyhow::Result<(Resolve, PackageId, Vec<PathBuf>)> {
let mut resolve = Resolve::default();
let mut files = Vec::new();
let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let mut parse = |path: &Path| -> anyhow::Result<_> {
if path.is_dir() {
let (pkg, sources) = resolve.push_dir(&path)?;
files = sources;
Ok(pkg)
} else {
let pkg = UnresolvedPackage::parse_file(path)?;
files.extend(pkg.source_files().map(|s| s.to_owned()));
resolve.push(pkg, &Default::default())
}
};
let pkg = match source {
Some(Source::Inline(s)) => resolve.push(
UnresolvedPackage::parse("macro-input".as_ref(), &s)?,
&Default::default(),
)?,
Some(Source::Path(s)) => parse(&root.join(&s))?,
None => parse(&root.join("wit"))?,
};
Ok((resolve, pkg, files))
}
fn parse_doc(s: &str, world: &mut Option<String>) -> String {
match s.find('.') {
Some(pos) => {
*world = Some(s[pos + 1..].to_string());
s[..pos].to_string()
}
None => s.to_string(),
}
}
mod kw {
syn::custom_keyword!(path);
syn::custom_keyword!(inline);
syn::custom_keyword!(path);
syn::custom_keyword!(tracing);
syn::custom_keyword!(trappable_error_type);
syn::custom_keyword!(world);
}
enum Opt {
World(syn::LitStr),
Path(syn::LitStr),
Inline(Span, World),
Inline(syn::LitStr),
Tracing(bool),
Async(bool),
TrappableErrorType(Vec<(String, String, String)>),
TrappableErrorType(Vec<TrappableError>),
}
impl Parse for Opt {
@@ -118,14 +187,13 @@ impl Parse for Opt {
input.parse::<Token![:]>()?;
Ok(Opt::Path(input.parse()?))
} else if l.peek(kw::inline) {
let span = input.parse::<kw::inline>()?.span;
input.parse::<kw::inline>()?;
input.parse::<Token![:]>()?;
let s = input.parse::<syn::LitStr>()?;
let world = Document::parse("<macro-input>".as_ref(), &s.value())
.map_err(|e| Error::new(s.span(), e))?
.into_world()
.map_err(|e| Error::new(s.span(), e))?;
Ok(Opt::Inline(span, world))
Ok(Opt::Inline(input.parse()?))
} else if l.peek(kw::world) {
input.parse::<kw::world>()?;
input.parse::<Token![:]>()?;
Ok(Opt::World(input.parse()?))
} else if l.peek(kw::tracing) {
input.parse::<kw::tracing>()?;
input.parse::<Token![:]>()?;
@@ -141,7 +209,16 @@ impl Parse for Opt {
let _lbrace = braced!(contents in input);
let fields: Punctuated<(String, String, String), Token![,]> =
contents.parse_terminated(trappable_error_field_parse)?;
Ok(Opt::TrappableErrorType(fields.into_iter().collect()))
Ok(Opt::TrappableErrorType(
fields
.into_iter()
.map(|(wit_owner, wit_name, rust_name)| TrappableError {
wit_owner: Some(wit_owner),
wit_name,
rust_name,
})
.collect(),
))
} else {
Err(l.error())
}

View File

@@ -12,10 +12,10 @@ pub fn foreach(input: TokenStream) -> TokenStream {
let f = f.unwrap().path();
if f.extension().and_then(|s| s.to_str()) == Some("wit") {
let name = f.file_stem().unwrap().to_str().unwrap();
let name = Ident::new(&name.replace("-", "_"), Span::call_site());
let ident = Ident::new(&name.replace("-", "_"), Span::call_site());
let path = f.to_str().unwrap();
result.push(quote! {
#input!(#name #path);
#input!(#ident #name #path);
});
}
}

View File

@@ -1,17 +1,19 @@
macro_rules! gentest {
($id:ident $path:tt) => {
($id:ident $name:tt $path:tt) => {
mod $id {
mod normal {
wasmtime::component::bindgen!($path);
wasmtime::component::bindgen!($name in $path);
}
mod async_ {
wasmtime::component::bindgen!({
world: $name,
path: $path,
async: true,
});
}
mod tracing {
wasmtime::component::bindgen!({
world: $name,
path: $path,
tracing: true,
});

View File

@@ -5,8 +5,7 @@ interface chars {
return-char: func() -> char
}
world the-world {
import imports: chars
export exports: chars
default export chars
default world the-world {
import imports: self.chars
export exports: self.chars
}

View File

@@ -32,8 +32,7 @@ interface conventions {
%bool: func()
}
world the-world {
import imports: conventions
export exports: conventions
default export conventions
default world the-world {
import imports: self.conventions
export exports: self.conventions
}

View File

@@ -1 +1 @@
world empty {}
default world empty {}

View File

@@ -47,8 +47,7 @@ interface flegs {
roundtrip-flag64: func(x: flag64) -> flag64
}
world the-flags {
import import-flags: flegs
export export-flags: flegs
default export flegs
default world the-flags {
import import-flags: self.flegs
export export-flags: self.flegs
}

View File

@@ -5,8 +5,7 @@ interface floats {
float64-result: func() -> float64
}
world the-world {
import imports: floats
export exports: floats
default export floats
default world the-world {
import imports: self.floats
export exports: self.floats
}

View File

@@ -32,8 +32,7 @@ interface integers {
pair-ret: func() -> tuple<s64, u8>
}
world the-world {
import imports: integers
export exports: integers
default export integers
default world the-world {
import imports: self.integers
export exports: self.integers
}

View File

@@ -77,8 +77,7 @@ interface lists {
load-store-everything: func(a: load-store-all-sizes) -> load-store-all-sizes
}
world the-lists {
import import-lists: lists
export export-lists: lists
default export lists
default world the-lists {
import import-lists: self.lists
export export-lists: self.lists
}

View File

@@ -44,8 +44,7 @@ interface manyarg {
big-argument: func(x: big-struct)
}
world the-world {
import imports: manyarg
export exports: manyarg
default export manyarg
default world the-world {
import imports: self.manyarg
export exports: self.manyarg
}

View File

@@ -6,8 +6,7 @@ interface multi-return {
mre: func() -> (a: u32, b: float32)
}
world the-world {
import imports: multi-return
export exports: multi-return
default export multi-return
default world the-world {
import imports: self.multi-return
export exports: self.multi-return
}

View File

@@ -53,8 +53,7 @@ interface records {
typedef-inout: func(e: tuple-typedef2) -> s32
}
world the-world {
import imports: records
export exports: records
default export records
default world the-world {
import imports: self.records
export exports: self.records
}

View File

@@ -9,8 +9,7 @@ interface simple {
f6: func(a: u32, b: u32, c: u32) -> tuple<u32, u32, u32>
}
world the-world {
import imports: simple
export exports: simple
default export simple
default world the-world {
import imports: self.simple
export exports: self.simple
}

View File

@@ -1,13 +1,11 @@
interface simple-lists {
simple-list1: func(l: list<u32>)
simple-list2: func() -> list<u32>
// TODO: reenable this when smw implements this
// simple-list3: func(a: list<u32>, b: list<u32>) -> tuple<list<u32>, list<u32>>
simple-list3: func(a: list<u32>, b: list<u32>) -> tuple<list<u32>, list<u32>>
simple-list4: func(l: list<list<u32>>) -> list<list<u32>>
}
world my-world {
import imports: simple-lists
export exports: simple-lists
default export simple-lists
default world my-world {
import imports: self.simple-lists
export exports: self.simple-lists
}

View File

@@ -7,8 +7,7 @@ interface anon {
option-test: func() -> result<option<string>, error>
}
world the-world {
import imports: anon
export exports: anon
default export anon
default world the-world {
import imports: self.anon
export exports: self.anon
}

View File

@@ -1,5 +1,3 @@
world the-world {
default export interface {
y: func()
}
default world the-world {
export y: func()
}

View File

@@ -1,4 +1,4 @@
world the-world {
default world the-world {
export the-name: interface {
y: func()
}

View File

@@ -1,4 +1,4 @@
world the-world {
default world the-world {
import imports: interface {
y: func()
}

View File

@@ -4,8 +4,7 @@ interface strings {
c: func(a: string, b: string) -> string
}
world the-world {
import imports: strings
export exports: strings
default export strings
default world the-world {
import imports: self.strings
export exports: self.strings
}

View File

@@ -58,8 +58,7 @@ interface unions {
identify-distinguishable-num: func(num: distinguishable-num) -> u8
}
world the-unions {
import import-unions: unions
export export-unions: unions
default export unions
default world the-unions {
import import-unions: self.unions
export export-unions: self.unions
}

View File

@@ -0,0 +1,27 @@
interface a {
record foo {}
a: func() -> foo
}
interface b {
use self.a.{foo}
a: func() -> foo
}
interface c {
use self.b.{foo}
a: func() -> foo
}
default world d {
import a: self.a
import b: self.b
import d: interface {
use self.c.{foo}
b: func() -> foo
}
}

View File

@@ -139,8 +139,7 @@ interface variants {
return-named-result: func() -> (a: result<u8, my-errno>)
}
world my-world {
import imports: variants
export exports: variants
default export variants
default world my-world {
import imports: self.variants
export exports: self.variants
}

View File

@@ -191,6 +191,9 @@ enum LocalInitializer<'data> {
AliasComponentExport(ComponentInstanceIndex, &'data str),
AliasModule(ClosedOverModule),
AliasComponent(ClosedOverComponent),
// export section
Export(ComponentItem),
}
/// The "closure environment" of components themselves.
@@ -615,6 +618,9 @@ impl<'a, 'data> Translator<'a, 'data> {
let item = self.kind_to_item(export.kind, export.index);
let prev = self.result.exports.insert(export.name, item);
assert!(prev.is_none());
self.result
.initializers
.push(LocalInitializer::Export(item));
}
}

View File

@@ -731,6 +731,29 @@ impl<'a> Inliner<'a> {
AliasComponent(idx) => {
frame.components.push(frame.closed_over_component(idx));
}
Export(item) => match item {
ComponentItem::Func(i) => {
frame
.component_funcs
.push(frame.component_funcs[*i].clone());
}
ComponentItem::Module(i) => {
frame.modules.push(frame.modules[*i].clone());
}
ComponentItem::Component(i) => {
frame.components.push(frame.components[*i].clone());
}
ComponentItem::ComponentInstance(i) => {
frame
.component_instances
.push(frame.component_instances[*i].clone());
}
// Type index spaces aren't maintained during this inlining pass
// so ignore this.
ComponentItem::Type(_) => {}
},
}
Ok(None)

View File

@@ -20,7 +20,7 @@ pub use self::instance::{ExportInstance, Exports, Instance, InstancePre};
pub use self::linker::{Linker, LinkerInstance};
pub use self::types::Type;
pub use self::values::Val;
pub use wasmtime_component_macro::{bindgen, flags, ComponentType, Lift, Lower};
pub use wasmtime_component_macro::{flags, ComponentType, Lift, Lower};
// These items are expected to be used by an eventual
// `#[derive(ComponentType)]`, they are not part of Wasmtime's API stability
@@ -42,3 +42,249 @@ pub mod __internal {
}
pub(crate) use self::store::ComponentStoreData;
/// Generate bindings for a WIT package.
///
/// This macro ingests a [WIT package] and will generate all the necessary
/// bindings for instantiating and invoking a particular `world` in the
/// package. A `world` in a WIT package is a description of imports and exports
/// for a component. This provides a higher-level representation of working with
/// a component than the raw [`Instance`] type which must be manually-type-check
/// and manually have its imports provided via the [`Linker`] type.
///
/// The most basic usage of this macro is:
///
/// ```rust,ignore
/// wasmtime::component::bindgen!("my-component");
/// ```
///
/// This will parse your projects WIT package in a `wit` directory adjacent to
/// your crate's `Cargo.toml`. All of the `*.wit` files in that directory are
/// parsed and then the `default world` will be looked up within
/// `my-component.wit`. This world is then used as the basis for generating
/// bindings.
///
/// For example if your project contained:
///
/// ```text,ignore
/// // wit/my-component.wit
///
/// default world hello-world {
/// import name: func() -> string
/// export greet: func()
/// }
/// ```
///
/// Then you can interact with the generated bindings like so:
///
/// ```rust,ignore
/// use anyhow::Result;
/// use wasmtime::component::*;
/// use wasmtime::{Config, Engine, Store};
///
/// bindgen!("my-component");
///
/// struct MyState {
/// name: String,
/// }
///
/// // Imports into the world, like the `name` import for this world, are satisfied
/// // through traits.
/// impl HelloWorldImports for MyState {
/// // Note the `Result` return value here where `Ok` is returned back to
/// // the component and `Err` will raise a trap.
/// fn name(&mut self) -> Result<String> {
/// Ok(self.name.clone())
/// }
/// }
///
/// fn main() -> Result<()> {
/// // Configure an `Engine` and compile the `Component` that is being run for
/// // the application.
/// let mut config = Config::new();
/// config.wasm_component_model(true);
/// let engine = Engine::new(&config)?;
/// let component = Component::from_file(&engine, "./your-component.wasm")?;
///
/// // Instantiation of bindings always happens through a `Linker`.
/// // Configuration of the linker is done through a generated `add_to_linker`
/// // method on the bindings structure.
/// //
/// // Note that the closure provided here is a projection from `T` in
/// // `Store<T>` to `&mut U` where `U` implements the `HelloWorldImports`
/// // trait. In this case the `T`, `MyState`, is stored directly in the
/// // structure so no projection is necessary here.
/// let mut linker = Linker::new(&engine);
/// HelloWorld::add_to_linker(&mut linker, |state: &mut MyState| state)?;
///
/// // As with the core wasm API of Wasmtime instantiation occurs within a
/// // `Store`. The bindings structure contains an `instantiate` method which
/// // takes the store, component, and linker. This returns the `bindings`
/// // structure which is an instance of `HelloWorld` and supports typed access
/// // to the exports of the component.
/// let mut store = Store::new(
/// &engine,
/// MyState {
/// name: "me".to_string(),
/// },
/// );
/// let (bindings, _) = HelloWorld::instantiate(&mut store, &component, &linker)?;
///
/// // Here our `greet` function doesn't take any parameters for the component,
/// // but in the Wasmtime embedding API the first argument is always a `Store`.
/// bindings.greet(&mut store)?;
/// Ok(())
/// }
/// ```
///
/// The function signatures within generated traits and on generated exports
/// match the component-model signatures as specified in the WIT `world` item.
/// Note that WIT also has support for importing and exports interfaces within
/// worlds, which can be bound here as well:
///
/// For example this WIT input
///
/// ```text,ignore
/// // wit/my-component.wit
///
/// interface host {
/// gen-random-integer: func() -> u32
/// sha256: func(bytes: list<u8>) -> string
/// }
///
/// default world hello-world {
/// import host: self.host
///
/// export demo: interface {
/// run: func()
/// }
/// }
/// ```
///
/// Then you can interact with the generated bindings like so:
///
/// ```rust,ignore
/// use anyhow::Result;
/// use wasmtime::component::*;
/// use wasmtime::{Config, Engine, Store};
///
/// bindgen!("my-component");
///
/// struct MyState {
/// // ...
/// }
///
/// // Note that the trait here is per-interface and within a submodule now.
/// impl host::Host for MyState {
/// fn gen_random_integer(&mut self) -> Result<u32> {
/// Ok(rand::thread_rng().gen())
/// }
///
/// fn sha256(&mut self, bytes: Vec<u8>) -> Result<String> {
/// // ...
/// }
/// }
///
/// fn main() -> Result<()> {
/// let mut config = Config::new();
/// config.wasm_component_model(true);
/// let engine = Engine::new(&config)?;
/// let component = Component::from_file(&engine, "./your-component.wasm")?;
///
/// let mut linker = Linker::new(&engine);
/// HelloWorld::add_to_linker(&mut linker, |state: &mut MyState| state)?;
///
/// let mut store = Store::new(
/// &engine,
/// MyState { /* ... */ },
/// );
/// let (bindings, _) = HelloWorld::instantiate(&mut store, &component, &linker)?;
///
/// // Note that the `demo` method returns a `&Demo` through which we can
/// // run the methods on that interface.
/// bindings.demo().run(&mut store)?;
/// Ok(())
/// }
/// ```
///
/// The generated bindings can additionally be explored more fully with `cargo
/// doc` to see what types and traits and such are generated.
///
/// # Syntax
///
/// This procedural macro accepts a few different syntaxes. The primary purpose
/// of this macro is to locate a WIT package, parse it, and then extract a
/// `world` from the parsed package. There are then codegen-specific options to
/// the bindings themselves which can additionally be specified.
///
/// Basic usage of this macro looks like:
///
/// ```rust,ignore
/// // Parse the `wit/` folder adjacent to this crate's `Cargo.toml` and look
/// // for the document `foo`, which must have a `default world` contained
/// // within it.
/// bindgen!("foo");
///
/// // Parse the `wit/` folder adjacent to `Cargo.toml` and look up the document
/// // `foo` and the world named `bar`.
/// bindgen!("foo.bar");
///
/// // Parse the folder `other/wit/folder` adjacent to `Cargo.toml`.
/// bindgen!("foo" in "other/wit/folder");
/// bindgen!("foo.bar" in "other/wit/folder");
///
/// // Parse the file `foo.wit` as a single-file WIT package with no
/// // dependencies.
/// bindgen!("foo" in "foo.wit");
/// ```
///
/// A more configured version of invoking this macro looks like:
///
/// ```rust,ignore
/// bindgen!({
/// world: "foo", // or "foo.bar", same as in `bindgen!("foo")`
///
/// // same as in `bindgen!("foo" in "other/wit/folder")
/// path: "other/wit/folder",
///
/// // Instead of `path` the WIT document can be provided inline if
/// // desired.
/// inline: "
/// default world foo {
/// // ...
/// }
/// ",
///
/// // Add calls to `tracing::span!` before each import or export is called
/// // to log arguments and return values.
/// //
/// // This option defaults to `false`.
/// tracing: true,
///
/// // Imports will be async functions through #[async_trait] and exports
/// // are also invoked as async functions. Requires `Config::async_support`
/// // to be `true`.
/// //
/// // Note that this is only async for the host as the guest will still
/// // appear as if it's invoking blocking functions.
/// //
/// // This option defaults to `false`.
/// async: true,
///
/// // This can be used to translate WIT return values of the form
/// // `result<T, error-type>` into `Result<T, RustErrorType>` in Rust.
/// // The `RustErrorType` structure will have an automatically generated
/// // implementation of `From<ErrorType> for RustErrorType`. The
/// // `RustErrorType` additionally can also represent a trap to
/// // conveniently flatten all errors into one container.
/// //
/// // By default this option is not specified.
/// trappable_error_type: {
/// interface::ErrorType: RustErrorType,
/// },
///
/// });
/// ```
///
/// [WIT package]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md
pub use wasmtime_component_macro::bindgen;

View File

@@ -29,8 +29,14 @@ use source::Source;
struct Wasmtime {
src: Source,
opts: Opts,
imports: Vec<String>,
imports: Vec<Import>,
exports: Exports,
types: Types,
}
enum Import {
Interface { snake: String },
Function { add_to_linker: String, sig: String },
}
#[derive(Default)]
@@ -50,40 +56,66 @@ pub struct Opts {
/// Whether or not to use async rust functions and traits.
pub async_: bool,
/// For a given wit interface and type name, generate a "trappable error type"
/// of the following Rust type name
pub trappable_error_type: Vec<(String, String, String)>,
/// A list of "trappable errors" which are used to replace the `E` in
/// `result<T, E>` found in WIT.
pub trappable_error_type: Vec<TrappableError>,
}
#[derive(Debug, Clone)]
pub struct TrappableError {
/// The name of the error in WIT that is being mapped.
pub wit_name: String,
/// The owner container of the error in WIT of the error that's being
/// mapped.
///
/// This is, for example, the name of the WIT interface or the WIT world
/// which owns the type. If this is set to `None` then any error type with
/// `wit_name` is remapped to `rust_name`.
pub wit_owner: Option<String>,
/// The name, in Rust, of the error type to generate.
pub rust_name: String,
}
impl Opts {
pub fn generate(&self, world: &World) -> String {
pub fn generate(&self, resolve: &Resolve, world: WorldId) -> String {
let mut r = Wasmtime::default();
r.opts = self.clone();
r.generate(world)
r.generate(resolve, world)
}
}
impl Wasmtime {
fn generate(&mut self, world: &World) -> String {
fn generate(&mut self, resolve: &Resolve, id: WorldId) -> String {
self.types.analyze(resolve, id);
let world = &resolve.worlds[id];
for (name, import) in world.imports.iter() {
self.import(name, import);
self.import(resolve, name, import);
}
for (name, export) in world.exports.iter() {
self.export(name, export);
self.export(resolve, name, export);
}
if let Some(iface) = &world.default {
self.export_default(&world.name, iface);
}
self.finish(world)
self.finish(resolve, id)
}
fn import(&mut self, name: &str, iface: &Interface) {
let mut gen = InterfaceGenerator::new(self, iface, TypeMode::Owned);
gen.types();
gen.generate_trappable_error_types();
gen.generate_add_to_linker(name);
fn import(&mut self, resolve: &Resolve, name: &str, item: &WorldItem) {
let snake = name.to_snake_case();
let mut gen = InterfaceGenerator::new(self, resolve, TypeMode::Owned);
let import = match item {
WorldItem::Function(func) => {
gen.generate_function_trait_sig(TypeOwner::None, &func);
let sig = mem::take(&mut gen.src).into();
gen.generate_add_function_to_linker(TypeOwner::None, &func, "linker");
let add_to_linker = gen.src.into();
Import::Function { sig, add_to_linker }
}
WorldItem::Interface(id) => {
gen.current_interface = Some(*id);
gen.types(*id);
gen.generate_trappable_error_types(TypeOwner::Interface(*id));
gen.generate_add_to_linker(*id, name);
let module = &gen.src[..];
uwriteln!(
@@ -98,18 +130,34 @@ impl Wasmtime {
}}
"
);
Import::Interface { snake }
}
};
self.imports.push(snake);
self.imports.push(import);
}
fn export(&mut self, name: &str, iface: &Interface) {
let mut gen = InterfaceGenerator::new(self, iface, TypeMode::AllBorrowed("'a"));
gen.types();
gen.generate_trappable_error_types();
fn export(&mut self, resolve: &Resolve, name: &str, item: &WorldItem) {
let snake = name.to_snake_case();
let mut gen = InterfaceGenerator::new(self, resolve, TypeMode::AllBorrowed("'a"));
let (ty, getter) = match item {
WorldItem::Function(func) => {
gen.define_rust_guest_export(None, func);
let body = mem::take(&mut gen.src).into();
let (_name, getter) = gen.extract_typed_function(func);
assert!(gen.src.is_empty());
self.exports.funcs.push(body);
(format!("wasmtime::component::Func"), getter)
}
WorldItem::Interface(id) => {
gen.current_interface = Some(*id);
gen.types(*id);
gen.generate_trappable_error_types(TypeOwner::Interface(*id));
let iface = &resolve.interfaces[*id];
let camel = name.to_upper_camel_case();
uwriteln!(gen.src, "pub struct {camel} {{");
for func in iface.functions.iter() {
for (_, func) in iface.functions.iter() {
uwriteln!(
gen.src,
"{}: wasmtime::component::Func,",
@@ -127,22 +175,23 @@ impl Wasmtime {
) -> anyhow::Result<{camel}> {{
"
);
let fields = gen.extract_typed_functions();
for (name, getter) in fields.iter() {
let mut fields = Vec::new();
for (_, func) in iface.functions.iter() {
let (name, getter) = gen.extract_typed_function(func);
uwriteln!(gen.src, "let {name} = {getter};");
fields.push(name);
}
uwriteln!(gen.src, "Ok({camel} {{");
for (name, _) in fields.iter() {
for name in fields {
uwriteln!(gen.src, "{name},");
}
uwriteln!(gen.src, "}})");
uwriteln!(gen.src, "}}");
for func in iface.functions.iter() {
for (_, func) in iface.functions.iter() {
gen.define_rust_guest_export(Some(name), func);
}
uwriteln!(gen.src, "}}");
let snake = name.to_snake_case();
let module = &gen.src[..];
uwriteln!(
@@ -166,11 +215,6 @@ impl Wasmtime {
)?\
"
);
let prev = self
.exports
.fields
.insert(snake.clone(), (format!("{snake}::{camel}"), getter));
assert!(prev.is_none());
self.exports.funcs.push(format!(
"
pub fn {snake}(&self) -> &{snake}::{camel} {{
@@ -178,35 +222,15 @@ impl Wasmtime {
}}
"
));
(format!("{snake}::{camel}"), getter)
}
fn export_default(&mut self, _name: &str, iface: &Interface) {
let mut gen = InterfaceGenerator::new(self, iface, TypeMode::AllBorrowed("'a"));
gen.types();
gen.generate_trappable_error_types();
let fields = gen.extract_typed_functions();
for (name, getter) in fields {
let prev = gen
.gen
.exports
.fields
.insert(name, ("wasmtime::component::Func".to_string(), getter));
};
let prev = self.exports.fields.insert(snake.clone(), (ty, getter));
assert!(prev.is_none());
}
for func in iface.functions.iter() {
let prev = mem::take(&mut gen.src);
gen.define_rust_guest_export(None, func);
let func = mem::replace(&mut gen.src, prev);
gen.gen.exports.funcs.push(func.to_string());
}
let src = gen.src;
self.src.push_str(&src);
}
fn finish(&mut self, world: &World) -> String {
let camel = world.name.to_upper_camel_case();
fn finish(&mut self, resolve: &Resolve, world: WorldId) -> String {
let camel = resolve.worlds[world].name.to_upper_camel_case();
uwriteln!(self.src, "pub struct {camel} {{");
for (name, (ty, _)) in self.exports.fields.iter() {
uwriteln!(self.src, "{name}: {ty},");
@@ -219,13 +243,16 @@ impl Wasmtime {
("", "", "", "")
};
self.toplevel_import_trait(resolve, world);
uwriteln!(self.src, "const _: () = {{");
uwriteln!(self.src, "use wasmtime::component::__internal::anyhow;");
uwriteln!(self.src, "impl {camel} {{");
self.toplevel_add_to_linker(resolve, world);
uwriteln!(
self.src,
"
impl {camel} {{
/// Instantiates the provided `module` using the specified
/// parameters, wrapping up the result in a structure that
/// translates between wasm and the host.
@@ -303,52 +330,138 @@ impl Wasmtime {
}
impl Wasmtime {
fn trappable_error_types<'a>(
&'a self,
iface: &'a Interface,
) -> impl Iterator<Item = (&String, &TypeId, &String)> + 'a {
self.opts
.trappable_error_type
fn toplevel_import_trait(&mut self, resolve: &Resolve, world: WorldId) {
let mut functions = Vec::new();
for import in self.imports.iter() {
match import {
Import::Interface { .. } => continue,
Import::Function {
sig,
add_to_linker: _,
} => functions.push(sig),
}
}
if functions.is_empty() {
return;
}
let world_camel = resolve.worlds[world].name.to_upper_camel_case();
uwriteln!(self.src, "pub trait {world_camel}Imports {{");
for sig in functions {
self.src.push_str(sig);
self.src.push_str("\n");
}
uwriteln!(self.src, "}}");
}
fn toplevel_add_to_linker(&mut self, resolve: &Resolve, world: WorldId) {
if self.imports.is_empty() {
return;
}
let mut functions = Vec::new();
let mut interfaces = Vec::new();
for import in self.imports.iter() {
match import {
Import::Interface { snake } => interfaces.push(snake),
Import::Function {
add_to_linker,
sig: _,
} => functions.push(add_to_linker),
}
}
uwrite!(
self.src,
"
pub fn add_to_linker<T, U>(
linker: &mut wasmtime::component::Linker<T>,
get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
) -> anyhow::Result<()>
where U: \
"
);
let world_camel = resolve.worlds[world].name.to_upper_camel_case();
let world_trait = format!("{world_camel}Imports");
for (i, name) in interfaces
.iter()
.filter(|(interface_name, _, _)| iface.name == *interface_name)
.filter_map(|(_, wit_typename, rust_typename)| {
let wit_type = iface.type_lookup.get(wit_typename)?;
Some((wit_typename, wit_type, rust_typename))
.map(|n| format!("{n}::{}", n.to_upper_camel_case()))
.chain(if functions.is_empty() {
None
} else {
Some(world_trait.clone())
})
.enumerate()
{
if i > 0 {
self.src.push_str(" + ");
}
self.src.push_str(&name);
}
let maybe_send = if self.opts.async_ {
" + Send, T: Send"
} else {
""
};
self.src.push_str(maybe_send);
self.src.push_str(",\n{\n");
for name in interfaces.iter() {
uwriteln!(self.src, "{name}::add_to_linker(linker, get)?;");
}
if !functions.is_empty() {
uwriteln!(self.src, "Self::add_root_to_linker(linker, get)?;");
}
uwriteln!(self.src, "Ok(())\n}}");
if functions.is_empty() {
return;
}
uwrite!(
self.src,
"
pub fn add_root_to_linker<T, U>(
linker: &mut wasmtime::component::Linker<T>,
get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
) -> anyhow::Result<()>
where U: {world_trait}{maybe_send}
{{
let mut linker = linker.root();
",
);
for add_to_linker in functions {
self.src.push_str(add_to_linker);
self.src.push_str("\n");
}
uwriteln!(self.src, "Ok(())\n}}");
}
}
struct InterfaceGenerator<'a> {
src: Source,
gen: &'a mut Wasmtime,
iface: &'a Interface,
resolve: &'a Resolve,
default_param_mode: TypeMode,
types: Types,
current_interface: Option<InterfaceId>,
}
impl<'a> InterfaceGenerator<'a> {
fn new(
gen: &'a mut Wasmtime,
iface: &'a Interface,
resolve: &'a Resolve,
default_param_mode: TypeMode,
) -> InterfaceGenerator<'a> {
let mut types = Types::default();
types.analyze(iface);
InterfaceGenerator {
src: Source::default(),
gen,
iface,
types,
resolve,
default_param_mode,
current_interface: None,
}
}
fn types(&mut self) {
for (id, ty) in self.iface.types.iter() {
let name = match &ty.name {
Some(name) => name,
None => continue,
};
fn types(&mut self, id: InterfaceId) {
for (name, id) in self.resolve.interfaces[id].types.iter() {
let id = *id;
let ty = &self.resolve.types[id];
match &ty.kind {
TypeDefKind::Record(record) => self.type_record(id, name, record, &ty.docs),
TypeDefKind::Flags(flags) => self.type_flags(id, name, flags, &ty.docs),
@@ -362,6 +475,7 @@ impl<'a> InterfaceGenerator<'a> {
TypeDefKind::Type(t) => self.type_alias(id, name, t, &ty.docs),
TypeDefKind::Future(_) => todo!("generate for future"),
TypeDefKind::Stream(_) => todo!("generate for stream"),
TypeDefKind::Unknown => unreachable!(),
}
}
}
@@ -790,33 +904,39 @@ impl<'a> InterfaceGenerator<'a> {
}
}
fn special_case_trappable_error(&self, results: &Results) -> Option<(Result_, String)> {
fn special_case_trappable_error(
&self,
owner: TypeOwner,
results: &Results,
) -> Option<(&'a Result_, String)> {
// We fillin a special trappable error type in the case when a function has just one
// result, which is itself a `result<a, e>`, and the `e` is *not* a primitive
// (i.e. defined in std) type, and matches the typename given by the user.
let mut i = results.iter_types();
if i.len() == 1 {
match i.next().unwrap() {
Type::Id(id) => match &self.iface.types[*id].kind {
TypeDefKind::Result(r) => match r.err {
Some(Type::Id(error_typeid)) => self
.gen
.trappable_error_types(&self.iface)
.find(|(_, wit_error_typeid, _)| error_typeid == **wit_error_typeid)
.map(|(_, _, rust_errortype)| (r.clone(), rust_errortype.clone())),
_ => None,
},
_ => None,
},
_ => None,
}
} else {
None
let id = match i.next()? {
Type::Id(id) => id,
_ => return None,
};
if i.next().is_some() {
return None;
}
let result = match &self.resolve.types[*id].kind {
TypeDefKind::Result(r) => r,
_ => return None,
};
let error_typeid = match result.err? {
Type::Id(id) => id,
_ => return None,
};
self.trappable_error_types(owner)
.find(|(wit_error_typeid, _)| error_typeid == *wit_error_typeid)
.map(|(_, rust_errortype)| (result, rust_errortype))
}
fn generate_add_to_linker(&mut self, name: &str) {
fn generate_add_to_linker(&mut self, id: InterfaceId, name: &str) {
let iface = &self.resolve.interfaces[id];
let camel = name.to_upper_camel_case();
let owner = TypeOwner::Interface(id);
if self.gen.opts.async_ {
uwriteln!(self.src, "#[wasmtime::component::__internal::async_trait]")
@@ -824,47 +944,8 @@ impl<'a> InterfaceGenerator<'a> {
// Generate the `pub trait` which represents the host functionality for
// this import.
uwriteln!(self.src, "pub trait {camel}: Sized {{");
for func in self.iface.functions.iter() {
self.rustdoc(&func.docs);
if self.gen.opts.async_ {
self.push_str("async ");
}
self.push_str("fn ");
self.push_str(&to_rust_ident(&func.name));
self.push_str("(&mut self, ");
for (name, param) in func.params.iter() {
let name = to_rust_ident(name);
self.push_str(&name);
self.push_str(": ");
self.print_ty(param, TypeMode::Owned);
self.push_str(",");
}
self.push_str(")");
self.push_str(" -> ");
if let Some((r, error_typename)) = self.special_case_trappable_error(&func.results) {
// Functions which have a single result `result<ok,err>` get special
// cased to use the host_wasmtime_rust::Error<err>, making it possible
// for them to trap or use `?` to propogate their errors
self.push_str("Result<");
if let Some(ok) = r.ok {
self.print_ty(&ok, TypeMode::Owned);
} else {
self.push_str("()");
}
self.push_str(",");
self.push_str(&error_typename);
self.push_str(">");
} else {
// All other functions get their return values wrapped in an anyhow::Result.
// Returning the anyhow::Error case can be used to trap.
self.push_str("anyhow::Result<");
self.print_result_ty(&func.results, TypeMode::Owned);
self.push_str(">");
}
self.push_str(";\n");
for (_, func) in iface.functions.iter() {
self.generate_function_trait_sig(owner, func);
}
uwriteln!(self.src, "}}");
@@ -885,10 +966,17 @@ impl<'a> InterfaceGenerator<'a> {
"
);
uwriteln!(self.src, "let mut inst = linker.instance(\"{name}\")?;");
for func in self.iface.functions.iter() {
for (_, func) in iface.functions.iter() {
self.generate_add_function_to_linker(owner, func, "inst");
}
uwriteln!(self.src, "Ok(())");
uwriteln!(self.src, "}}");
}
fn generate_add_function_to_linker(&mut self, owner: TypeOwner, func: &Function, linker: &str) {
uwrite!(
self.src,
"inst.{}(\"{}\", ",
"{linker}.{}(\"{}\", ",
if self.gen.opts.async_ {
"func_wrap_async"
} else {
@@ -896,14 +984,11 @@ impl<'a> InterfaceGenerator<'a> {
},
func.name
);
self.generate_guest_import_closure(func);
self.generate_guest_import_closure(owner, func);
uwriteln!(self.src, ")?;")
}
uwriteln!(self.src, "Ok(())");
uwriteln!(self.src, "}}");
}
fn generate_guest_import_closure(&mut self, func: &Function) {
fn generate_guest_import_closure(&mut self, owner: TypeOwner, func: &Function) {
// Generate the closure that's passed to a `Linker`, the final piece of
// codegen here.
self.src
@@ -936,7 +1021,15 @@ impl<'a> InterfaceGenerator<'a> {
);
let _enter = span.enter();
",
self.iface.name, func.name,
match owner {
TypeOwner::Interface(id) => self.resolve.interfaces[id]
.name
.as_deref()
.unwrap_or("<no module>"),
TypeOwner::World(id) => &self.resolve.worlds[id].name,
TypeOwner::None => "<no owner>",
},
func.name,
));
}
@@ -952,7 +1045,10 @@ impl<'a> InterfaceGenerator<'a> {
uwrite!(self.src, ");\n");
}
if self.special_case_trappable_error(&func.results).is_some() {
if self
.special_case_trappable_error(owner, &func.results)
.is_some()
{
uwrite!(
self.src,
"match r {{
@@ -977,10 +1073,51 @@ impl<'a> InterfaceGenerator<'a> {
}
}
fn extract_typed_functions(&mut self) -> Vec<(String, String)> {
fn generate_function_trait_sig(&mut self, owner: TypeOwner, func: &Function) {
self.rustdoc(&func.docs);
if self.gen.opts.async_ {
self.push_str("async ");
}
self.push_str("fn ");
self.push_str(&to_rust_ident(&func.name));
self.push_str("(&mut self, ");
for (name, param) in func.params.iter() {
let name = to_rust_ident(name);
self.push_str(&name);
self.push_str(": ");
self.print_ty(param, TypeMode::Owned);
self.push_str(",");
}
self.push_str(")");
self.push_str(" -> ");
if let Some((r, error_typename)) = self.special_case_trappable_error(owner, &func.results) {
// Functions which have a single result `result<ok,err>` get special
// cased to use the host_wasmtime_rust::Error<err>, making it possible
// for them to trap or use `?` to propogate their errors
self.push_str("Result<");
if let Some(ok) = r.ok {
self.print_ty(&ok, TypeMode::Owned);
} else {
self.push_str("()");
}
self.push_str(",");
self.push_str(&error_typename);
self.push_str(">");
} else {
// All other functions get their return values wrapped in an anyhow::Result.
// Returning the anyhow::Error case can be used to trap.
self.push_str("anyhow::Result<");
self.print_result_ty(&func.results, TypeMode::Owned);
self.push_str(">");
}
self.push_str(";\n");
}
fn extract_typed_function(&mut self, func: &Function) -> (String, String) {
let prev = mem::take(&mut self.src);
let mut ret = Vec::new();
for func in self.iface.functions.iter() {
let snake = func.name.to_snake_case();
uwrite!(self.src, "*__exports.typed_func::<(");
for (_, ty) in func.params.iter() {
@@ -996,8 +1133,7 @@ impl<'a> InterfaceGenerator<'a> {
self.src.push_str(&func.name);
self.src.push_str("\")?.func()");
ret.push((snake, mem::take(&mut self.src).to_string()));
}
let ret = (snake, mem::take(&mut self.src).to_string());
self.src = prev;
return ret;
}
@@ -1097,17 +1233,49 @@ impl<'a> InterfaceGenerator<'a> {
self.src.push_str("}\n");
}
fn generate_trappable_error_types(&mut self) {
for (wit_typename, wit_type, trappable_type) in self.gen.trappable_error_types(&self.iface)
{
let info = self.info(*wit_type);
if self.lifetime_for(&info, TypeMode::Owned).is_some() {
panic!(
"type {:?} in interface {:?} is not 'static",
wit_typename, self.iface.name
)
fn trappable_error_types(
&self,
owner: TypeOwner,
) -> impl Iterator<Item = (TypeId, String)> + '_ {
let resolve = self.resolve;
self.gen
.opts
.trappable_error_type
.iter()
.filter_map(move |trappable| {
if let Some(name) = &trappable.wit_owner {
let owner_name = match owner {
TypeOwner::Interface(id) => resolve.interfaces[id].name.as_deref()?,
TypeOwner::World(id) => &resolve.worlds[id].name,
TypeOwner::None => return None,
};
if owner_name != name {
return None;
}
let abi_type = self.param_name(*wit_type);
}
let id = match owner {
TypeOwner::Interface(id) => {
*resolve.interfaces[id].types.get(&trappable.wit_name)?
}
// TODO: right now worlds can't have types defined within
// them but that's just a temporary limitation of
// `wit-parser`. Once that's filled in this should be
// replaced with a type-lookup in the world.
TypeOwner::World(_id) => unimplemented!(),
TypeOwner::None => return None,
};
Some((id, trappable.rust_name.clone()))
})
}
fn generate_trappable_error_types(&mut self, owner: TypeOwner) {
for (wit_type, trappable_type) in self.trappable_error_types(owner).collect::<Vec<_>>() {
let info = self.info(wit_type);
if self.lifetime_for(&info, TypeMode::Owned).is_some() {
panic!("wit error for {trappable_type} is not 'static")
}
let abi_type = self.param_name(wit_type);
uwriteln!(
self.src,
@@ -1164,8 +1332,12 @@ impl<'a> InterfaceGenerator<'a> {
}
impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
fn iface(&self) -> &'a Interface {
self.iface
fn resolve(&self) -> &'a Resolve {
self.resolve
}
fn current_interface(&self) -> Option<InterfaceId> {
self.current_interface
}
fn default_param_mode(&self) -> TypeMode {
@@ -1177,6 +1349,6 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
}
fn info(&self, ty: TypeId) -> TypeInfo {
self.types.get(ty)
self.gen.types.get(ty)
}
}

View File

@@ -11,11 +11,12 @@ pub enum TypeMode {
}
pub trait RustGenerator<'a> {
fn iface(&self) -> &'a Interface;
fn resolve(&self) -> &'a Resolve;
fn push_str(&mut self, s: &str);
fn info(&self, ty: TypeId) -> TypeInfo;
fn default_param_mode(&self) -> TypeMode;
fn current_interface(&self) -> Option<InterfaceId>;
fn print_ty(&mut self, ty: &Type, mode: TypeMode) {
match ty {
@@ -56,25 +57,41 @@ pub trait RustGenerator<'a> {
fn print_tyid(&mut self, id: TypeId, mode: TypeMode) {
let info = self.info(id);
let lt = self.lifetime_for(&info, mode);
let ty = &self.iface().types[id];
let ty = &self.resolve().types[id];
if ty.name.is_some() {
let name = if lt.is_some() {
self.param_name(id)
} else {
self.result_name(id)
};
if let TypeOwner::Interface(id) = ty.owner {
if let Some(name) = &self.resolve().interfaces[id].name {
match self.current_interface() {
Some(cur) if cur == id => {}
Some(_other) => {
self.push_str("super::");
self.push_str(&name.to_snake_case());
self.push_str("::");
}
None => {
self.push_str(&name.to_snake_case());
self.push_str("::");
}
}
}
}
self.push_str(&name);
// If the type recursively owns data and it's a
// variant/record/list, then we need to place the
// lifetime parameter on the type as well.
if info.has_list && needs_generics(self.iface(), &ty.kind) {
if info.has_list && needs_generics(self.resolve(), &ty.kind) {
self.print_generics(lt);
}
return;
fn needs_generics(iface: &Interface, ty: &TypeDefKind) -> bool {
fn needs_generics(resolve: &Resolve, ty: &TypeDefKind) -> bool {
match ty {
TypeDefKind::Variant(_)
| TypeDefKind::Record(_)
@@ -87,9 +104,12 @@ pub trait RustGenerator<'a> {
| TypeDefKind::Enum(_)
| TypeDefKind::Tuple(_)
| TypeDefKind::Union(_) => true,
TypeDefKind::Type(Type::Id(t)) => needs_generics(iface, &iface.types[*t].kind),
TypeDefKind::Type(Type::Id(t)) => {
needs_generics(resolve, &resolve.types[*t].kind)
}
TypeDefKind::Type(Type::String) => true,
TypeDefKind::Type(_) => false,
TypeDefKind::Unknown => unreachable!(),
}
}
}
@@ -150,6 +170,7 @@ pub trait RustGenerator<'a> {
}
TypeDefKind::Type(t) => self.print_ty(t, mode),
TypeDefKind::Unknown => unreachable!(),
}
}
@@ -214,7 +235,7 @@ pub trait RustGenerator<'a> {
Type::Char => out.push_str("Char"),
Type::String => out.push_str("String"),
Type::Id(id) => {
let ty = &self.iface().types[*id];
let ty = &self.resolve().types[*id];
match &ty.name {
Some(name) => out.push_str(&name.to_upper_camel_case()),
None => match &ty.kind {
@@ -244,6 +265,7 @@ pub trait RustGenerator<'a> {
TypeDefKind::Variant(_) => out.push_str("Variant"),
TypeDefKind::Enum(_) => out.push_str("Enum"),
TypeDefKind::Union(_) => out.push_str("Union"),
TypeDefKind::Unknown => unreachable!(),
},
}
}
@@ -309,7 +331,7 @@ pub trait RustGenerator<'a> {
fn param_name(&self, ty: TypeId) -> String {
let info = self.info(ty);
let name = self.iface().types[ty]
let name = self.resolve().types[ty]
.name
.as_ref()
.unwrap()
@@ -323,7 +345,7 @@ pub trait RustGenerator<'a> {
fn result_name(&self, ty: TypeId) -> String {
let info = self.info(ty);
let name = self.iface().types[ty]
let name = self.resolve().types[ty]
.name
.as_ref()
.unwrap()

View File

@@ -34,14 +34,29 @@ impl std::ops::BitOrAssign for TypeInfo {
}
impl Types {
pub fn analyze(&mut self, iface: &Interface) {
for (t, _) in iface.types.iter() {
self.type_id_info(iface, t);
pub fn analyze(&mut self, resolve: &Resolve, world: WorldId) {
let world = &resolve.worlds[world];
for (_, item) in world.imports.iter().chain(world.exports.iter()) {
match item {
WorldItem::Function(f) => self.type_info_func(resolve, f),
WorldItem::Interface(id) => {
let iface = &resolve.interfaces[*id];
for (_, t) in iface.types.iter() {
self.type_id_info(resolve, *t);
}
for f in iface.functions.iter() {
for (_, ty) in f.params.iter() {
for (_, f) in iface.functions.iter() {
self.type_info_func(resolve, f);
}
}
}
}
}
fn type_info_func(&mut self, resolve: &Resolve, func: &Function) {
for (_, ty) in func.params.iter() {
self.set_param_result_ty(
iface,
resolve,
ty,
TypeInfo {
param: true,
@@ -49,9 +64,9 @@ impl Types {
},
);
}
for ty in f.results.iter_types() {
for ty in func.results.iter_types() {
self.set_param_result_ty(
iface,
resolve,
ty,
TypeInfo {
result: true,
@@ -60,133 +75,136 @@ impl Types {
);
}
}
}
pub fn get(&self, id: TypeId) -> TypeInfo {
self.type_info[&id]
}
fn type_id_info(&mut self, iface: &Interface, ty: TypeId) -> TypeInfo {
fn type_id_info(&mut self, resolve: &Resolve, ty: TypeId) -> TypeInfo {
if let Some(info) = self.type_info.get(&ty) {
return *info;
}
let mut info = TypeInfo::default();
match &iface.types[ty].kind {
match &resolve.types[ty].kind {
TypeDefKind::Record(r) => {
for field in r.fields.iter() {
info |= self.type_info(iface, &field.ty);
info |= self.type_info(resolve, &field.ty);
}
}
TypeDefKind::Tuple(t) => {
for ty in t.types.iter() {
info |= self.type_info(iface, ty);
info |= self.type_info(resolve, ty);
}
}
TypeDefKind::Flags(_) => {}
TypeDefKind::Enum(_) => {}
TypeDefKind::Variant(v) => {
for case in v.cases.iter() {
info |= self.optional_type_info(iface, case.ty.as_ref());
info |= self.optional_type_info(resolve, case.ty.as_ref());
}
}
TypeDefKind::List(ty) => {
info = self.type_info(iface, ty);
info = self.type_info(resolve, ty);
info.has_list = true;
}
TypeDefKind::Type(ty) => {
info = self.type_info(iface, ty);
info = self.type_info(resolve, ty);
}
TypeDefKind::Option(ty) => {
info = self.type_info(iface, ty);
info = self.type_info(resolve, ty);
}
TypeDefKind::Result(r) => {
info = self.optional_type_info(iface, r.ok.as_ref());
info |= self.optional_type_info(iface, r.err.as_ref());
info = self.optional_type_info(resolve, r.ok.as_ref());
info |= self.optional_type_info(resolve, r.err.as_ref());
}
TypeDefKind::Union(u) => {
for case in u.cases.iter() {
info |= self.type_info(iface, &case.ty);
info |= self.type_info(resolve, &case.ty);
}
}
TypeDefKind::Future(ty) => {
info = self.optional_type_info(iface, ty.as_ref());
info = self.optional_type_info(resolve, ty.as_ref());
}
TypeDefKind::Stream(stream) => {
info = self.optional_type_info(iface, stream.element.as_ref());
info |= self.optional_type_info(iface, stream.end.as_ref());
info = self.optional_type_info(resolve, stream.element.as_ref());
info |= self.optional_type_info(resolve, stream.end.as_ref());
}
TypeDefKind::Unknown => unreachable!(),
}
self.type_info.insert(ty, info);
info
}
fn type_info(&mut self, iface: &Interface, ty: &Type) -> TypeInfo {
fn type_info(&mut self, resolve: &Resolve, ty: &Type) -> TypeInfo {
let mut info = TypeInfo::default();
match ty {
Type::String => info.has_list = true,
Type::Id(id) => return self.type_id_info(iface, *id),
Type::Id(id) => return self.type_id_info(resolve, *id),
_ => {}
}
info
}
fn optional_type_info(&mut self, iface: &Interface, ty: Option<&Type>) -> TypeInfo {
fn optional_type_info(&mut self, resolve: &Resolve, ty: Option<&Type>) -> TypeInfo {
match ty {
Some(ty) => self.type_info(iface, ty),
Some(ty) => self.type_info(resolve, ty),
None => TypeInfo::default(),
}
}
fn set_param_result_id(&mut self, iface: &Interface, ty: TypeId, info: TypeInfo) {
match &iface.types[ty].kind {
fn set_param_result_id(&mut self, resolve: &Resolve, ty: TypeId, info: TypeInfo) {
match &resolve.types[ty].kind {
TypeDefKind::Record(r) => {
for field in r.fields.iter() {
self.set_param_result_ty(iface, &field.ty, info)
self.set_param_result_ty(resolve, &field.ty, info)
}
}
TypeDefKind::Tuple(t) => {
for ty in t.types.iter() {
self.set_param_result_ty(iface, ty, info)
self.set_param_result_ty(resolve, ty, info)
}
}
TypeDefKind::Flags(_) => {}
TypeDefKind::Enum(_) => {}
TypeDefKind::Variant(v) => {
for case in v.cases.iter() {
self.set_param_result_optional_ty(iface, case.ty.as_ref(), info)
self.set_param_result_optional_ty(resolve, case.ty.as_ref(), info)
}
}
TypeDefKind::List(ty) | TypeDefKind::Type(ty) | TypeDefKind::Option(ty) => {
self.set_param_result_ty(iface, ty, info)
self.set_param_result_ty(resolve, ty, info)
}
TypeDefKind::Result(r) => {
self.set_param_result_optional_ty(iface, r.ok.as_ref(), info);
self.set_param_result_optional_ty(resolve, r.ok.as_ref(), info);
let mut info2 = info;
info2.error = info.result;
self.set_param_result_optional_ty(iface, r.err.as_ref(), info2);
self.set_param_result_optional_ty(resolve, r.err.as_ref(), info2);
}
TypeDefKind::Union(u) => {
for case in u.cases.iter() {
self.set_param_result_ty(iface, &case.ty, info)
self.set_param_result_ty(resolve, &case.ty, info)
}
}
TypeDefKind::Future(ty) => self.set_param_result_optional_ty(iface, ty.as_ref(), info),
TypeDefKind::Future(ty) => {
self.set_param_result_optional_ty(resolve, ty.as_ref(), info)
}
TypeDefKind::Stream(stream) => {
self.set_param_result_optional_ty(iface, stream.element.as_ref(), info);
self.set_param_result_optional_ty(iface, stream.end.as_ref(), info);
self.set_param_result_optional_ty(resolve, stream.element.as_ref(), info);
self.set_param_result_optional_ty(resolve, stream.end.as_ref(), info);
}
TypeDefKind::Unknown => unreachable!(),
}
}
fn set_param_result_ty(&mut self, iface: &Interface, ty: &Type, info: TypeInfo) {
fn set_param_result_ty(&mut self, resolve: &Resolve, ty: &Type, info: TypeInfo) {
match ty {
Type::Id(id) => {
self.type_id_info(iface, *id);
self.type_id_info(resolve, *id);
let cur = self.type_info.get_mut(id).unwrap();
let prev = *cur;
*cur |= info;
if prev != *cur {
self.set_param_result_id(iface, *id, info);
self.set_param_result_id(resolve, *id, info);
}
}
_ => {}
@@ -195,12 +213,12 @@ impl Types {
fn set_param_result_optional_ty(
&mut self,
iface: &Interface,
resolve: &Resolve,
ty: Option<&Type>,
info: TypeInfo,
) {
match ty {
Some(ty) => self.set_param_result_ty(iface, ty, info),
Some(ty) => self.set_param_result_ty(resolve, ty, info),
None => (),
}
}

View File

@@ -585,6 +585,12 @@ criteria = "safe-to-deploy"
version = "0.20.0"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasm-encoder]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
version = "0.21.0"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasm-encoder]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
@@ -645,6 +651,12 @@ criteria = "safe-to-run"
version = "0.2.13"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasm-mutate]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-run"
version = "0.2.14"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasm-smith]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
@@ -699,6 +711,12 @@ criteria = "safe-to-run"
version = "0.11.10"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasm-smith]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-run"
version = "0.11.11"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasmi]]
who = "Robin Freyler <robin.freyler@gmail.com>"
criteria = "safe-to-run"
@@ -801,6 +819,12 @@ criteria = "safe-to-deploy"
version = "0.96.0"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasmparser]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
version = "0.97.0"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasmparser-nostd]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-run"
@@ -866,6 +890,12 @@ criteria = "safe-to-deploy"
version = "0.2.45"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wasmprinter]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
version = "0.2.46"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wast]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
@@ -920,6 +950,12 @@ criteria = "safe-to-deploy"
version = "50.0.0"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wast]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
version = "51.0.0"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wat]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
@@ -956,6 +992,12 @@ criteria = "safe-to-deploy"
version = "1.0.52"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wat]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
version = "1.0.53"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wat]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
@@ -1028,3 +1070,9 @@ criteria = "safe-to-deploy"
version = "0.3.1"
notes = "The Bytecode Alliance is the author of this crate."
[[audits.wit-parser]]
who = "Alex Crichton <alex@alexcrichton.com>"
criteria = "safe-to-deploy"
version = "0.4.0"
notes = "The Bytecode Alliance is the author of this crate."

View File

@@ -12,14 +12,12 @@ mod no_imports {
wasmtime::component::bindgen!({
inline: "
world no-imports {
default world no-imports {
export foo: interface {
foo: func()
}
default export interface {
bar: func()
}
export bar: func()
}
",
});
@@ -59,14 +57,12 @@ mod one_import {
wasmtime::component::bindgen!({
inline: "
world one-import {
default world one-import {
import foo: interface {
foo: func()
}
default export interface {
bar: func()
}
export bar: func()
}
",
});

View File

@@ -9,14 +9,12 @@ mod empty_error {
use super::*;
wasmtime::component::bindgen!({
inline: "
world result-playground {
default world result-playground {
import imports: interface {
empty-error: func(a: float64) -> result<float64>
}
default export interface {
empty-error: func(a: float64) -> result<float64>
}
export empty-error: func(a: float64) -> result<float64>
}",
});
@@ -108,14 +106,12 @@ mod string_error {
use super::*;
wasmtime::component::bindgen!({
inline: "
world result-playground {
default world result-playground {
import imports: interface {
string-error: func(a: float64) -> result<float64, string>
}
default export interface {
string-error: func(a: float64) -> result<float64, string>
}
export string-error: func(a: float64) -> result<float64, string>
}",
});
@@ -223,9 +219,9 @@ mod enum_error {
enum e1 { a, b, c }
enum-error: func(a: float64) -> result<float64, e1>
}
world result-playground {
import imports: imports
default export interface {
default world result-playground {
import imports: self.imports
export foo: interface {
enum e1 { a, b, c }
enum-error: func(a: float64) -> result<float64, e1>
}
@@ -273,11 +269,14 @@ mod enum_error {
(with "libc" (instance $libc))
))
(func $f_enum_error
(export "enum-error")
(param "a" float64)
(result (result float64 (error (enum "a" "b" "c"))))
(canon lift (core func $i "core_enum_error_export") (memory $libc "memory"))
)
(instance (export "foo")
(export "enum-error" (func $f_enum_error))
)
)
"#
),
@@ -328,6 +327,7 @@ mod enum_error {
assert_eq!(
results
.foo()
.enum_error(&mut store, 0.0)
.expect("no trap")
.expect("no error returned"),
@@ -335,13 +335,18 @@ mod enum_error {
);
let e = results
.foo()
.enum_error(&mut store, 1.0)
.expect("no trap")
.err()
.expect("error returned");
assert_eq!(e, enum_error::E1::A);
assert_eq!(e, enum_error::foo::E1::A);
let e = results.enum_error(&mut store, 2.0).err().expect("trap");
let e = results
.foo()
.enum_error(&mut store, 2.0)
.err()
.expect("trap");
assert_eq!(
format!("{}", e.source().expect("trap message is stored in source")),
"MyTrap"
@@ -361,9 +366,9 @@ mod record_error {
record e2 { line: u32, col: u32 }
record-error: func(a: float64) -> result<float64, e2>
}
world result-playground {
import imports: imports
default export interface {
default world result-playground {
import imports: self.imports
export foo: interface {
record e2 { line: u32, col: u32 }
record-error: func(a: float64) -> result<float64, e2>
}
@@ -411,11 +416,14 @@ mod record_error {
(with "libc" (instance $libc))
))
(func $f_record_error
(export "record-error")
(param "a" float64)
(result (result float64 (error (record (field "line" u32) (field "col" u32)))))
(canon lift (core func $i "core_record_error_export") (memory $libc "memory"))
)
(instance (export "foo")
(export "record-error" (func $f_record_error))
)
)
"#
),
@@ -447,6 +455,7 @@ mod record_error {
assert_eq!(
results
.foo()
.record_error(&mut store, 0.0)
.expect("no trap")
.expect("no error returned"),
@@ -454,19 +463,24 @@ mod record_error {
);
let e = results
.foo()
.record_error(&mut store, 1.0)
.expect("no trap")
.err()
.expect("error returned");
assert!(matches!(
e,
record_error::E2 {
record_error::foo::E2 {
line: 420,
col: 1312
}
));
let e = results.record_error(&mut store, 2.0).err().expect("trap");
let e = results
.foo()
.record_error(&mut store, 2.0)
.err()
.expect("trap");
assert_eq!(
format!("{}", e.source().expect("trap message is stored in source")),
"record_error: trap"
@@ -486,9 +500,9 @@ mod variant_error {
variant e3 { E1(e1), E2(e2) }
variant-error: func(a: float64) -> result<float64, e3>
}
world result-playground {
import imports: imports
default export interface {
default world result-playground {
import imports: self.imports
export foo: interface {
enum e1 { a, b, c }
record e2 { line: u32, col: u32 }
variant e3 { E1(e1), E2(e2) }
@@ -538,11 +552,14 @@ mod variant_error {
(with "libc" (instance $libc))
))
(func $f_variant_error
(export "variant-error")
(param "a" float64)
(result (result float64 (error (variant (case "E1" (enum "a" "b" "c")) (case "E2"(record (field "line" u32) (field "col" u32)))))))
(canon lift (core func $i "core_variant_error_export") (memory $libc "memory"))
)
(instance (export "foo")
(export "variant-error" (func $f_variant_error))
)
)
"#
),
@@ -574,6 +591,7 @@ mod variant_error {
assert_eq!(
results
.foo()
.variant_error(&mut store, 0.0)
.expect("no trap")
.expect("no error returned"),
@@ -581,19 +599,24 @@ mod variant_error {
);
let e = results
.foo()
.variant_error(&mut store, 1.0)
.expect("no trap")
.err()
.expect("error returned");
assert!(matches!(
e,
variant_error::E3::E2(variant_error::E2 {
variant_error::foo::E3::E2(variant_error::foo::E2 {
line: 420,
col: 1312
})
));
let e = results.variant_error(&mut store, 2.0).err().expect("trap");
let e = results
.foo()
.variant_error(&mut store, 2.0)
.err()
.expect("trap");
assert_eq!(
format!("{}", e.source().expect("trap message is stored in source")),
"variant_error: trap"