Skip to content

Commit a41c71f

Browse files
rust: qualify named types in future/stream payload vtables
1 parent 2e00369 commit a41c71f

File tree

1 file changed

+129
-2
lines changed

1 file changed

+129
-2
lines changed

crates/rust/src/interface.rs

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,12 +655,46 @@ macro_rules! {macro_name} {{
655655
}
656656
}
657657

658+
// `use` statements to bring every named type reachable from the
659+
// payload into scope of the generated `vtable{ordinal}` module.
660+
//
661+
// Without these, a type like `future<result<R, string>>` where
662+
// `R` is brought in via `use t.{r}` at world level emits
663+
// `-> Result<R, super::super::_rt::String>` inside `vtable1`,
664+
// with `R` unresolved: the composite type is anonymous so no
665+
// owner-qualified path is inserted, and the world-level alias
666+
// `pub type R = ...;` lives two modules up. Importing it here
667+
// makes the bare identifier resolve regardless of how deeply
668+
// nested the payload type is.
669+
let mut type_imports = BTreeSet::new();
670+
if let Some(ty) = payload_type {
671+
collect_named_type_ids(self.resolve, ty, &mut type_imports);
672+
}
673+
let type_imports: String = type_imports
674+
.into_iter()
675+
.map(|id| {
676+
let path = self.type_path(id, /*owned=*/ true);
677+
// `type_path` emits the full `super::super::...::Name`
678+
// when the type is owned by an interface, but falls
679+
// back to a bare name when the owner is a world (the
680+
// `use interface.{T}` alias case). Bare names need the
681+
// `super::super::` prefix to reach the world-level
682+
// `pub type T = ...;` alias at macro root.
683+
let path = if path.starts_with("super::") {
684+
path
685+
} else {
686+
format!("super::super::{path}")
687+
};
688+
format!(" use {path};\n")
689+
})
690+
.collect();
691+
658692
let code = format!(
659693
r#"
660694
#[doc(hidden)]
661-
#[allow(unused_unsafe)]
695+
#[allow(unused_unsafe, unused_imports)]
662696
pub mod vtable{ordinal} {{
663-
697+
{type_imports}
664698
#[cfg(not(target_arch = "wasm32"))]
665699
unsafe extern "C" fn cancel_write(_: u32) -> u32 {{ unreachable!() }}
666700
#[cfg(not(target_arch = "wasm32"))]
@@ -3157,3 +3191,96 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator<
31573191
self.interface.push_str(&format!("; {size}]"));
31583192
}
31593193
}
3194+
3195+
/// Walk `ty`'s type tree and insert every named type id into `out`.
3196+
///
3197+
/// A type id is "named" if `resolve.types[id].name` is set, i.e. it is
3198+
/// a user-defined type (record, variant, enum, flags, resource, type
3199+
/// alias) as opposed to an anonymous structural one (`result<_, _>`,
3200+
/// `tuple<_, _>`, `list<_>`, etc.). We still recurse through the
3201+
/// anonymous composites to collect their inner named types — that's
3202+
/// the whole reason this exists: `future<result<R, _>>` hides `R` one
3203+
/// level below an anonymous `result` that the emitter never flags.
3204+
///
3205+
/// Used by stream/future payload emission to pre-compute `use`
3206+
/// statements for every named type the emitted `vtable{ordinal}`
3207+
/// module will reference, so bare identifiers inside the `fn lift` /
3208+
/// `fn lower` / `impl StreamPayload` bodies resolve even when the
3209+
/// type lives at macro-root via a world-level `use` alias.
3210+
fn collect_named_type_ids(resolve: &Resolve, ty: &Type, out: &mut BTreeSet<TypeId>) {
3211+
let Type::Id(id) = ty else { return };
3212+
collect_named_type_ids_from_id(resolve, *id, out);
3213+
}
3214+
3215+
fn collect_named_type_ids_from_id(resolve: &Resolve, id: TypeId, out: &mut BTreeSet<TypeId>) {
3216+
// Avoid cycles: a named type we've already queued doesn't need a
3217+
// second walk through its structure.
3218+
if out.contains(&id) {
3219+
return;
3220+
}
3221+
let ty = &resolve.types[id];
3222+
let named = ty.name.is_some();
3223+
if named {
3224+
out.insert(id);
3225+
// A named alias (e.g. `use t.{r};` or `type x = r;`) is what
3226+
// the emitter references verbatim — its target is a separate
3227+
// type def reachable by its own name. Walking through the
3228+
// alias would import both the alias and its target under the
3229+
// same Rust identifier and trip E0252. Stop at the alias.
3230+
if matches!(ty.kind, TypeDefKind::Type(_)) {
3231+
return;
3232+
}
3233+
}
3234+
match &ty.kind {
3235+
TypeDefKind::Record(r) => {
3236+
for field in &r.fields {
3237+
collect_named_type_ids(resolve, &field.ty, out);
3238+
}
3239+
}
3240+
TypeDefKind::Variant(v) => {
3241+
for case in &v.cases {
3242+
if let Some(ty) = &case.ty {
3243+
collect_named_type_ids(resolve, ty, out);
3244+
}
3245+
}
3246+
}
3247+
TypeDefKind::Tuple(t) => {
3248+
for ty in &t.types {
3249+
collect_named_type_ids(resolve, ty, out);
3250+
}
3251+
}
3252+
TypeDefKind::Option(t)
3253+
| TypeDefKind::List(t)
3254+
| TypeDefKind::FixedLengthList(t, _)
3255+
| TypeDefKind::Type(t) => {
3256+
collect_named_type_ids(resolve, t, out);
3257+
}
3258+
TypeDefKind::Result(r) => {
3259+
if let Some(ty) = &r.ok {
3260+
collect_named_type_ids(resolve, ty, out);
3261+
}
3262+
if let Some(ty) = &r.err {
3263+
collect_named_type_ids(resolve, ty, out);
3264+
}
3265+
}
3266+
TypeDefKind::Map(k, v) => {
3267+
collect_named_type_ids(resolve, k, out);
3268+
collect_named_type_ids(resolve, v, out);
3269+
}
3270+
TypeDefKind::Future(inner) | TypeDefKind::Stream(inner) => {
3271+
if let Some(ty) = inner {
3272+
collect_named_type_ids(resolve, ty, out);
3273+
}
3274+
}
3275+
TypeDefKind::Handle(h) => {
3276+
let resource_id = match h {
3277+
Handle::Own(id) | Handle::Borrow(id) => *id,
3278+
};
3279+
collect_named_type_ids_from_id(resolve, resource_id, out);
3280+
}
3281+
TypeDefKind::Enum(_)
3282+
| TypeDefKind::Flags(_)
3283+
| TypeDefKind::Resource
3284+
| TypeDefKind::Unknown => {}
3285+
}
3286+
}

0 commit comments

Comments
 (0)