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
33 changes: 30 additions & 3 deletions rust/private/rust_wasm_component_bindgen.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ def rust_wasm_component_bindgen(
symmetric = False,
invert_direction = False,
bitflags_dep = "@crates//:bitflags",
async_interfaces = None,
**kwargs):
"""Builds a Rust WebAssembly component with automatic WIT binding generation.

Expand Down Expand Up @@ -507,12 +508,38 @@ def rust_wasm_component_bindgen(
bitflags = "2"

And configure crate_universe in MODULE.bazel.
async_interfaces: Optional wit-bindgen `--async` filters for the guest
(WASM) bindings, overriding the default. By default p3 lifts every
export async (`["all"]`) and p2 is fully sync (`[]`). Under the p3
default a plain-sync WIT function still becomes an async-lift export,
which a call-return consumer cannot invoke (#526). Override to mix
sync and async exports. Each entry is a wit-bindgen `--async` value;
**filter names must include the package version**, e.g.
`pkg:iface/i@0.1.0#fn`:

[] # no --async: each export
# follows its WIT signature
# (async-typed -> async,
# sync -> sync). Solves #526
# when the WIT already
# distinguishes them.
["-all"] # force every export sync
["-export:pkg:iface/i@0.1.0#fn"] # `fn` sync, others WIT-default
["pkg:iface/i@0.1.0#stream-fn"] # `stream-fn` async, others default

Note: wit-bindgen (0.54) rejects combining the `all` blanket with a
per-export `-export:` exclude ("unused async option") — use the
WIT-default (`[]`) or allowlist forms above instead. Only affects the
guest bindings; native-guest bindings are always sync (the host
runtime has no async_support).
**kwargs: Additional arguments passed to rust_wasm_component
"""

# Determine P3 async settings from wasi_version
# Determine P3 async settings from wasi_version, unless the caller supplied
# an explicit override. Default: p3 lifts all exports async, p2 is sync.
wasi_version = kwargs.get("wasi_version", "p2")
p3_async_interfaces = ["all"] if wasi_version == "p3" else []
if async_interfaces == None:
async_interfaces = ["all"] if wasi_version == "p3" else []

# Generate WIT bindings based on symmetric flag
if symmetric:
Expand Down Expand Up @@ -541,7 +568,7 @@ def rust_wasm_component_bindgen(
wit = wit,
language = "rust",
generation_mode = "guest",
async_interfaces = p3_async_interfaces,
async_interfaces = async_interfaces,
visibility = ["//visibility:private"],
)

Expand Down
23 changes: 23 additions & 0 deletions test/p3/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,27 @@ build_test(
targets = [":hello_p3"],
)

# Per-function async override (#526): a plain-sync WIT export kept sync under p3
# while the component is still p3. Without the override this fails to compile
# ("method should be `async` ... but it is synchronous"); the override forces a
# call-return lift so a sync consumer can invoke it.
rust_wasm_component_bindgen(
name = "hello_p3_sync",
srcs = ["src_p3_sync/lib.rs"],
# Force the `greet` export sync (call-return) even though the component is
# p3. The filter name requires the package version. Other exports (none
# here) would follow their WIT default.
async_interfaces = ["-export:hello:interfaces/greeting@0.1.0#greet"],
profiles = ["release"],
wasi_version = "p3",
wit = ":hello_interfaces",
)

build_test(
name = "p3_sync_export_build_test",
targets = [":hello_p3_sync"],
)

# ============================================================================
# WASI 0.3 interface WIT (stable v0.3.0) — covers wasi_p3_deps.bzl
# ============================================================================
Expand Down Expand Up @@ -102,6 +123,7 @@ test_suite(
name = "p3",
tests = [
":p3_build_test",
":p3_sync_export_build_test",
":wasi_p3_wit_build_test",
],
)
Expand All @@ -111,6 +133,7 @@ test_suite(
tests = [
":p2_build_test",
":p3_build_test",
":p3_sync_export_build_test",
":wasi_p3_wit_build_test",
],
)
19 changes: 19 additions & 0 deletions test/p3/src_p3_sync/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// P3 component exposing a SYNC export via the async_interfaces override (#526).
//
// `greet` has a plain-sync WIT signature. Under the p3 default (--async all) it
// becomes an async-lift export, and this sync `fn greet` would fail to compile
// with "method should be `async` or return a future, but it is synchronous" —
// the exact friction reported in #526. The bindgen target forces it sync with
// async_interfaces = ["all", "-export:hello:interfaces/greeting#greet"]
// so a call-return consumer (e.g. a witness MC/DC harness) can invoke it.
use hello_p3_sync_bindings::exports::hello::interfaces::greeting::Guest;

struct Component;

impl Guest for Component {
fn greet(name: String) -> String {
format!("Hello, {}! (P3 sync export)", name)
}
}

hello_p3_sync_bindings::export!(Component with_types_in hello_p3_sync_bindings);
9 changes: 7 additions & 2 deletions wit/private/wit_bindgen.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ def _build_derive_args(additional_derives):
return args

def _build_async_args(async_interfaces):
"""Build --async arguments for async interface configuration"""
"""Build --async arguments for async interface configuration.

Uses the `--async=VALUE` form rather than `--async VALUE` so that filter
values beginning with `-` (e.g. `-all`, `-export:pkg:iface/i#fn`, which force
a method sync) are not misparsed by clap as separate flags.
"""
args = []
for async_interface in async_interfaces:
args.extend(["--async", async_interface])
args.append("--async=" + async_interface)
return args

def _to_snake_case(name):
Expand Down