diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 38caf7c14..ba3fd8a52 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -655,12 +655,46 @@ macro_rules! {macro_name} {{ } } + // `use` statements to bring every named type reachable from the + // payload into scope of the generated `vtable{ordinal}` module. + // + // Without these, a type like `future>` where + // `R` is brought in via `use t.{r}` at world level emits + // `-> Result` inside `vtable1`, + // with `R` unresolved: the composite type is anonymous so no + // owner-qualified path is inserted, and the world-level alias + // `pub type R = ...;` lives two modules up. Importing it here + // makes the bare identifier resolve regardless of how deeply + // nested the payload type is. + let mut type_imports = BTreeSet::new(); + if let Some(ty) = payload_type { + collect_named_type_ids(self.resolve, ty, &mut type_imports); + } + let type_imports: String = type_imports + .into_iter() + .map(|id| { + let path = self.type_path(id, /*owned=*/ true); + // `type_path` emits the full `super::super::...::Name` + // when the type is owned by an interface, but falls + // back to a bare name when the owner is a world (the + // `use interface.{T}` alias case). Bare names need the + // `super::super::` prefix to reach the world-level + // `pub type T = ...;` alias at macro root. + let path = if path.starts_with("super::") { + path + } else { + format!("super::super::{path}") + }; + format!(" use {path};\n") + }) + .collect(); + let code = format!( r#" #[doc(hidden)] -#[allow(unused_unsafe)] +#[allow(unused_unsafe, unused_imports)] pub mod vtable{ordinal} {{ - +{type_imports} #[cfg(not(target_arch = "wasm32"))] unsafe extern "C" fn cancel_write(_: u32) -> u32 {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] @@ -3157,3 +3191,96 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< self.interface.push_str(&format!("; {size}]")); } } + +/// Walk `ty`'s type tree and insert every named type id into `out`. +/// +/// A type id is "named" if `resolve.types[id].name` is set, i.e. it is +/// a user-defined type (record, variant, enum, flags, resource, type +/// alias) as opposed to an anonymous structural one (`result<_, _>`, +/// `tuple<_, _>`, `list<_>`, etc.). We still recurse through the +/// anonymous composites to collect their inner named types — that's +/// the whole reason this exists: `future>` hides `R` one +/// level below an anonymous `result` that the emitter never flags. +/// +/// Used by stream/future payload emission to pre-compute `use` +/// statements for every named type the emitted `vtable{ordinal}` +/// module will reference, so bare identifiers inside the `fn lift` / +/// `fn lower` / `impl StreamPayload` bodies resolve even when the +/// type lives at macro-root via a world-level `use` alias. +fn collect_named_type_ids(resolve: &Resolve, ty: &Type, out: &mut BTreeSet) { + let Type::Id(id) = ty else { return }; + collect_named_type_ids_from_id(resolve, *id, out); +} + +fn collect_named_type_ids_from_id(resolve: &Resolve, id: TypeId, out: &mut BTreeSet) { + // Avoid cycles: a named type we've already queued doesn't need a + // second walk through its structure. + if out.contains(&id) { + return; + } + let ty = &resolve.types[id]; + let named = ty.name.is_some(); + if named { + out.insert(id); + // A named alias (e.g. `use t.{r};` or `type x = r;`) is what + // the emitter references verbatim — its target is a separate + // type def reachable by its own name. Walking through the + // alias would import both the alias and its target under the + // same Rust identifier and trip E0252. Stop at the alias. + if matches!(ty.kind, TypeDefKind::Type(_)) { + return; + } + } + match &ty.kind { + TypeDefKind::Record(r) => { + for field in &r.fields { + collect_named_type_ids(resolve, &field.ty, out); + } + } + TypeDefKind::Variant(v) => { + for case in &v.cases { + if let Some(ty) = &case.ty { + collect_named_type_ids(resolve, ty, out); + } + } + } + TypeDefKind::Tuple(t) => { + for ty in &t.types { + collect_named_type_ids(resolve, ty, out); + } + } + TypeDefKind::Option(t) + | TypeDefKind::List(t) + | TypeDefKind::FixedLengthList(t, _) + | TypeDefKind::Type(t) => { + collect_named_type_ids(resolve, t, out); + } + TypeDefKind::Result(r) => { + if let Some(ty) = &r.ok { + collect_named_type_ids(resolve, ty, out); + } + if let Some(ty) = &r.err { + collect_named_type_ids(resolve, ty, out); + } + } + TypeDefKind::Map(k, v) => { + collect_named_type_ids(resolve, k, out); + collect_named_type_ids(resolve, v, out); + } + TypeDefKind::Future(inner) | TypeDefKind::Stream(inner) => { + if let Some(ty) = inner { + collect_named_type_ids(resolve, ty, out); + } + } + TypeDefKind::Handle(h) => { + let resource_id = match h { + Handle::Own(id) | Handle::Borrow(id) => *id, + }; + collect_named_type_ids_from_id(resolve, resource_id, out); + } + TypeDefKind::Enum(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Resource + | TypeDefKind::Unknown => {} + } +} diff --git a/crates/rust/tests/codegen.rs b/crates/rust/tests/codegen.rs index a6df22cf9..20379dc0d 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -235,3 +235,29 @@ mod method_chaining { enable_method_chaining: true }); } + +// Regression: a named type brought in via `use interface.{T}` at world +// level, embedded inside `future>` on an exported function, +// used to emit unresolved bare `T` references in the generated +// `wit_future::vtable{N}` module. See issue #1598. The fix walks the +// payload type tree and emits a `use super::super::...;` per named type +// at the top of each vtable module. +#[allow(unused, reason = "testing codegen, not functionality")] +mod issue_1598_future_result_use { + wit_bindgen::generate!({ + inline: r#" + package a:b; + + world w { + use t.{r}; + export f: func() -> future>; + } + + interface t { + record r { + x: u32, + } + } + "#, + }); +}