From 4978537f2fd9eb10cd24725c3efec03ffa77610b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 13 Nov 2025 14:08:25 -0800 Subject: [PATCH 1/4] Bake `async` into component model function types This commit is a breaking change to the component-model-async implementation in this repository. This will effectively invalidate all WASIp3 binaries created prior to this commit. Given that this feature is off-by-default everywhere (right? right?) it's expected that while this will have churn it's the appropriate time to make such a change as this. Concretely the changes here are: * Function types in the component model now reflect whether they're `async`-or-not. This is no longer WIT-level sugar. * Kebab names no longer support `[async]`, `[async method]`, or `[async static]`. * The text format now supports `(func async? (param ...))`. The binary format previously used 0x40 as the prefix byte for "this is a component function type" and now 0x43 is used as "this is an async function type". The `async_` boolean is plumbed throughout component function type locations and is required to be handled by callers. For now there is no subtyping relationship between async and non-async functions, they must be exactly the same when linking. The rationale for this change is going to be expanded on more fully in an upcoming PR to the component-model repository itself. The rough tl;dr; is that to fully support JS in/out of components the component model is going to need to require traps in some dynamic situations such as when a synchronous function-type function tries to block. This is required to respect JS's "cannot block the main thread" semantics, for example. This will be more fully explain in the component-model PR and this PR is not intended to serve as justification alone for this change. Instead this PR is intended to change/update Wasmtime as quickly as possible to understand the new binary format so implementation work can proceed as most of the work here is on the runtime side of things, not validation. One minor caveat for this change is that the `wit-dylib` crate has some tests which exercise async functionality. Those are now all disabled or set as "expect this to fail" because Wasmtime, the runner for these tests, no longer understands the binaries it's ingesting. --- Cargo.lock | 1 + crates/wasm-encoder/src/component/types.rs | 29 ++++++- crates/wasm-encoder/src/reencode/component.rs | 1 + .../wasmparser/src/readers/component/types.rs | 10 ++- crates/wasmparser/src/validator/component.rs | 36 ++++----- .../src/validator/component_types.rs | 11 +++ crates/wasmparser/src/validator/names.rs | 71 +++-------------- crates/wasmprinter/src/component.rs | 3 + crates/wast/src/component/binary.rs | 2 +- crates/wast/src/component/types.rs | 4 + crates/wit-component/src/encoding/types.rs | 2 +- crates/wit-component/src/printing.rs | 6 +- .../link-lib-with-async-export/component.wat | 24 +++--- .../link-lib-with-async-export/lib-foo.wat | 6 +- crates/wit-dylib/Cargo.toml | 1 + .../test-programs/src/bin/async_callee.rs | 14 ++-- .../test-programs/src/bin/async_caller.rs | 23 +++--- .../test-programs/src/bin/roundtrip_caller.rs | 20 ++--- .../src/bin/streams_and_futures_callee.rs | 8 +- .../src/bin/streams_and_futures_caller.rs | 16 ++-- crates/wit-dylib/tests/all.rs | 22 ++++++ crates/wit-dylib/tests/roundtrip.rs | 8 +- crates/wit-parser/src/ast/resolve.rs | 14 +--- crates/wit-parser/src/decoding.rs | 27 ++++--- crates/wit-parser/src/lib.rs | 49 ++++-------- crates/wit-parser/tests/ui/async.wit.json | 20 ++--- .../ui/parse-fail/async-bad-world3.wit.result | 2 +- .../ui/parse-fail/async-bad-world4.wit.result | 2 +- tests/cli/component-model/async/names.wast | 79 +++---------------- tests/cli/dump/alias.wat.stdout | 2 +- tests/cli/dump/alias2.wat.stdout | 4 +- tests/cli/dump/bundled.wat.stdout | 4 +- .../cli/dump/component-inline-type.wat.stdout | 2 +- .../dump/component-instance-type.wat.stdout | 2 +- tests/cli/dump/component-linking.wat.stdout | 4 +- .../cli/dump/component-outer-alias.wat.stdout | 12 +-- tests/cli/dump/instance-expand.wat.stdout | 2 +- tests/cli/dump/instance-type.wat.stdout | 2 +- tests/cli/dump/instantiate.wat.stdout | 2 +- tests/cli/dump/instantiate2.wat.stdout | 2 +- tests/cli/dump/nested-component.wat.stdout | 4 +- .../component-model/async.wast | 15 +--- .../cli/component-model/async/names.wast.json | 75 +++--------------- .../component-model/async.wast.json | 18 +---- 44 files changed, 261 insertions(+), 400 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f091e86d0..9c71d703c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3076,6 +3076,7 @@ dependencies = [ "libtest-mimic", "tempfile", "wasm-encoder 0.240.0", + "wasmparser 0.240.0", "wit-component 0.240.0", "wit-parser 0.240.0", "wit-smith", diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs index 980d7701ab..f9887e0e7e 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -353,6 +353,7 @@ impl Encode for InstanceType { /// Used to encode component function types. #[derive(Debug)] pub struct ComponentFuncTypeEncoder<'a> { + async_encoded: bool, params_encoded: bool, results_encoded: bool, sink: &'a mut Vec, @@ -360,14 +361,36 @@ pub struct ComponentFuncTypeEncoder<'a> { impl<'a> ComponentFuncTypeEncoder<'a> { fn new(sink: &'a mut Vec) -> Self { - sink.push(0x40); Self { + async_encoded: false, params_encoded: false, results_encoded: false, sink, } } + /// Indicates whether this is an `async` function or not. + /// + /// If this function is not invoked then the function type will not be + /// `async`. + /// + /// # Panics + /// + /// This method will panic if parameters or results have already been + /// encoded. + pub fn async_(&mut self, is_async: bool) -> &mut Self { + assert!(!self.params_encoded); + assert!(!self.results_encoded); + assert!(!self.async_encoded); + self.async_encoded = true; + if is_async { + self.sink.push(0x43); + } else { + self.sink.push(0x40); + } + self + } + /// Defines named parameters. /// /// Parameters must be defined before defining results. @@ -383,6 +406,9 @@ impl<'a> ComponentFuncTypeEncoder<'a> { T: Into, { assert!(!self.params_encoded); + if !self.async_encoded { + self.async_(false); + } self.params_encoded = true; let params = params.into_iter(); params.len().encode(self.sink); @@ -402,6 +428,7 @@ impl<'a> ComponentFuncTypeEncoder<'a> { /// This method will panic if the function is called twice, called before /// the `params` method, or called in addition to the `results` method. pub fn result(&mut self, ty: Option) -> &mut Self { + assert!(self.async_encoded); assert!(self.params_encoded); assert!(!self.results_encoded); self.results_encoded = true; diff --git a/crates/wasm-encoder/src/reencode/component.rs b/crates/wasm-encoder/src/reencode/component.rs index 55990698ec..e6d5afe5b6 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -731,6 +731,7 @@ pub mod component_utils { mut func: crate::ComponentFuncTypeEncoder<'_>, ty: wasmparser::ComponentFuncType<'_>, ) -> Result<(), Error> { + func.async_(ty.async_); func.params( Vec::from(ty.params) .into_iter() diff --git a/crates/wasmparser/src/readers/component/types.rs b/crates/wasmparser/src/readers/component/types.rs index 5a9dfb24bc..b87c26da92 100644 --- a/crates/wasmparser/src/readers/component/types.rs +++ b/crates/wasmparser/src/readers/component/types.rs @@ -282,12 +282,16 @@ impl<'a> FromReader<'a> for ComponentType<'a> { b => return reader.invalid_leading_byte(b, "resource destructor"), }, }, - 0x40 => { + byte @ (0x40 | 0x43) => { let params = reader .read_iter(MAX_WASM_FUNCTION_PARAMS, "component function parameters")? .collect::>()?; let result = read_resultlist(reader)?; - ComponentType::Func(ComponentFuncType { params, result }) + ComponentType::Func(ComponentFuncType { + async_: byte == 0x43, + params, + result, + }) } 0x41 => ComponentType::Component( reader @@ -387,6 +391,8 @@ impl<'a> FromReader<'a> for InstanceTypeDeclaration<'a> { /// Represents a type of a function in a WebAssembly component. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ComponentFuncType<'a> { + /// Whether or not this is an async function. + pub async_: bool, /// The function parameters. pub params: Box<[(&'a str, ComponentValType)]>, /// The function result. diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index 71cee60489..9dfe0a0ef1 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -1447,6 +1447,7 @@ impl ComponentState { } let func_ty = ComponentFuncType { + async_: false, info: TypeInfo::new(), params: result .iter() @@ -3051,6 +3052,13 @@ impl ComponentState { ) -> Result { let mut info = TypeInfo::new(); + if ty.async_ && !self.features.cm_async() { + bail!( + offset, + "async component functions require the component model async feature" + ); + } + let mut set = Set::default(); set.reserve(core::cmp::max( ty.params.len(), @@ -3092,6 +3100,7 @@ impl ComponentState { .transpose()?; Ok(ComponentFuncType { + async_: ty.async_, info, params, result, @@ -4534,11 +4543,8 @@ impl ComponentNameContext { if let ExternKind::Export = kind { match kebab.kind() { ComponentNameKind::Label(_) - | ComponentNameKind::AsyncLabel(_) | ComponentNameKind::Method(_) - | ComponentNameKind::AsyncMethod(_) | ComponentNameKind::Static(_) - | ComponentNameKind::AsyncStatic(_) | ComponentNameKind::Constructor(_) | ComponentNameKind::Interface(_) => {} @@ -4552,7 +4558,7 @@ impl ComponentNameContext { // Validate that the kebab name, if it has structure such as // `[method]a.b`, is indeed valid with respect to known resources. - self.validate(&kebab, ty, types, offset, features) + self.validate(&kebab, ty, types, offset) .with_context(|| format!("{} name `{kebab}` is not valid", kind.desc()))?; // Top-level kebab-names must all be unique, even between both imports @@ -4593,7 +4599,6 @@ impl ComponentNameContext { ty: &ComponentEntityType, types: &TypeAlloc, offset: usize, - features: &WasmFeatures, ) -> Result<()> { let func = || { let id = match ty { @@ -4602,24 +4607,10 @@ impl ComponentNameContext { }; Ok(&types[id]) }; - match name.kind() { - ComponentNameKind::AsyncLabel(_) - | ComponentNameKind::AsyncMethod(_) - | ComponentNameKind::AsyncStatic(_) => { - if !features.cm_async() { - bail!( - offset, - "async kebab-names require the component model async feature" - ); - } - } - _ => {} - } match name.kind() { // No validation necessary for these styles of names ComponentNameKind::Label(_) - | ComponentNameKind::AsyncLabel(_) | ComponentNameKind::Interface(_) | ComponentNameKind::Url(_) | ComponentNameKind::Dependency(_) @@ -4630,6 +4621,9 @@ impl ComponentNameContext { // within this context to match `rname`. ComponentNameKind::Constructor(rname) => { let ty = func()?; + if ty.async_ { + bail!(offset, "constructor function cannot be async"); + } let ty = match ty.result { Some(result) => result, None => bail!(offset, "function should return one value"), @@ -4661,7 +4655,7 @@ impl ComponentNameContext { // Methods must take `(param "self" (borrow $resource))` as the // first argument where `$resources` matches the name `resource` as // named in this context. - ComponentNameKind::Method(name) | ComponentNameKind::AsyncMethod(name) => { + ComponentNameKind::Method(name) => { let ty = func()?; if ty.params.len() == 0 { bail!(offset, "function should have at least one argument"); @@ -4693,7 +4687,7 @@ impl ComponentNameContext { // Static methods don't have much validation beyond that they must // be a function and the resource name referred to must already be // in this context. - ComponentNameKind::Static(name) | ComponentNameKind::AsyncStatic(name) => { + ComponentNameKind::Static(name) => { func()?; if !self.all_resource_names.contains(name.resource().as_str()) { bail!(offset, "static resource name is not known in this context"); diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index 64ddf1ccae..8db17eb511 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -1086,6 +1086,8 @@ impl TypeData for ComponentInstanceType { pub struct ComponentFuncType { /// Metadata about this function type. pub(crate) info: TypeInfo, + /// Whether or not this is an async function. + pub async_: bool, /// The function parameters. pub params: Box<[(KebabString, ComponentValType)]>, /// The function's result. @@ -3237,6 +3239,15 @@ impl<'a> SubtypeCx<'a> { let a = &self.a[a]; let b = &self.b[b]; + if a.async_ != b.async_ { + let a_desc = if a.async_ { "async" } else { "sync" }; + let b_desc = if b.async_ { "async" } else { "sync" }; + bail!( + offset, + "expected {a_desc} function, found {b_desc} function", + ); + } + // Note that this intentionally diverges from the upstream // specification in terms of subtyping. This is a full // type-equality check which ensures that the structure of `a` diff --git a/crates/wasmparser/src/validator/names.rs b/crates/wasmparser/src/validator/names.rs index a35edea75c..5ec899634e 100644 --- a/crates/wasmparser/src/validator/names.rs +++ b/crates/wasmparser/src/validator/names.rs @@ -242,9 +242,6 @@ impl From for String { /// * a plain method name : `[method]a-b.c-d` /// * a plain static method name : `[static]a-b.c-d` /// * a plain constructor: `[constructor]a-b` -/// * an async plain label: `[async]a-b-c` -/// * an async plain method name : `[async method]a-b.c-d` -/// * an async plain static method name : `[async static]a-b.c-d` /// * an interface name: `wasi:cli/reactor@0.1.0` /// * a dependency name: `locked-dep=foo:bar/baz` /// * a URL name: `url=https://..` @@ -271,9 +268,6 @@ enum ParsedComponentNameKind { Dependency, Url, Hash, - AsyncLabel, - AsyncMethod, - AsyncStatic, } /// Created via [`ComponentName::kind`] and classifies a name. @@ -301,22 +295,11 @@ pub enum ComponentNameKind<'a> { /// `integrity=sha256:...` #[allow(missing_docs)] Hash(HashName<'a>), - /// `[async]a-b-c` - AsyncLabel(&'a KebabStr), - /// `[async method]a-b.c-d` - #[allow(missing_docs)] - AsyncMethod(ResourceFunc<'a>), - /// `[async static]a-b.c-d` - #[allow(missing_docs)] - AsyncStatic(ResourceFunc<'a>), } const CONSTRUCTOR: &str = "[constructor]"; const METHOD: &str = "[method]"; const STATIC: &str = "[static]"; -const ASYNC: &str = "[async]"; -const ASYNC_METHOD: &str = "[async method]"; -const ASYNC_STATIC: &str = "[async static]"; impl ComponentName { /// Attempts to parse `name` as a valid component name, returning `Err` if @@ -352,12 +335,9 @@ impl ComponentName { use ParsedComponentNameKind as PK; match self.kind { PK::Label => Label(KebabStr::new_unchecked(&self.raw)), - PK::AsyncLabel => AsyncLabel(KebabStr::new_unchecked(&self.raw[ASYNC.len()..])), PK::Constructor => Constructor(KebabStr::new_unchecked(&self.raw[CONSTRUCTOR.len()..])), PK::Method => Method(ResourceFunc(&self.raw[METHOD.len()..])), - PK::AsyncMethod => AsyncMethod(ResourceFunc(&self.raw[ASYNC_METHOD.len()..])), PK::Static => Static(ResourceFunc(&self.raw[STATIC.len()..])), - PK::AsyncStatic => AsyncStatic(ResourceFunc(&self.raw[ASYNC_STATIC.len()..])), PK::Interface => Interface(InterfaceName(&self.raw)), PK::Dependency => Dependency(DependencyName(&self.raw)), PK::Url => Url(UrlName(&self.raw)), @@ -427,9 +407,6 @@ impl ComponentNameKind<'_> { Self::Dependency(_) => ParsedComponentNameKind::Dependency, Self::Url(_) => ParsedComponentNameKind::Url, Self::Hash(_) => ParsedComponentNameKind::Hash, - Self::AsyncLabel(_) => ParsedComponentNameKind::AsyncLabel, - Self::AsyncMethod(_) => ParsedComponentNameKind::AsyncMethod, - Self::AsyncStatic(_) => ParsedComponentNameKind::AsyncStatic, } } } @@ -439,22 +416,17 @@ impl Ord for ComponentNameKind<'_> { use ComponentNameKind::*; match (self, other) { - (Label(lhs) | AsyncLabel(lhs), Label(rhs) | AsyncLabel(rhs)) => lhs.cmp(rhs), + (Label(lhs), Label(rhs)) => lhs.cmp(rhs), (Constructor(lhs), Constructor(rhs)) => lhs.cmp(rhs), - ( - Method(lhs) | AsyncMethod(lhs) | Static(lhs) | AsyncStatic(lhs), - Method(rhs) | AsyncMethod(rhs) | Static(rhs) | AsyncStatic(rhs), - ) => lhs.cmp(rhs), - - // `[..]l.l` is equivalent to `l` and `[async]l`. - ( - Label(plain) | AsyncLabel(plain), - Method(method) | AsyncMethod(method) | Static(method) | AsyncStatic(method), - ) - | ( - Method(method) | AsyncMethod(method) | Static(method) | AsyncStatic(method), - Label(plain) | AsyncLabel(plain), - ) if *plain == method.resource() && *plain == method.method() => Ordering::Equal, + (Method(lhs) | Static(lhs), Method(rhs) | Static(rhs)) => lhs.cmp(rhs), + + // `[..]l.l` is equivalent to `l` + (Label(plain), Method(method) | Static(method)) + | (Method(method) | Static(method), Label(plain)) + if *plain == method.resource() && *plain == method.method() => + { + Ordering::Equal + } (Interface(lhs), Interface(rhs)) => lhs.cmp(rhs), (Dependency(lhs), Dependency(rhs)) => lhs.cmp(rhs), @@ -462,12 +434,9 @@ impl Ord for ComponentNameKind<'_> { (Hash(lhs), Hash(rhs)) => lhs.cmp(rhs), (Label(_), _) - | (AsyncLabel(_), _) | (Constructor(_), _) | (Method(_), _) | (Static(_), _) - | (AsyncMethod(_), _) - | (AsyncStatic(_), _) | (Interface(_), _) | (Dependency(_), _) | (Url(_), _) @@ -486,10 +455,10 @@ impl Hash for ComponentNameKind<'_> { fn hash(&self, hasher: &mut H) { use ComponentNameKind::*; match self { - Label(name) | AsyncLabel(name) => (0u8, name).hash(hasher), + Label(name) => (0u8, name).hash(hasher), Constructor(name) => (1u8, name).hash(hasher), - Method(name) | Static(name) | AsyncMethod(name) | AsyncStatic(name) => { + Method(name) | Static(name) => { // `l.l` hashes the same as `l` since they're equal above, // otherwise everything is hashed as `a.b` with a unique // prefix. @@ -630,10 +599,6 @@ struct ComponentNameParser<'a> { impl<'a> ComponentNameParser<'a> { fn parse(&mut self) -> Result { - if self.eat_str(ASYNC) { - self.expect_kebab()?; - return Ok(ParsedComponentNameKind::AsyncLabel); - } if self.eat_str(CONSTRUCTOR) { self.expect_kebab()?; return Ok(ParsedComponentNameKind::Constructor); @@ -650,18 +615,6 @@ impl<'a> ComponentNameParser<'a> { self.expect_kebab()?; return Ok(ParsedComponentNameKind::Static); } - if self.eat_str(ASYNC_METHOD) { - let resource = self.take_until('.')?; - self.kebab(resource)?; - self.expect_kebab()?; - return Ok(ParsedComponentNameKind::AsyncMethod); - } - if self.eat_str(ASYNC_STATIC) { - let resource = self.take_until('.')?; - self.kebab(resource)?; - self.expect_kebab()?; - return Ok(ParsedComponentNameKind::AsyncStatic); - } // 'unlocked-dep=<' '>' if self.eat_str("unlocked-dep=") { diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 7b7f001c39..0c15f30fcc 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -472,6 +472,9 @@ impl Printer<'_, '_> { ty: &ComponentFuncType, ) -> Result<()> { self.start_group("func")?; + if ty.async_ { + self.print_type_keyword(" async")?; + } for (name, ty) in ty.params.iter() { self.result.write_str(" ")?; self.start_group("param ")?; diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index be3759a3ab..fb2e833c0a 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -74,8 +74,8 @@ fn encode_type(encoder: ComponentTypeEncoder, ty: &TypeDef) { } TypeDef::Func(f) => { let mut encoder = encoder.function(); + encoder.async_(f.async_); encoder.params(f.params.iter().map(|p| (p.name, &p.ty))); - encoder.result(f.result.as_ref().map(|ty| ty.into())); } TypeDef::Component(c) => { diff --git a/crates/wast/src/component/types.rs b/crates/wast/src/component/types.rs index 4153f28b54..a6652669d4 100644 --- a/crates/wast/src/component/types.rs +++ b/crates/wast/src/component/types.rs @@ -747,6 +747,8 @@ impl<'a> Parse<'a> for Future<'a> { /// A component function type with parameters and result. #[derive(Debug)] pub struct ComponentFunctionType<'a> { + /// Whether or not this is an `async` fnction. + pub async_: bool, /// The parameters of a function, optionally each having an identifier for /// name resolution and a name for the custom `name` section. pub params: Box<[ComponentFunctionParam<'a>]>, @@ -756,6 +758,7 @@ pub struct ComponentFunctionType<'a> { impl<'a> Parse<'a> for ComponentFunctionType<'a> { fn parse(parser: Parser<'a>) -> Result { + let async_ = parser.parse::>()?.is_some(); let mut params: Vec = Vec::new(); while parser.peek2::()? { params.push(parser.parens(|p| p.parse())?); @@ -771,6 +774,7 @@ impl<'a> Parse<'a> for ComponentFunctionType<'a> { }; Ok(Self { + async_, params: params.into(), result, }) diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 202febbefa..abbda325d9 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -115,7 +115,7 @@ pub trait ValtypeEncoder<'a> { // Encode the function type let (index, mut f) = self.define_function_type(); - f.params(params).result(result); + f.async_(func.kind.is_async()).params(params).result(result); let prev = self.type_encoding_maps().func_type_map.insert(key, index); assert!(prev.is_none()); Ok(index) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index c120023f7a..e97e77bfb0 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -487,11 +487,7 @@ impl WitPrinter { self.output.indent_end(); } WorldItem::Function(f) => { - // Note that `f.item_name()` is used here instead of - // `name` because if this is an async function then we - // want to print `foo`, not `[async]foo` under the - // `import` name. - self.print_name_type(f.item_name(), TypeKind::Other); + self.print_name_type(&f.name, TypeKind::Other); self.output.str(": "); self.print_function(resolve, f)?; self.output.semicolon(); diff --git a/crates/wit-component/tests/components/link-lib-with-async-export/component.wat b/crates/wit-component/tests/components/link-lib-with-async-export/component.wat index 2ac086f7a7..cf3c5be089 100644 --- a/crates/wit-component/tests/components/link-lib-with-async-export/component.wat +++ b/crates/wit-component/tests/components/link-lib-with-async-export/component.wat @@ -48,10 +48,10 @@ (type (;1;) (func)) (type (;2;) (func (result i32))) (type (;3;) (func (param i32 i32 i32) (result i32))) - (import "[export]$root" "[task-return][async]f" (func (;0;) (type 0))) + (import "[export]$root" "[task-return]f" (func (;0;) (type 0))) (export "foo" (func 1)) - (export "[async-lift][async]f" (func 2)) - (export "[callback][async-lift][async]f" (func 3)) + (export "[async-lift]f" (func 2)) + (export "[callback][async-lift]f" (func 3)) (func (;1;) (type 1)) (func (;2;) (type 2) (result i32) unreachable @@ -79,9 +79,9 @@ (core module $wit-component-shim-module (;4;) (type (;0;) (func (param i32))) (table (;0;) 1 1 funcref) - (export "0" (func $"task-return-[async]f")) + (export "0" (func $task-return-f)) (export "$imports" (table 0)) - (func $"task-return-[async]f" (;0;) (type 0) (param i32) + (func $task-return-f (;0;) (type 0) (param i32) local.get 0 i32.const 0 call_indirect (type 0) @@ -110,9 +110,9 @@ (with "env" (instance $env)) ) ) - (alias core export $wit-component-shim-instance "0" (core func $"task-return-[async]f" (;1;))) + (alias core export $wit-component-shim-instance "0" (core func $task-return-f (;1;))) (core instance $"[export]$root" (;4;) - (export "[task-return][async]f" (func $"task-return-[async]f")) + (export "[task-return]f" (func $task-return-f)) ) (core instance $foo (;5;) (instantiate $foo (with "[export]$root" (instance $"[export]$root")) @@ -133,11 +133,11 @@ (with "foo" (instance $foo)) ) ) - (type (;4;) (func (result $big))) - (alias core export $foo "[async-lift][async]f" (core func $"[async-lift][async]f" (;3;))) - (alias core export $foo "[callback][async-lift][async]f" (core func $"[callback][async-lift][async]f" (;4;))) - (func $"[async]f" (;0;) (type 4) (canon lift (core func $"[async-lift][async]f") (memory $memory) string-encoding=utf8 async (callback $"[callback][async-lift][async]f"))) - (export $"#func1 [async]f" (@name "[async]f") (;1;) "[async]f" (func $"[async]f")) + (type (;4;) (func async (result $big))) + (alias core export $foo "[async-lift]f" (core func $"[async-lift]f" (;3;))) + (alias core export $foo "[callback][async-lift]f" (core func $"[callback][async-lift]f" (;4;))) + (func $f (;0;) (type 4) (canon lift (core func $"[async-lift]f") (memory $memory) string-encoding=utf8 async (callback $"[callback][async-lift]f"))) + (export $"#func1 f" (@name "f") (;1;) "f" (func $f)) (@producers (processed-by "wit-component" "$CARGO_PKG_VERSION") ) diff --git a/crates/wit-component/tests/components/link-lib-with-async-export/lib-foo.wat b/crates/wit-component/tests/components/link-lib-with-async-export/lib-foo.wat index e11681efc8..a9b26107a6 100644 --- a/crates/wit-component/tests/components/link-lib-with-async-export/lib-foo.wat +++ b/crates/wit-component/tests/components/link-lib-with-async-export/lib-foo.wat @@ -4,9 +4,9 @@ (needed "c") ) - (import "[export]$root" "[task-return][async]f" (func (param i32))) + (import "[export]$root" "[task-return]f" (func (param i32))) (func (export "foo")) - (func (export "[async-lift][async]f") (result i32) unreachable) - (func (export "[callback][async-lift][async]f") (param i32 i32 i32) (result i32) unreachable) + (func (export "[async-lift]f") (result i32) unreachable) + (func (export "[callback][async-lift]f") (param i32 i32 i32) (result i32) unreachable) ) diff --git a/crates/wit-dylib/Cargo.toml b/crates/wit-dylib/Cargo.toml index c1bb4ff2f6..60479a01f9 100644 --- a/crates/wit-dylib/Cargo.toml +++ b/crates/wit-dylib/Cargo.toml @@ -32,6 +32,7 @@ arbtest = "0.3.2" indexmap = { workspace = true } wit-component = { workspace = true } env_logger = { workspace = true } +wasmparser = { workspace = true } [lib] test = false diff --git a/crates/wit-dylib/test-programs/src/bin/async_callee.rs b/crates/wit-dylib/test-programs/src/bin/async_callee.rs index 9c7040190a..71b11ba09b 100644 --- a/crates/wit-dylib/test-programs/src/bin/async_callee.rs +++ b/crates/wit-dylib/test-programs/src/bin/async_callee.rs @@ -23,7 +23,7 @@ impl TestCase for MyInterpreter { let big = Val::Record(vec![big2.clone(), big2.clone(), big2.clone(), big2.clone()]); match func.name() { - "[async]f" => { + "f" => { assert_eq!(func.params().len(), 0); assert!(func.result().is_none()); assert_eq!(args.len(), 0); @@ -33,7 +33,7 @@ impl TestCase for MyInterpreter { } None } - "[async]f-scalar-param" => { + "f-scalar-param" => { assert_eq!(func.params().len(), 1); assert!(func.result().is_none()); assert_eq!(args.len(), 1); @@ -42,14 +42,14 @@ impl TestCase for MyInterpreter { assert_eq!(args.next(), None); None } - "[async]f-scalar-result" => { + "f-scalar-result" => { assert_eq!(func.params().len(), 0); assert!(func.result().is_some()); assert_eq!(args.len(), 0); Some(Val::U32(202)) } - "[async]aggregates" => { + "aggregates" => { assert_eq!(func.params().len(), 2); assert!(func.result().is_some()); assert_eq!(args.len(), 2); @@ -72,7 +72,7 @@ impl TestCase for MyInterpreter { Val::F32(64.0), ])) } - "[async]indirect-params" => { + "indirect-params" => { assert_eq!(func.params().len(), 2); assert!(func.result().is_none()); assert_eq!(args.len(), 2); @@ -83,7 +83,7 @@ impl TestCase for MyInterpreter { None } - "[async]indirect-params-and-result" => { + "indirect-params-and-result" => { assert_eq!(func.params().len(), 1); assert!(func.result().is_some()); assert_eq!(args.len(), 1); @@ -92,7 +92,7 @@ impl TestCase for MyInterpreter { Some(big.clone()) } - "[async]echo-string" => { + "echo-string" => { assert_eq!(func.params().len(), 1); assert!(func.result().is_some()); args.next() diff --git a/crates/wit-dylib/test-programs/src/bin/async_caller.rs b/crates/wit-dylib/test-programs/src/bin/async_caller.rs index d8c56832e5..12a8f46c41 100644 --- a/crates/wit-dylib/test-programs/src/bin/async_caller.rs +++ b/crates/wit-dylib/test-programs/src/bin/async_caller.rs @@ -18,30 +18,25 @@ impl TestCase for MyInterpreter { args: impl ExactSizeIterator, ) -> Option { assert_eq!(func.interface(), None); - assert_eq!(func.name(), "[async]run"); + assert_eq!(func.name(), "run"); assert_eq!(func.params().len(), 0); assert!(func.result().is_none()); assert_eq!(args.len(), 0); - let ret = Self::call_import_async(wit, Some("a:b/x"), "[async]f", &[]).await; + let ret = Self::call_import_async(wit, Some("a:b/x"), "f", &[]).await; assert!(ret.is_none()); - let ret = Self::call_import_async( - wit, - Some("a:b/x"), - "[async]f-scalar-param", - &[Val::U32(101)], - ) - .await; + let ret = + Self::call_import_async(wit, Some("a:b/x"), "f-scalar-param", &[Val::U32(101)]).await; assert!(ret.is_none()); - let ret = Self::call_import_async(wit, Some("a:b/x"), "[async]f-scalar-result", &[]).await; + let ret = Self::call_import_async(wit, Some("a:b/x"), "f-scalar-result", &[]).await; assert_eq!(ret, Some(Val::U32(202))); let ret = Self::call_import_async( wit, Some("a:b/x"), - "[async]aggregates", + "aggregates", &[ Val::Record(vec![ Val::Record(vec![Val::U32(2000), Val::Char('y')]), @@ -66,7 +61,7 @@ impl TestCase for MyInterpreter { let ret = Self::call_import_async( wit, Some("a:b/x"), - "[async]indirect-params", + "indirect-params", &[big.clone(), big.clone()], ) .await; @@ -75,7 +70,7 @@ impl TestCase for MyInterpreter { let ret = Self::call_import_async( wit, Some("a:b/x"), - "[async]indirect-params-and-result", + "indirect-params-and-result", &[big.clone()], ) .await; @@ -85,7 +80,7 @@ impl TestCase for MyInterpreter { let ret = Self::call_import_async( wit, Some("a:b/x"), - "[async]echo-string", + "echo-string", &[Val::String(s.to_string())], ) .await; diff --git a/crates/wit-dylib/test-programs/src/bin/roundtrip_caller.rs b/crates/wit-dylib/test-programs/src/bin/roundtrip_caller.rs index 5a95da1736..caf4e2f9a2 100644 --- a/crates/wit-dylib/test-programs/src/bin/roundtrip_caller.rs +++ b/crates/wit-dylib/test-programs/src/bin/roundtrip_caller.rs @@ -5,21 +5,20 @@ use test_programs::*; export_test!(struct MyInterpreter); impl TestCase for MyInterpreter { - fn call_export( - _wit: Wit, - _func: ExportFunction, - _args: impl ExactSizeIterator, - ) -> Option { - unreachable!() - } + // // TODO: old signature for when wasmtime re-syncs async support + // async fn call_export_async( + // wit: Wit, + // func: ExportFunction, + // mut args: impl ExactSizeIterator, + // ) -> Option { - async fn call_export_async( + fn call_export( wit: Wit, func: ExportFunction, mut args: impl ExactSizeIterator, ) -> Option { assert_eq!(func.interface(), None); - assert_eq!(func.name(), "[async]run"); + assert_eq!(func.name(), "run"); assert_eq!(func.params().len(), 2); assert!(func.result().is_none()); assert_eq!(args.len(), 2); @@ -93,7 +92,8 @@ impl TestCase for MyInterpreter { }); } let result = if import.is_async() { - Self::call_import_func_async(*import, &args).await + panic!("wasmtime doesn't support async yet"); + // Self::call_import_func_async(*import, &args).await } else { Self::call_import_func(*import, &args) }; diff --git a/crates/wit-dylib/test-programs/src/bin/streams_and_futures_callee.rs b/crates/wit-dylib/test-programs/src/bin/streams_and_futures_callee.rs index 797c9d174f..c183656edc 100644 --- a/crates/wit-dylib/test-programs/src/bin/streams_and_futures_callee.rs +++ b/crates/wit-dylib/test-programs/src/bin/streams_and_futures_callee.rs @@ -62,7 +62,7 @@ impl TestCase for MyInterpreter { assert_eq!(func.interface(), Some("a:b/x")); match func.name() { - "[async]echo-stream-u8" => { + "echo-stream-u8" => { assert_eq!(func.params().len(), 1); assert!(matches!(func.params().next(), Some(Type::Stream(_)))); assert!(matches!(func.result(), Some(Type::Stream(_)))); @@ -101,7 +101,7 @@ impl TestCase for MyInterpreter { Some(Val::Stream(result.take_handle())) } - "[async]echo-future-string" => { + "echo-future-string" => { assert_eq!(func.params().len(), 1); assert!(matches!(func.params().next(), Some(Type::Future(_)))); assert!(matches!(func.result(), Some(Type::Future(_)))); @@ -126,7 +126,7 @@ impl TestCase for MyInterpreter { Some(Val::Future(result.take_handle())) } - "[async method]thing.get" => { + "thing.get" => { assert_eq!(func.params().len(), 1); assert!(matches!(func.params().next(), Some(Type::Borrow(_)))); assert!(matches!(func.result(), Some(Type::String))); @@ -147,7 +147,7 @@ impl TestCase for MyInterpreter { Some(Val::String(value)) } - "[async]short-reads" => { + "short-reads" => { assert_eq!(func.params().len(), 1); assert!(matches!(func.params().next(), Some(Type::Stream(_)))); assert!(matches!(func.result(), Some(Type::Stream(_)))); diff --git a/crates/wit-dylib/test-programs/src/bin/streams_and_futures_caller.rs b/crates/wit-dylib/test-programs/src/bin/streams_and_futures_caller.rs index bedb74a8e1..2520c7b439 100644 --- a/crates/wit-dylib/test-programs/src/bin/streams_and_futures_caller.rs +++ b/crates/wit-dylib/test-programs/src/bin/streams_and_futures_caller.rs @@ -24,14 +24,14 @@ impl TestCase for MyInterpreter { args: impl ExactSizeIterator, ) -> Option { assert_eq!(func.interface(), None); - assert_eq!(func.name(), "[async]run"); + assert_eq!(func.name(), "run"); assert_eq!(func.params().len(), 0); assert!(func.result().is_none()); assert_eq!(args.len(), 0); { let Some(Type::Stream(ty)) = wit - .unwrap_import(Some("a:b/x"), "[async]echo-stream-u8") + .unwrap_import(Some("a:b/x"), "echo-stream-u8") .params() .next() else { @@ -45,7 +45,7 @@ impl TestCase for MyInterpreter { let Some(Val::Stream(rx)) = Self::call_import_async( wit, Some("a:b/x"), - "[async]echo-stream-u8", + "echo-stream-u8", &[Val::Stream(rx.take_handle())], ) .await @@ -65,7 +65,7 @@ impl TestCase for MyInterpreter { { let Some(Type::Future(ty)) = wit - .unwrap_import(Some("a:b/x"), "[async]echo-future-string") + .unwrap_import(Some("a:b/x"), "echo-future-string") .params() .next() else { @@ -79,7 +79,7 @@ impl TestCase for MyInterpreter { let Some(Val::Future(rx)) = Self::call_import_async( wit, Some("a:b/x"), - "[async]echo-future-string", + "echo-future-string", &[Val::Future(rx.take_handle())], ) .await @@ -117,7 +117,7 @@ impl TestCase for MyInterpreter { .collect::>(); let Some(Type::Stream(ty)) = wit - .unwrap_import(Some("a:b/x"), "[async]short-reads") + .unwrap_import(Some("a:b/x"), "short-reads") .params() .next() else { @@ -131,7 +131,7 @@ impl TestCase for MyInterpreter { let Some(Val::Stream(rx)) = Self::call_import_async( wit, Some("a:b/x"), - "[async]short-reads", + "short-reads", &[Val::Stream(rx.take_handle())], ) .await @@ -168,7 +168,7 @@ impl TestCase for MyInterpreter { let Val::String(value) = Self::call_import_async( wit, Some("a:b/x"), - "[async method]thing.get", + "thing.get", &[Val::Borrow(handle.borrow())], ) .await diff --git a/crates/wit-dylib/tests/all.rs b/crates/wit-dylib/tests/all.rs index 97cd69b25e..f5e3a342bf 100644 --- a/crates/wit-dylib/tests/all.rs +++ b/crates/wit-dylib/tests/all.rs @@ -3,6 +3,7 @@ use libtest_mimic::{Arguments, Trial}; use std::path::Path; use std::process::Command; use tempfile::TempDir; +use wasmparser::{Validator, WasmFeatures}; use wit_parser::Resolve; fn main() { @@ -78,5 +79,26 @@ fn run_test(tempdir: &TempDir, caller: &Path, callee: &Path, wit: &Path) -> Resu )); } + if uses_async_and_wasmtime_does_not_support_async(&composition_file, &error)? { + return Ok(()); + } + anyhow::bail!("{error}") } + +fn uses_async_and_wasmtime_does_not_support_async(wasm: &Path, error: &str) -> Result { + if !error.contains("invalid leading byte (0x43) for component defined type") { + return Ok(false); + } + let wasm = std::fs::read(wasm)?; + + let validates_with_cm_async = Validator::new_with_features(WasmFeatures::all()) + .validate_all(&wasm) + .is_ok(); + let validates_without_cm_async = + Validator::new_with_features(WasmFeatures::all() ^ WasmFeatures::CM_ASYNC) + .validate_all(&wasm) + .is_ok(); + + Ok(validates_with_cm_async && !validates_without_cm_async) +} diff --git a/crates/wit-dylib/tests/roundtrip.rs b/crates/wit-dylib/tests/roundtrip.rs index 5f43bc6d31..3cebbd2706 100644 --- a/crates/wit-dylib/tests/roundtrip.rs +++ b/crates/wit-dylib/tests/roundtrip.rs @@ -65,7 +65,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { config.fixed_size_list = false; config.futures = false; // TODO config.streams = false; // TODO - config.async_ = true; + config.async_ = false; let wasm = wit_smith::smith(&config, u)?; std::fs::write("./hello.wasm", &wasm).unwrap(); let (mut resolve, _pkg) = match wit_parser::decoding::decode(&wasm).unwrap() { @@ -203,10 +203,10 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { // Inject the actual entrypoint of the test. resolve.worlds[caller].exports.insert( - WorldKey::Name("[async]run".to_string()), + WorldKey::Name("run".to_string()), WorldItem::Function(Function { - name: "[async]run".to_string(), - kind: FunctionKind::AsyncFreestanding, + name: "run".to_string(), + kind: FunctionKind::Freestanding, params: vec![ ("iters".to_string(), Type::U32), ("seed".to_string(), Type::U64), diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 761668e44f..a997add506 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -776,12 +776,10 @@ impl<'a> Resolver<'a> { Ok(WorldItem::Interface { id, stability }) } ast::ExternKind::Func(name, func) => { - let prefix = if func.async_ { "[async]" } else { "" }; - let name = format!("{prefix}{}", name.name); let func = self.resolve_function( docs, attrs, - &name, + &name.name, func, if func.async_ { FunctionKind::AsyncFreestanding @@ -833,12 +831,10 @@ impl<'a> Resolver<'a> { match field { ast::InterfaceItem::Func(f) => { self.define_interface_name(&f.name, TypeOrItem::Item("function"))?; - let prefix = if f.func.async_ { "[async]" } else { "" }; - let name = format!("{prefix}{}", f.name.name); funcs.push(self.resolve_function( &f.docs, &f.attributes, - &name, + &f.name.name, &f.func, if f.func.async_ { FunctionKind::AsyncFreestanding @@ -1039,8 +1035,7 @@ impl<'a> Resolver<'a> { let async_ = named_func.func.async_; match func { ast::ResourceFunc::Method(f) => { - let prefix = if async_ { "[async method]" } else { "[method]" }; - name = format!("{prefix}{}.{}", resource.name, f.name.name); + name = format!("[method]{}.{}", resource.name, f.name.name); kind = if async_ { FunctionKind::AsyncMethod(resource_id) } else { @@ -1048,8 +1043,7 @@ impl<'a> Resolver<'a> { }; } ast::ResourceFunc::Static(f) => { - let prefix = if async_ { "[async static]" } else { "[static]" }; - name = format!("{prefix}{}.{}", resource.name, f.name.name); + name = format!("[static]{}.{}", resource.name, f.name.name); kind = if async_ { FunctionKind::AsyncStatic(resource_id) } else { diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 6c58ee1e43..54b023a9a3 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -1198,22 +1198,29 @@ impl WitPackageDecoder<'_> { docs: Default::default(), stability: Default::default(), kind: match name.kind() { - ComponentNameKind::Label(_) => FunctionKind::Freestanding, - ComponentNameKind::AsyncLabel(_) => FunctionKind::AsyncFreestanding, + ComponentNameKind::Label(_) => { + if ty.async_ { + FunctionKind::AsyncFreestanding + } else { + FunctionKind::Freestanding + } + } ComponentNameKind::Constructor(resource) => { FunctionKind::Constructor(self.resources[&owner][resource.as_str()]) } ComponentNameKind::Method(name) => { - FunctionKind::Method(self.resources[&owner][name.resource().as_str()]) - } - ComponentNameKind::AsyncMethod(name) => { - FunctionKind::AsyncMethod(self.resources[&owner][name.resource().as_str()]) + if ty.async_ { + FunctionKind::AsyncMethod(self.resources[&owner][name.resource().as_str()]) + } else { + FunctionKind::Method(self.resources[&owner][name.resource().as_str()]) + } } ComponentNameKind::Static(name) => { - FunctionKind::Static(self.resources[&owner][name.resource().as_str()]) - } - ComponentNameKind::AsyncStatic(name) => { - FunctionKind::AsyncStatic(self.resources[&owner][name.resource().as_str()]) + if ty.async_ { + FunctionKind::AsyncStatic(self.resources[&owner][name.resource().as_str()]) + } else { + FunctionKind::Static(self.resources[&owner][name.resource().as_str()]) + } } // Functions shouldn't have ID-based names at this time. diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 7274758491..e591e775d1 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -450,39 +450,12 @@ pub enum WorldKey { Interface(InterfaceId), } -/// Helper type to deduplicate kebab strings in maps. -/// -/// The component model doesn't allow importing both `x` and `[async]x` and this -/// type is what helps encapsulate that rule. This is similar ish to `KebabStr` -/// inside of `wasmparser` except that it's only used here for hashing/equality -/// of `WorldKey` to ensure that world items all hash/dedupe as expected. -#[derive(PartialEq, Hash)] -enum KebabDedupe<'a> { - Normal(&'a str), -} - -impl<'a> From<&'a str> for KebabDedupe<'a> { - fn from(s: &'a str) -> KebabDedupe<'a> { - // If the name starts with `[async]` then strip it off. That conflicts - // with names without `[async]` so pretend it's the same. - if let Some(s) = s.strip_prefix("[async]") { - return KebabDedupe::Normal(s); - } - - // Note that at this time there's no handling of `[method]` or - // `[static]` or similar. In theory that might work here but that's - // already handled via other means in `wit-parser` at this time and - // `[async]` is chosen to be handled here. - KebabDedupe::Normal(s) - } -} - impl Hash for WorldKey { fn hash(&self, hasher: &mut H) { match self { WorldKey::Name(s) => { 0u8.hash(hasher); - KebabDedupe::from(s.as_str()).hash(hasher); + s.as_str().hash(hasher); } WorldKey::Interface(i) => { 1u8.hash(hasher); @@ -495,9 +468,7 @@ impl Hash for WorldKey { impl PartialEq for WorldKey { fn eq(&self, other: &WorldKey) -> bool { match (self, other) { - (WorldKey::Name(a), WorldKey::Name(b)) => { - KebabDedupe::from(a.as_str()) == KebabDedupe::from(b.as_str()) - } + (WorldKey::Name(a), WorldKey::Name(b)) => a.as_str() == b.as_str(), (WorldKey::Name(_), _) => false, (WorldKey::Interface(a), WorldKey::Interface(b)) => a == b, (WorldKey::Interface(_), _) => false, @@ -957,6 +928,19 @@ pub enum FunctionKind { } impl FunctionKind { + /// Returns whether this is an async function or not. + pub fn is_async(&self) -> bool { + match self { + FunctionKind::Freestanding + | FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) => false, + FunctionKind::AsyncFreestanding + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => true, + } + } + /// Returns the resource, if present, that this function kind refers to. pub fn resource(&self) -> Option { match self { @@ -1121,8 +1105,7 @@ impl ManglingAndAbi { impl Function { pub fn item_name(&self) -> &str { match &self.kind { - FunctionKind::Freestanding => &self.name, - FunctionKind::AsyncFreestanding => &self.name["[async]".len()..], + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &self.name, FunctionKind::Method(_) | FunctionKind::Static(_) | FunctionKind::AsyncMethod(_) diff --git a/crates/wit-parser/tests/ui/async.wit.json b/crates/wit-parser/tests/ui/async.wit.json index b2510cbfb3..fd009db69b 100644 --- a/crates/wit-parser/tests/ui/async.wit.json +++ b/crates/wit-parser/tests/ui/async.wit.json @@ -3,9 +3,9 @@ { "name": "y", "imports": { - "[async]x": { + "x": { "function": { - "name": "[async]x", + "name": "x", "kind": "async-freestanding", "params": [] } @@ -19,9 +19,9 @@ } }, "exports": { - "[async]x": { + "x": { "function": { - "name": "[async]x", + "name": "x", "kind": "async-freestanding", "params": [] } @@ -44,8 +44,8 @@ "z": 0 }, "functions": { - "[async]x": { - "name": "[async]x", + "x": { + "name": "x", "kind": "async-freestanding", "params": [ { @@ -68,8 +68,8 @@ "params": [], "result": 2 }, - "[async method]z.x": { - "name": "[async method]z.x", + "[method]z.x": { + "name": "[method]z.x", "kind": { "async-method": 0 }, @@ -92,8 +92,8 @@ } ] }, - "[async static]z.static-x": { - "name": "[async static]z.static-x", + "[static]z.static-x": { + "name": "[static]z.static-x", "kind": { "async-static": 0 }, diff --git a/crates/wit-parser/tests/ui/parse-fail/async-bad-world3.wit.result b/crates/wit-parser/tests/ui/parse-fail/async-bad-world3.wit.result index 706856243d..42627ce954 100644 --- a/crates/wit-parser/tests/ui/parse-fail/async-bad-world3.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/async-bad-world3.wit.result @@ -1,4 +1,4 @@ -import `[async]x` conflicts with prior type of same name +import `x` conflicts with prior type of same name --> tests/ui/parse-fail/async-bad-world3.wit:4:10 | 4 | import x: async func(); diff --git a/crates/wit-parser/tests/ui/parse-fail/async-bad-world4.wit.result b/crates/wit-parser/tests/ui/parse-fail/async-bad-world4.wit.result index 771b8619be..07282a4c1c 100644 --- a/crates/wit-parser/tests/ui/parse-fail/async-bad-world4.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/async-bad-world4.wit.result @@ -1,4 +1,4 @@ -import of `[async]x` shadows previously imported items +import of `x` shadows previously imported items --> tests/ui/parse-fail/async-bad-world4.wit:8:11 | 8 | include a; diff --git a/tests/cli/component-model/async/names.wast b/tests/cli/component-model/async/names.wast index ee43c9b3c0..5208920568 100644 --- a/tests/cli/component-model/async/names.wast +++ b/tests/cli/component-model/async/names.wast @@ -1,88 +1,29 @@ ;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async -(component - (import "[async]f" (func)) - (import "r" (type $r (sub resource))) - (import "[async method]r.f" (func (param "self" (borrow $r)))) - (import "[async static]r.f2" (func)) -) - -;; name conflicts where the "base kebab name" only differs on abi/etc and these -;; should all conflict with one another. +;; historically these were part of the component-model-async development but +;; they have since been removed. (assert_invalid (component (import "[async]f" (func)) - (import "f" (func)) ) - "conflicts with previous name") + "not in kebab case") (assert_invalid (component - (import "f" (func)) - (import "[async]f" (func)) + (import "[async method]f" (func)) ) - "conflicts with previous name") - -(assert_invalid - (component - (import "r" (type $r (sub resource))) - (import "[method]r.f" (func (param "self" (borrow $r)))) - (import "[async static]r.f" (func))) - "conflicts with previous name") - -(assert_invalid - (component - (import "r" (type $r (sub resource))) - (import "[method]r.f" (func (param "self" (borrow $r)))) - (import "[async method]r.f" (func (param "self" (borrow $r))))) - "conflicts with previous name") - -(assert_invalid - (component - (import "r" (type $r (sub resource))) - (import "[method]r.f" (func (param "self" (borrow $r)))) - (import "[async static]r.f" (func))) - "conflicts with previous name") - -(assert_invalid - (component - (import "r" (type $r (sub resource))) - (import "[async method]r.f" (func (param "self" (borrow $r)))) - (import "[async method]r.f" (func (param "self" (borrow $r))))) - "conflicts with previous name") - -(assert_invalid - (component - (import "r" (type $r (sub resource))) - (import "[async method]r.f" (func (param "self" (borrow $r)))) - (import "[static]r.f" (func))) - "conflicts with previous name") - -(assert_invalid - (component - (import "r" (type $r (sub resource))) - (import "[async method]r.f" (func (param "self" (borrow $r)))) - (import "[async static]r.f" (func))) - "conflicts with previous name") + "not in kebab case") (assert_invalid (component - (import "r" (type $r (sub resource))) - (import "[async static]r.f" (func)) - (import "[static]r.f" (func))) - "conflicts with previous name") - -(assert_invalid - (component - (import "a" (type $a (sub resource))) - (import "[async method]a.a" (func (param "self" (borrow $a)))) + (import "[async static]f" (func)) ) - "import name `[async method]a.a` conflicts with previous name `a`") + "not in kebab case") + (assert_invalid (component (import "a" (type $a (sub resource))) - (import "[async static]a.a" (func)) - ) - "import name `[async static]a.a` conflicts with previous name `a`") + (import "[constructor]a" (func async (result (own $a))))) + "constructor function cannot be async") diff --git a/tests/cli/dump/alias.wat.stdout b/tests/cli/dump/alias.wat.stdout index 7100013041..8bc1eb18dc 100644 --- a/tests/cli/dump/alias.wat.stdout +++ b/tests/cli/dump/alias.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 1f | component type section 0xa | 01 | 1 count - 0xb | 42 04 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { params: [], result: None })), Export { name: ComponentExportName("f1"), ty: Func(0) }, Type(Func(ComponentFuncType { params: [("p1", Primitive(String))], result: None })), Export { name: ComponentExportName("f2"), ty: Func(1) }]) + 0xb | 42 04 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName("f1"), ty: Func(0) }, Type(Func(ComponentFuncType { async_: false, params: [("p1", Primitive(String))], result: None })), Export { name: ComponentExportName("f2"), ty: Func(1) }]) | 00 01 00 04 | 00 02 66 31 | 01 00 01 40 diff --git a/tests/cli/dump/alias2.wat.stdout b/tests/cli/dump/alias2.wat.stdout index 47695fca9e..07f1aed469 100644 --- a/tests/cli/dump/alias2.wat.stdout +++ b/tests/cli/dump/alias2.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExportName("a"), ty: Module(0) }, Type(Func(ComponentFuncType { params: [], result: None })), Export { name: ComponentExportName("b"), ty: Func(0) }, Export { name: ComponentExportName("c"), ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExportName("d"), ty: Instance(1) }, Type(Component([])), Export { name: ComponentExportName("e"), ty: Component(2) }]) + 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExportName("a"), ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName("b"), ty: Func(0) }, Export { name: ComponentExportName("c"), ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExportName("d"), ty: Instance(1) }, Type(Component([])), Export { name: ComponentExportName("e"), ty: Component(2) }]) | 00 04 00 01 | 61 00 11 00 | 01 40 00 01 @@ -30,7 +30,7 @@ | 11 00 0x5a | 07 05 | component type section 0x5c | 01 | 1 count - 0x5d | 40 00 01 00 | [type 0] Func(ComponentFuncType { params: [], result: None }) + 0x5d | 40 00 01 00 | [type 0] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x61 | 0a 0b | component import section 0x63 | 02 | 2 count 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentImportName("b"), ty: Func(0) } diff --git a/tests/cli/dump/bundled.wat.stdout b/tests/cli/dump/bundled.wat.stdout index ff40ee87fe..a7c135a07d 100644 --- a/tests/cli/dump/bundled.wat.stdout +++ b/tests/cli/dump/bundled.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 06 01 70 | [type 0] Instance([Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { params: [("len", Primitive(U32))], result: Some(Type(0)) })), Export { name: ComponentExportName("read"), ty: Func(1) }, Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { params: [("buf", Type(2))], result: Some(Primitive(U32)) })), Export { name: ComponentExportName("write"), ty: Func(3) }]) + 0xb | 42 06 01 70 | [type 0] Instance([Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("len", Primitive(U32))], result: Some(Type(0)) })), Export { name: ComponentExportName("read"), ty: Func(1) }, Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("buf", Type(2))], result: Some(Primitive(U32)) })), Export { name: ComponentExportName("write"), ty: Func(3) }]) | 7d 01 40 01 | 03 6c 65 6e | 79 00 00 04 @@ -176,7 +176,7 @@ | 65 12 02 0x1ad | 07 05 | component type section 0x1af | 01 | 1 count - 0x1b0 | 40 00 01 00 | [type 1] Func(ComponentFuncType { params: [], result: None }) + 0x1b0 | 40 00 01 00 | [type 1] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x1b4 | 06 1e | component alias section 0x1b6 | 03 | 3 count 0x1b7 | 00 00 01 03 | alias [core func 2] CoreInstanceExport { kind: Func, instance_index: 3, name: "play" } diff --git a/tests/cli/dump/component-inline-type.wat.stdout b/tests/cli/dump/component-inline-type.wat.stdout index 8023a9b01b..4d0d178807 100644 --- a/tests/cli/dump/component-inline-type.wat.stdout +++ b/tests/cli/dump/component-inline-type.wat.stdout @@ -23,7 +23,7 @@ | 01 0x30 | 07 05 | component type section 0x32 | 01 | 1 count - 0x33 | 40 00 01 00 | [type 2] Func(ComponentFuncType { params: [], result: None }) + 0x33 | 40 00 01 00 | [type 2] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x37 | 0a 06 | component import section 0x39 | 01 | 1 count 0x3a | 00 01 64 01 | [func 0] ComponentImport { name: ComponentImportName("d"), ty: Func(2) } diff --git a/tests/cli/dump/component-instance-type.wat.stdout b/tests/cli/dump/component-instance-type.wat.stdout index 52b5bdc484..8b98d2b96e 100644 --- a/tests/cli/dump/component-instance-type.wat.stdout +++ b/tests/cli/dump/component-instance-type.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 28 | component type section 0xa | 01 | 1 count - 0xb | 42 03 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { params: [], result: None })), Type(Component([Type(Func(ComponentFuncType { params: [], result: None })), Alias(Outer { kind: Type, count: 1, index: 0 }), Import(ComponentImport { name: ComponentImportName("1"), ty: Func(0) }), Export { name: ComponentExportName("1"), ty: Func(1) }])), Export { name: ComponentExportName("c5"), ty: Component(1) }]) + 0xb | 42 03 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Type(Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Alias(Outer { kind: Type, count: 1, index: 0 }), Import(ComponentImport { name: ComponentImportName("1"), ty: Func(0) }), Export { name: ComponentExportName("1"), ty: Func(1) }])), Export { name: ComponentExportName("c5"), ty: Component(1) }]) | 00 01 00 01 | 41 04 01 40 | 00 01 00 02 diff --git a/tests/cli/dump/component-linking.wat.stdout b/tests/cli/dump/component-linking.wat.stdout index c69dec6467..1e5e5cc3d9 100644 --- a/tests/cli/dump/component-linking.wat.stdout +++ b/tests/cli/dump/component-linking.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExportName("a"), ty: Module(0) }, Type(Func(ComponentFuncType { params: [], result: None })), Export { name: ComponentExportName("b"), ty: Func(0) }, Export { name: ComponentExportName("c"), ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExportName("d"), ty: Instance(1) }, Type(Component([])), Export { name: ComponentExportName("e"), ty: Component(2) }]) + 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExportName("a"), ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName("b"), ty: Func(0) }, Export { name: ComponentExportName("c"), ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExportName("d"), ty: Instance(1) }, Type(Component([])), Export { name: ComponentExportName("e"), ty: Component(2) }]) | 00 04 00 01 | 61 00 11 00 | 01 40 00 01 @@ -30,7 +30,7 @@ | 11 00 0x5a | 07 05 | component type section 0x5c | 01 | 1 count - 0x5d | 40 00 01 00 | [type 0] Func(ComponentFuncType { params: [], result: None }) + 0x5d | 40 00 01 00 | [type 0] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x61 | 0a 0b | component import section 0x63 | 02 | 2 count 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentImportName("b"), ty: Func(0) } diff --git a/tests/cli/dump/component-outer-alias.wat.stdout b/tests/cli/dump/component-outer-alias.wat.stdout index 7ce95d09ec..8fe249003f 100644 --- a/tests/cli/dump/component-outer-alias.wat.stdout +++ b/tests/cli/dump/component-outer-alias.wat.stdout @@ -3,7 +3,7 @@ 0x8 | 07 0e | component type section 0xa | 02 | 2 count 0xb | 73 | [type 0] Defined(Primitive(String)) - 0xc | 41 02 02 03 | [type 1] Component([Alias(Outer { kind: Type, count: 1, index: 0 }), Type(Func(ComponentFuncType { params: [], result: Some(Type(0)) }))]) + 0xc | 41 02 02 03 | [type 1] Component([Alias(Outer { kind: Type, count: 1, index: 0 }), Type(Func(ComponentFuncType { async_: false, params: [], result: Some(Type(0)) }))]) | 02 01 00 01 | 40 00 00 00 0x18 | 04 2e | [component 0] inline size @@ -14,7 +14,7 @@ 0x25 | 03 02 01 00 | alias [type 0] Outer { kind: Type, count: 1, index: 0 } 0x29 | 07 05 | component type section 0x2b | 01 | 1 count - 0x2c | 40 00 00 00 | [type 1] Func(ComponentFuncType { params: [], result: Some(Type(0)) }) + 0x2c | 40 00 00 00 | [type 1] Func(ComponentFuncType { async_: false, params: [], result: Some(Type(0)) }) 0x30 | 00 16 | custom section 0x32 | 0e 63 6f 6d | name: "component-name" | 70 6f 6e 65 @@ -31,8 +31,8 @@ 0x55 | 03 02 01 00 | alias [type 0] Outer { kind: Type, count: 1, index: 0 } 0x59 | 07 09 | component type section 0x5b | 02 | 2 count - 0x5c | 40 00 00 00 | [type 1] Func(ComponentFuncType { params: [], result: Some(Type(0)) }) - 0x60 | 40 00 00 00 | [type 2] Func(ComponentFuncType { params: [], result: Some(Type(0)) }) + 0x5c | 40 00 00 00 | [type 1] Func(ComponentFuncType { async_: false, params: [], result: Some(Type(0)) }) + 0x60 | 40 00 00 00 | [type 2] Func(ComponentFuncType { async_: false, params: [], result: Some(Type(0)) }) 0x64 | 00 16 | custom section 0x66 | 0e 63 6f 6d | name: "component-name" | 70 6f 6e 65 @@ -52,8 +52,8 @@ 0x8d | 03 02 01 02 | alias [type 0] Outer { kind: Type, count: 1, index: 2 } 0x91 | 07 09 | component type section 0x93 | 02 | 2 count - 0x94 | 40 00 00 00 | [type 1] Func(ComponentFuncType { params: [], result: Some(Type(0)) }) - 0x98 | 40 00 00 00 | [type 2] Func(ComponentFuncType { params: [], result: Some(Type(0)) }) + 0x94 | 40 00 00 00 | [type 1] Func(ComponentFuncType { async_: false, params: [], result: Some(Type(0)) }) + 0x98 | 40 00 00 00 | [type 2] Func(ComponentFuncType { async_: false, params: [], result: Some(Type(0)) }) 0x9c | 00 17 | custom section 0x9e | 0e 63 6f 6d | name: "component-name" | 70 6f 6e 65 diff --git a/tests/cli/dump/instance-expand.wat.stdout b/tests/cli/dump/instance-expand.wat.stdout index d90ee35d82..48cf6e3d5c 100644 --- a/tests/cli/dump/instance-expand.wat.stdout +++ b/tests/cli/dump/instance-expand.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 0d | component type section 0xa | 01 | 1 count - 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { params: [], result: None })), Export { name: ComponentExportName(""), ty: Func(0) }]) + 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName(""), ty: Func(0) }]) | 00 01 00 04 | 00 00 01 00 0x17 | 0a 06 | component import section diff --git a/tests/cli/dump/instance-type.wat.stdout b/tests/cli/dump/instance-type.wat.stdout index 0fecf8a819..9d1e20356a 100644 --- a/tests/cli/dump/instance-type.wat.stdout +++ b/tests/cli/dump/instance-type.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 17 | component type section 0xa | 02 | 2 count - 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { params: [], result: None })), Export { name: ComponentExportName(""), ty: Func(0) }]) + 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName(""), ty: Func(0) }]) | 00 01 00 04 | 00 00 01 00 0x17 | 42 02 01 42 | [type 1] Instance([Type(Instance([])), Export { name: ComponentExportName(""), ty: Instance(0) }]) diff --git a/tests/cli/dump/instantiate.wat.stdout b/tests/cli/dump/instantiate.wat.stdout index edd401fb4e..84883c42aa 100644 --- a/tests/cli/dump/instantiate.wat.stdout +++ b/tests/cli/dump/instantiate.wat.stdout @@ -9,7 +9,7 @@ | 00 0x15 | 07 05 | component type section 0x17 | 01 | 1 count - 0x18 | 40 00 01 00 | [type 1] Func(ComponentFuncType { params: [], result: None }) + 0x18 | 40 00 01 00 | [type 1] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x1c | 0a 06 | component import section 0x1e | 01 | 1 count 0x1f | 00 01 66 01 | [func 0] ComponentImport { name: ComponentImportName("f"), ty: Func(1) } diff --git a/tests/cli/dump/instantiate2.wat.stdout b/tests/cli/dump/instantiate2.wat.stdout index bf505f389f..194a87c901 100644 --- a/tests/cli/dump/instantiate2.wat.stdout +++ b/tests/cli/dump/instantiate2.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 0e | component type section 0xa | 01 | 1 count - 0xb | 41 02 01 40 | [type 0] Component([Type(Func(ComponentFuncType { params: [], result: None })), Import(ComponentImport { name: ComponentImportName("a"), ty: Func(0) })]) + 0xb | 41 02 01 40 | [type 0] Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Import(ComponentImport { name: ComponentImportName("a"), ty: Func(0) })]) | 00 01 00 03 | 00 01 61 01 | 00 diff --git a/tests/cli/dump/nested-component.wat.stdout b/tests/cli/dump/nested-component.wat.stdout index 3a412280d8..5c44753924 100644 --- a/tests/cli/dump/nested-component.wat.stdout +++ b/tests/cli/dump/nested-component.wat.stdout @@ -35,7 +35,7 @@ 0x64 | 01 6d | "m" 0x66 | 07 08 | component type section 0x68 | 01 | 1 count - 0x69 | 40 01 01 70 | [type 0] Func(ComponentFuncType { params: [("p", Primitive(String))], result: None }) + 0x69 | 40 01 01 70 | [type 0] Func(ComponentFuncType { async_: false, params: [("p", Primitive(String))], result: None }) | 73 01 00 0x70 | 0a 06 | component import section 0x72 | 01 | 1 count @@ -47,7 +47,7 @@ | 11 00 00 0x82 | 07 0e | component type section 0x84 | 01 | 1 count - 0x85 | 42 02 01 40 | [type 1] Instance([Type(Func(ComponentFuncType { params: [], result: None })), Export { name: ComponentExportName("a"), ty: Func(0) }]) + 0x85 | 42 02 01 40 | [type 1] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExportName("a"), ty: Func(0) }]) | 00 01 00 04 | 00 01 61 01 | 00 diff --git a/tests/cli/missing-features/component-model/async.wast b/tests/cli/missing-features/component-model/async.wast index 5b8e4a14ee..3682381e72 100644 --- a/tests/cli/missing-features/component-model/async.wast +++ b/tests/cli/missing-features/component-model/async.wast @@ -367,17 +367,8 @@ "requires the component model async builtins feature" ) +;; async function types (assert_invalid - (component (import "[async]f" (func))) - "require the component model async feature" -) - -(assert_invalid - (component (import "[async method]a.b" (func))) - "require the component model async feature" -) - -(assert_invalid - (component (import "[async static]a.b" (func))) - "require the component model async feature" + (component (import "x" (func async))) + "async component functions require the component model async feature" ) diff --git a/tests/snapshots/cli/component-model/async/names.wast.json b/tests/snapshots/cli/component-model/async/names.wast.json index 34bf1a1b78..ac16aa47f4 100644 --- a/tests/snapshots/cli/component-model/async/names.wast.json +++ b/tests/snapshots/cli/component-model/async/names.wast.json @@ -2,87 +2,32 @@ "source_filename": "tests/cli/component-model/async/names.wast", "commands": [ { - "type": "module", - "line": 3, + "type": "assert_invalid", + "line": 7, "filename": "names.0.wasm", - "module_type": "binary" + "module_type": "binary", + "text": "not in kebab case" }, { "type": "assert_invalid", - "line": 14, + "line": 13, "filename": "names.1.wasm", "module_type": "binary", - "text": "conflicts with previous name" + "text": "not in kebab case" }, { "type": "assert_invalid", - "line": 21, + "line": 19, "filename": "names.2.wasm", "module_type": "binary", - "text": "conflicts with previous name" + "text": "not in kebab case" }, { "type": "assert_invalid", - "line": 28, + "line": 26, "filename": "names.3.wasm", "module_type": "binary", - "text": "conflicts with previous name" - }, - { - "type": "assert_invalid", - "line": 35, - "filename": "names.4.wasm", - "module_type": "binary", - "text": "conflicts with previous name" - }, - { - "type": "assert_invalid", - "line": 42, - "filename": "names.5.wasm", - "module_type": "binary", - "text": "conflicts with previous name" - }, - { - "type": "assert_invalid", - "line": 49, - "filename": "names.6.wasm", - "module_type": "binary", - "text": "conflicts with previous name" - }, - { - "type": "assert_invalid", - "line": 56, - "filename": "names.7.wasm", - "module_type": "binary", - "text": "conflicts with previous name" - }, - { - "type": "assert_invalid", - "line": 63, - "filename": "names.8.wasm", - "module_type": "binary", - "text": "conflicts with previous name" - }, - { - "type": "assert_invalid", - "line": 70, - "filename": "names.9.wasm", - "module_type": "binary", - "text": "conflicts with previous name" - }, - { - "type": "assert_invalid", - "line": 77, - "filename": "names.10.wasm", - "module_type": "binary", - "text": "import name `[async method]a.a` conflicts with previous name `a`" - }, - { - "type": "assert_invalid", - "line": 84, - "filename": "names.11.wasm", - "module_type": "binary", - "text": "import name `[async static]a.a` conflicts with previous name `a`" + "text": "constructor function cannot be async" } ] } \ No newline at end of file diff --git a/tests/snapshots/cli/missing-features/component-model/async.wast.json b/tests/snapshots/cli/missing-features/component-model/async.wast.json index 1748404365..6f945d4a3c 100644 --- a/tests/snapshots/cli/missing-features/component-model/async.wast.json +++ b/tests/snapshots/cli/missing-features/component-model/async.wast.json @@ -220,24 +220,10 @@ }, { "type": "assert_invalid", - "line": 371, + "line": 372, "filename": "async.31.wasm", "module_type": "binary", - "text": "require the component model async feature" - }, - { - "type": "assert_invalid", - "line": 376, - "filename": "async.32.wasm", - "module_type": "binary", - "text": "require the component model async feature" - }, - { - "type": "assert_invalid", - "line": 381, - "filename": "async.33.wasm", - "module_type": "binary", - "text": "require the component model async feature" + "text": "async component functions require the component model async feature" } ] } \ No newline at end of file From eec81a14bfab16f490405882fe37512a0712f2d4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 13 Nov 2025 14:21:01 -0800 Subject: [PATCH 2/4] Delete outdated snapshot --- .../cli/component-model/async/names.wast/0.print | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 tests/snapshots/cli/component-model/async/names.wast/0.print diff --git a/tests/snapshots/cli/component-model/async/names.wast/0.print b/tests/snapshots/cli/component-model/async/names.wast/0.print deleted file mode 100644 index 77183e5f8c..0000000000 --- a/tests/snapshots/cli/component-model/async/names.wast/0.print +++ /dev/null @@ -1,10 +0,0 @@ -(component - (type (;0;) (func)) - (import "[async]f" (func (;0;) (type 0))) - (import "r" (type $r (;1;) (sub resource))) - (type (;2;) (borrow $r)) - (type (;3;) (func (param "self" 2))) - (import "[async method]r.f" (func (;1;) (type 3))) - (type (;4;) (func)) - (import "[async static]r.f2" (func (;2;) (type 4))) -) From a6551ef1ef70d7b8f6ce80b4e3d1dcb7d1c26e1a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 13 Nov 2025 19:45:05 -0800 Subject: [PATCH 3/4] Propagate async-ness in wasm-compose too --- crates/wasm-compose/src/encoding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index 6782a99fd2..5022f7f388 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -482,7 +482,7 @@ impl<'a> TypeEncoder<'a> { let index = state.cur.encodable.type_count(); let mut f = state.cur.encodable.ty().function(); - f.params(params).result(result); + f.async_(ty.async_).params(params).result(result); index } From e5a18e016894da309897b2d99f191e1b0a1e3403 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 13 Nov 2025 19:52:05 -0800 Subject: [PATCH 4/4] Add `async` to the function type hash key --- crates/wit-component/src/encoding/types.rs | 2 ++ tests/cli/dummy-ctor-static-async-confusion.wit | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 tests/cli/dummy-ctor-static-async-confusion.wit diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index abbda325d9..2dec9fbf5e 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -10,6 +10,7 @@ use wit_parser::{ /// Represents a key type for interface function definitions. #[derive(Hash, PartialEq, Eq, Clone)] pub struct FunctionKey<'a> { + async_: bool, params: &'a [(String, Type)], result: &'a Option, } @@ -98,6 +99,7 @@ pub trait ValtypeEncoder<'a> { /// document. fn encode_func_type(&mut self, resolve: &'a Resolve, func: &'a Function) -> Result { let key = FunctionKey { + async_: func.kind.is_async(), params: &func.params, result: &func.result, }; diff --git a/tests/cli/dummy-ctor-static-async-confusion.wit b/tests/cli/dummy-ctor-static-async-confusion.wit new file mode 100644 index 0000000000..77238a6d0e --- /dev/null +++ b/tests/cli/dummy-ctor-static-async-confusion.wit @@ -0,0 +1,16 @@ +// RUN: component embed --dummy-names legacy --async-stackful % -t | \ +// component new | \ +// validate -f cm-async + +package a:b; + +interface i { + resource x { + foo: static async func() -> x; + constructor(); + } +} + +world w { + import i; +}