feat(bindgen): per-function async override for p3 (async_interfaces) (#526)#527
Merged
Conversation
…526) `rust_wasm_component_bindgen(wasi_version="p3")` hardcoded `--async all`, so every export — including plain-sync WIT functions — was lifted async. A call-return consumer (e.g. a witness MC/DC harness) cannot invoke an async-lift export, forcing a separate p2 sibling component just to expose one sync entry point (#526). Add an `async_interfaces` parameter exposing wit-bindgen's `--async` filters on the guest bindings. Default is unchanged (p3 -> ["all"], p2 -> []). Callers can now pass: [] # no --async: each export follows its WIT # signature (async-typed async, sync sync) ["-all"] # force every export sync ["-export:pkg:iface/i@0.1.0#fn"] # that export sync, others WIT-default ["pkg:iface/i@0.1.0#stream-fn"] # that export async, others WIT-default Also fix `_build_async_args` to emit `--async=VALUE` (not `--async VALUE`) so filter values beginning with `-` (`-all`, `-export:...`) aren't misparsed by clap as separate flags. Verified end-to-end: new //test/p3:hello_p3_sync builds a p3 component whose `greet` export is a plain sync `fn` (no async, no cfg dance) via async_interfaces=["-export:hello:interfaces/greeting@0.1.0#greet"]. Under the p3 default this fails to compile ("method should be `async` or return a future, but it is synchronous"); generated bindings drop from 3 async-lift exports to 0. Default p3 path (hello_p3, --async=all) still builds — no regression. Findings captured for the wit-bindgen 0.54 contract: filter names require the package version; combining `all` with a per-export `-export:` exclude is rejected ("unused async option") — use [] (WIT defaults) or the allowlist form. Refs: pulseengine/relay#202, pulseengine/witness#107 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #526.
Problem
rust_wasm_component_bindgen(wasi_version = "p3")hardcoded--async all, so every export — including functions whose WIT signature is plain sync — was lifted async ([async-lift]…/[task-return]…). A call-return consumer can't invoke an async-lift export, so relay had to stand up a whole separate p2 sibling component just to expose one sync entry point for its witness MC/DC harness.Change
Adds an
async_interfacesparameter torust_wasm_component_bindgenexposing wit-bindgen's--asyncfilters on the guest bindings (native-guest stays sync — the host has no async_support). Default is unchanged:wasi_versionasync_interfaces["all"][]Callers can now mix sync and async exports:
For #526's exact case — keep stream exports async, lift one function sync — the cleanest answer is often
async_interfaces = []: once the WIT distinguishes async-typed (stream/future) from sync functions, wit-bindgen's per-function default already does the right thing; it was only the blanketallforcing the sync ones async.Also fixes
_build_async_argsto emit--async=VALUEinstead of--async VALUE, so filter values starting with-(-all,-export:…) aren't misparsed by clap as separate flags (error: unexpected argument '-e').Verification
New
//test/p3:hello_p3_syncbuilds a p3 component whosegreetexport is implemented as a plain syncfn greet(noasync, no#[cfg]split) usingasync_interfaces = ["-export:hello:interfaces/greeting@0.1.0#greet"]:bazel test //test/p3:p3_sync_export_build_testpasses.--async=all) the same sync impl fails to compile —method should beasyncor return a future, but it is synchronous— and the generated bindings carry 3 async-lift markers forgreet; with the override they carry 0.//test/p3:hello_p3,--async=all) still builds — no regression from the arg-form change.p2_build_test,p3_build_teststill pass.wit-bindgen 0.54 contract (documented in the rule)
pkg:iface/i@0.1.0#fn).allblanket with a per-export-export:exclude is rejected (unused async option) — use[](WIT defaults) or the allowlist form instead.Refs: pulseengine/relay#202, pulseengine/witness#107
🤖 Generated with Claude Code