Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ wasm-encoder = { workspace = true }
cranelift-native = { workspace = true }
futures = { workspace = true }

# Should only be used for tests that are explicitly testing `anyhow::Error` and
# `wasmtime::Error` integration and conversion. Otherwise, always prefer to use
# `wasmtime::Error`.
anyhow-for-testing = { workspace = true }

[target.'cfg(windows)'.dev-dependencies]
windows-sys = { workspace = true, features = ["Win32_System_Memory"] }

Expand Down Expand Up @@ -364,6 +369,7 @@ object = { version = "0.37.3", default-features = false, features = ['read_core'
gimli = { version = "0.32.3", default-features = false, features = ['read'] }
addr2line = { version = "0.25.1", default-features = false }
anyhow = { version = "1.0.100", default-features = false }
anyhow-for-testing = { package = "anyhow", version = "1.0.100", default-features = false }
windows-sys = "0.61.2"
env_logger = "0.11.5"
log = { version = "0.4.28", default-features = false }
Expand Down
9 changes: 9 additions & 0 deletions crates/component-macro/src/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ impl Parse for Config {
Opt::WasmtimeCrate(f) => {
opts.wasmtime_crate = Some(f.into_token_stream().to_string())
}
Opt::Anyhow(val) => {
opts.anyhow = val;
}
Opt::IncludeGeneratedCodeFromFile(i) => include_generated_code_from_file = i,
Opt::Imports(config, span) => {
if imports_configured {
Expand Down Expand Up @@ -253,6 +256,7 @@ mod kw {
syn::custom_keyword!(skip_mut_forwarding_impls);
syn::custom_keyword!(require_store_data_send);
syn::custom_keyword!(wasmtime_crate);
syn::custom_keyword!(anyhow);
syn::custom_keyword!(include_generated_code_from_file);
syn::custom_keyword!(debug);
syn::custom_keyword!(imports);
Expand All @@ -277,6 +281,7 @@ enum Opt {
SkipMutForwardingImpls(bool),
RequireStoreDataSend(bool),
WasmtimeCrate(syn::Path),
Anyhow(bool),
IncludeGeneratedCodeFromFile(bool),
Debug(bool),
Imports(FunctionConfig, Span),
Expand Down Expand Up @@ -403,6 +408,10 @@ impl Parse for Opt {
input.parse::<kw::wasmtime_crate>()?;
input.parse::<Token![:]>()?;
Ok(Opt::WasmtimeCrate(input.parse()?))
} else if l.peek(kw::anyhow) {
input.parse::<kw::anyhow>()?;
input.parse::<Token![:]>()?;
Ok(Opt::Anyhow(input.parse::<syn::LitBool>()?.value))
} else if l.peek(kw::include_generated_code_from_file) {
input.parse::<kw::include_generated_code_from_file>()?;
input.parse::<Token![:]>()?;
Expand Down
10 changes: 10 additions & 0 deletions crates/wasmtime/src/runtime/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,16 @@ pub(crate) use self::store::ComponentStoreData;
/// // By default this is `wasmtime`.
/// wasmtime_crate: path::to::wasmtime,
///
/// // Whether to use `anyhow::Result` for trappable host-defined function
/// // imports, rather than `wasmtime::Result`.
/// //
/// // By default, this is false and `wasmtime::Result` is used instead of
/// // `anyhow::Result`.
/// //
/// // When enabled, the generated code requires the `"anyhow"` cargo feature
/// // to also be enabled in the `wasmtime` crate.
/// anyhow: false,
///
/// // This is an in-source alternative to using `WASMTIME_DEBUG_BINDGEN`.
/// //
/// // Note that if this option is specified then the compiler will always
Expand Down
18 changes: 17 additions & 1 deletion crates/wit-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ pub struct Opts {
/// Path to the `wasmtime` crate if it's not the default path.
pub wasmtime_crate: Option<String>,

/// Whether to use `anyhow::Result` for trappable host-defined function
/// imports.
///
/// By default, `wasmtime::Result` is used instead of `anyhow::Result`.
///
/// When enabled, the generated code requires the `"anyhow"` cargo feature
/// to also be enabled in the `wasmtime` crate.
pub anyhow: bool,

/// If true, write the generated bindings to a file for better error
/// messages from `rustc`.
///
Expand Down Expand Up @@ -2595,7 +2604,14 @@ impl<'a> InterfaceGenerator<'a> {
}},))"
);
} else if func.result.is_some() {
uwrite!(self.src, "Ok((r?,))\n");
if self.generator.opts.anyhow {
uwrite!(
self.src,
"Ok(({wt}::ToWasmtimeResult::to_wasmtime_result(r)?,))\n"
);
} else {
uwrite!(self.src, "Ok((r?,))\n");
}
} else {
uwrite!(self.src, "r\n");
}
Expand Down
93 changes: 93 additions & 0 deletions tests/all/component_model/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,3 +910,96 @@ mod unstable_import {
Ok(())
}
}

mod anyhow_errors {
use super::*;
use crate::ErrorExt;
use wasmtime::component::HasSelf;
use wasmtime::error::Context as _;

wasmtime::component::bindgen!({
anyhow: true,
imports: { default: trappable },
inline: "
package foo:foo;

interface my-interface {
ok: func() -> u32;
trap: func() -> u32;
}

world my-world {
import my-interface;
export ok: func() -> u32;
export trap: func() -> u32;
}
",
});

#[test]
fn run() -> Result<()> {
let engine = engine();

let component = Component::new(
&engine,
r#"
(component
(import "foo:foo/my-interface" (instance $i
(export "ok" (func (result u32)))
(export "trap" (func (result u32)))
))

(core module $m
(import "" "ok" (func (result i32)))
(import "" "trap" (func (result i32)))
(export "ok" (func 0))
(export "trap" (func 1))
)

(core func $ok (canon lower (func $i "ok")))
(core func $trap (canon lower (func $i "trap")))

(core instance $r (instantiate $m
(with "" (instance (export "ok" (func $ok))
(export "trap" (func $trap))))
))

(func (export "ok") (result u32) (canon lift (core func $r "ok")))
(func (export "trap") (result u32) (canon lift (core func $r "trap")))
)
"#,
)?;

#[derive(Default)]
struct MyHost;

impl foo::foo::my_interface::Host for MyHost {
// NB: these must return an `anyhow::Result` since we `bindgen!`ed
// with `anyhow: true`.
fn ok(&mut self) -> anyhow_for_testing::Result<u32> {
Ok(42)
}
fn trap(&mut self) -> anyhow_for_testing::Result<u32> {
anyhow_for_testing::bail!("anyhow error")
}
}

let mut linker = Linker::new(&engine);
MyWorld::add_to_linker::<_, HasSelf<_>>(&mut linker, |h| h)
.context("failed to add to linker")?;
let mut store = Store::new(&engine, MyHost::default());
let instance = MyWorld::instantiate(&mut store, &component, &linker)
.context("failed to instantiate")?;

let x = instance
.call_ok(&mut store)
.context("failed to call `ok` function")?;
assert_eq!(x, 42);

let result = instance.call_trap(&mut store);
let error = result.unwrap_err();
error.assert_contains("anyhow error");

Ok(())
}
}