diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 3927835af..5cb339c1c 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -94,8 +94,27 @@ impl Types { } } } - pub fn collect_equal_types(&mut self, resolve: &Resolve) { + + /// Populates the return value of [`Types::get_representative_type`] with + /// the `resolve` passed in. + /// + /// The `may_alias_another_type` closure is used to determine whether the + /// language's definition of the provided `TypeId` might possibly alias + /// some other type in a language. This is a language-specific deduction + /// which can also be affected by options to a binding generator. If a type + /// can't be aliased by anything else then it can't be considered equal to + /// anything else. Note that in this situations other types may still + /// be equal to it, such as aliased types at the WIT level (e.g. `type foo + /// = some-record`). + pub fn collect_equal_types( + &mut self, + resolve: &Resolve, + may_alias_another_type: &dyn Fn(TypeId) -> bool, + ) { for (i, (ty, _)) in resolve.types.iter().enumerate() { + if !may_alias_another_type(ty) { + continue; + } // TODO: we could define a hash function for TypeDefKind to prevent the inner loop. for (earlier, _) in resolve.types.iter().take(i) { if self.equal_types.find(ty) == self.equal_types.find(earlier) { diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 5ac5b60c0..dfe01ab1b 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -1,6 +1,6 @@ use crate::{ - ConstructorReturnType, Identifier, InterfaceGenerator, RustFlagsRepr, - classify_constructor_return_type, int_repr, to_rust_ident, + ConstructorReturnType, InterfaceGenerator, RustFlagsRepr, classify_constructor_return_type, + int_repr, to_rust_ident, }; use heck::*; use std::fmt::Write as _; @@ -428,28 +428,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { let async_support = self.r#gen.r#gen.async_support_path(); let op = &operands[0]; let name = match payload { - Some(Type::Id(type_id)) => { - let dealiased_id = dealias(resolve, *type_id); - self.r#gen.type_name_owned_with_id( - &Type::Id(dealiased_id), - Identifier::StreamOrFuturePayload, - ) - } - Some(ty) => self - .r#gen - .type_name_owned_with_id(ty, Identifier::StreamOrFuturePayload), - None => "()".into(), + Some(ty) => self.r#gen.type_name_owned(ty), + None => "()".to_string(), }; - let ordinal = self - .r#gen - .r#gen - .future_payloads - .get_index_of(&name) - .unwrap(); let path = self.r#gen.path_to_root(); results.push(format!( "{async_support}::FutureReader::new\ - ({op} as u32, &{path}wit_future::vtable{ordinal}::VTABLE)" + ({op} as u32, &<{name} as {path}wit_future::FuturePayload>::VTABLE)" )) } @@ -462,28 +447,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { let async_support = self.r#gen.r#gen.async_support_path(); let op = &operands[0]; let name = match payload { - Some(Type::Id(type_id)) => { - let dealiased_id = dealias(resolve, *type_id); - self.r#gen.type_name_owned_with_id( - &Type::Id(dealiased_id), - Identifier::StreamOrFuturePayload, - ) - } - Some(ty) => self - .r#gen - .type_name_owned_with_id(ty, Identifier::StreamOrFuturePayload), - None => "()".into(), + Some(ty) => self.r#gen.type_name_owned(ty), + None => "()".to_string(), }; - let ordinal = self - .r#gen - .r#gen - .stream_payloads - .get_index_of(&name) - .unwrap(); let path = self.r#gen.path_to_root(); results.push(format!( "{async_support}::StreamReader::new\ - ({op} as u32, &{path}wit_stream::vtable{ordinal}::VTABLE)" + ({op} as u32, &<{name} as {path}wit_stream::StreamPayload>::VTABLE)" )) } @@ -1281,7 +1251,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(&format!( " let array{tmp}: [_; {size}] = core::array::from_fn(|{index_var}| {{ let base = {base}.add({index_var} * {elemsize}); - {body} + {body} }});" )); let result = format!("array{tmp}"); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index ae0f991c8..8e4eade44 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -523,11 +523,30 @@ macro_rules! {macro_name} {{ func_name: &str, payload_type: Option<&Type>, ) { - let name = match payload_type { - Some(Type::Id(type_id)) => { - let dealiased_id = dealias(self.resolve, *type_id); - self.type_name_owned(&Type::Id(dealiased_id)) + let payload_type = match payload_type { + // Rust requires one-impl-per-type, so any `id` here is transformed + // into its canonical representation using + // `get_representative_type`. This ensures that type aliases, uses, + // etc, all get canonicalized to the exact same ID regardless of + // type structure. + // + // Note that `get_representative_type` maps ids-to-ids which is 95% + // of what we want, but this additionally goes one layer further to + // see if the final id is actually itself a typedef, which would + // always be to a primitive, and then uses the primitive type + // instead of the typedef to canonicalize with other streams/futures + // using the primitive type. + Some(Type::Id(id)) => { + let id = self.r#gen.types.get_representative_type(*id); + match self.resolve.types[id].kind { + TypeDefKind::Type(t) => Some(t), + _ => Some(Type::Id(id)), + } } + other => other.copied(), + }; + let payload_type = payload_type.as_ref(); + let name = match payload_type { Some(payload_type) => self.type_name_owned(payload_type), None => "()".into(), }; @@ -536,7 +555,7 @@ macro_rules! {macro_name} {{ PayloadFor::Stream => &mut self.r#gen.stream_payloads, }; - if map.contains_key(&name) { + if map.contains_key(&payload_type.copied()) { return; } let ordinal = map.len(); @@ -685,7 +704,7 @@ pub mod vtable{ordinal} {{ PayloadFor::Future => &mut self.r#gen.future_payloads, PayloadFor::Stream => &mut self.r#gen.stream_payloads, }; - map.insert(name, code); + map.insert(payload_type.copied(), code); } fn generate_guest_import(&mut self, func: &Function, interface: Option<&WorldKey>) { @@ -1713,14 +1732,7 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) } } - pub(crate) fn type_name_owned_with_id(&mut self, ty: &Type, id: Identifier<'i>) -> String { - let old_identifier = mem::replace(&mut self.identifier, id); - let name = self.type_name_owned(ty); - self.identifier = old_identifier; - name - } - - fn type_name_owned(&mut self, ty: &Type) -> String { + pub fn type_name_owned(&mut self, ty: &Type) -> String { self.type_name( ty, TypeMode { @@ -2579,7 +2591,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { fn define_type(&mut self, name: &str, id: TypeId) { let equal = self.r#gen.types.get_representative_type(id); - if equal == id { + if !self.r#gen.opts.merge_structurally_equal_types() || equal == id { wit_bindgen_core::define_type(self, name, id) } else { let docs = &self.resolve.types[id].docs; diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 1bffaf22d..9ace98f17 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -50,8 +50,8 @@ pub struct RustWasm { /// Maps wit interface and type names to their Rust identifiers with: GenerationConfiguration, - future_payloads: IndexMap, - stream_payloads: IndexMap, + future_payloads: IndexMap, String>, + stream_payloads: IndexMap, String>, } #[derive(Default)] @@ -1128,9 +1128,39 @@ impl WorldGenerator for RustWasm { uwriteln!(self.src_preamble, "// * async: {opt}"); } self.types.analyze(resolve); - if self.opts.merge_structurally_equal_types() { - self.types.collect_equal_types(resolve); - } + self.types.collect_equal_types(resolve, &|a| { + // If `--merge-structurally-equal-types` is enabled then any type + // anywhere can be generated as a type alias to anything else. + if self.opts.merge_structurally_equal_types() { + return true; + } + + match resolve.types[a].kind { + // These types are all defined with `type Foo = ...` in Rust + // since Rust either has native representations or they live in + // libraries or similar. + TypeDefKind::Type(_) + | TypeDefKind::Handle(_) + | TypeDefKind::List(_) + | TypeDefKind::Tuple(_) + | TypeDefKind::Option(_) + | TypeDefKind::Result(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::Map(..) + | TypeDefKind::FixedLengthList(..) => true, + + // These types are all defined with fresh new types defined + // in generated bindings and thus can't alias some other + // existing type. + TypeDefKind::Record(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Resource + | TypeDefKind::Unknown => false, + } + }); self.world = Some(world); let world = &resolve.worlds[world]; diff --git a/crates/test/src/cpp.rs b/crates/test/src/cpp.rs index 5253de14d..ff10eeb12 100644 --- a/crates/test/src/cpp.rs +++ b/crates/test/src/cpp.rs @@ -41,22 +41,11 @@ impl LanguageMethods for Cpp { fn should_fail_verify( &self, - name: &str, - _config: &crate::config::WitConfig, + _name: &str, + config: &crate::config::WitConfig, _args: &[String], ) -> bool { - match name { - "async-trait-function.wit" - | "error-context.wit" - | "futures.wit" - | "import-export-future.wit" - | "import-export-stream.wit" - | "resources-with-futures.wit" - | "resources-with-streams.wit" - | "streams.wit" - | "async-resource-func.wit" => true, - _ => false, - } + config.async_ } fn prepare(&self, runner: &mut Runner) -> anyhow::Result<()> { diff --git a/crates/test/src/csharp.rs b/crates/test/src/csharp.rs index 44758678b..a26eddac7 100644 --- a/crates/test/src/csharp.rs +++ b/crates/test/src/csharp.rs @@ -49,6 +49,9 @@ impl LanguageMethods for Csharp { | "resource-fallible-constructor.wit" | "async-resource-func.wit" | "import-export-stream.wit" + | "issue-1432.wit" + | "issue-1433.wit" + | "future-same-type-different-names.wit" ) } diff --git a/crates/test/src/rust.rs b/crates/test/src/rust.rs index 1e9be1b44..d7d3fa389 100644 --- a/crates/test/src/rust.rs +++ b/crates/test/src/rust.rs @@ -63,8 +63,9 @@ impl LanguageMethods for Rust { // no_std doesn't currently work with async if config.async_ && args.iter().any(|s| s == "--std-feature") - // Except this one actually _does_ work: + // Except these actually do work: && name != "async-trait-function.wit-no-std" + && name != "async-resource-func.wit-no-std" { return true; } diff --git a/tests/codegen/async-resource-func.wit b/tests/codegen/async-resource-func.wit index b9704c1f2..41f0e205c 100644 --- a/tests/codegen/async-resource-func.wit +++ b/tests/codegen/async-resource-func.wit @@ -1,3 +1,5 @@ +//@ async = true + package foo:bar; world bindings { diff --git a/tests/codegen/future-same-type-different-names.wit b/tests/codegen/future-same-type-different-names.wit new file mode 100644 index 000000000..33818b8a0 --- /dev/null +++ b/tests/codegen/future-same-type-different-names.wit @@ -0,0 +1,67 @@ +//@ async = true + +package a:b; + +interface i { + resource a; + f1: func(x: future); + + type b = a; + f2: func(x: future); +} + +interface i2 { + use i.{a}; + f3: func(x: future); + + use i.{b}; + f4: func(x: future); +} + +interface i3 { + f5: func(x: future); + + type hello = u8; + f6: func(x: future); +} + +interface i4 { + type a = result; + f7: func(x: future); + + type b = result; + f8: func(x: future); +} + +interface i5 { + type a = result; + f9: func(x: future); +} + +interface i6 { + enum error-code { + io, + closed, + } +} + +interface i7 { + use i6.{error-code}; + f10: func() -> future>; +} + +interface i8 { + use i6.{error-code}; + f11: func() -> future>; +} + +world foo { + import i; + import i2; + import i3; + import i4; + import i5; + import i6; + import i7; + import i8; +} diff --git a/tests/codegen/issue-1432.wit b/tests/codegen/issue-1432.wit new file mode 100644 index 000000000..e569c9759 --- /dev/null +++ b/tests/codegen/issue-1432.wit @@ -0,0 +1,19 @@ +//@ async = true + +package a:b; + +world w { + import i; + export i; +} + +interface i { + use t.{r}; + f: func(p: stream); +} + +interface t { + record r { + x: u32, + } +} diff --git a/tests/codegen/issue-1433.wit b/tests/codegen/issue-1433.wit new file mode 100644 index 000000000..f332a949a --- /dev/null +++ b/tests/codegen/issue-1433.wit @@ -0,0 +1,14 @@ +//@ async = true + +package a:b; + +world w { + use t.{r}; + export f: func(p: stream); +} + +interface t { + record r { + x: u32, + } +}