From 674be0ec21f9dcf1fe20c6d76825f74a57aaff86 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 6 Feb 2026 13:31:15 -0500 Subject: [PATCH] Resolve Self::AssocType as struct constructor when it's an associated type When path resolution fails for a value like Self::Associated or Self::Err(42), check whether we're inside an impl and the name is an associated type that resolves to a struct (unit or tuple). If so, treat the path as that struct's constructor. - Fixes #71054 (unit struct: Self::Associated) - Fixes #120871 (tuple struct: Self::Err(42)) Add try_associated_type_constructor() in FnCtxt and use it from resolve_ty_and_res_fully_qualified_call on NoMatch. Extend to support both CtorKind::Const and CtorKind::Fn. Tests: issue-71054.rs (unit + brace form), issue-120871.rs (FromStr tuple ctor). Convert associated-type-call.rs and invalid-ctor.rs to check-pass and remove their .stderr/.fixed. --- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 51 ++++++++++++ .../associated-type-call.fixed | 22 ------ .../associated-types/associated-type-call.rs | 6 +- .../associated-type-call.stderr | 15 ---- tests/ui/associated-types/invalid-ctor.fixed | 22 ------ tests/ui/associated-types/invalid-ctor.rs | 7 +- tests/ui/associated-types/invalid-ctor.stderr | 15 ---- .../ui/issues/self-assoc-type-struct-ctor.rs | 78 +++++++++++++++++++ 8 files changed, 137 insertions(+), 79 deletions(-) delete mode 100644 tests/ui/associated-types/associated-type-call.fixed delete mode 100644 tests/ui/associated-types/associated-type-call.stderr delete mode 100644 tests/ui/associated-types/invalid-ctor.fixed delete mode 100644 tests/ui/associated-types/invalid-ctor.stderr create mode 100644 tests/ui/issues/self-assoc-type-struct-ctor.rs diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 67007523a0671..1c6bc15eeb3ee 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -736,6 +736,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let result = self .resolve_fully_qualified_call(span, item_name, ty.normalized, qself.span, hir_id) .or_else(|error| { + // Fix for #71054: when `Self::Associated` (unit struct) fails value lookup, + // try resolving the path as the associated type's constructor. + if let method::MethodError::NoMatch(_) = error + && let Some((kind, def_id)) = + self.try_associated_type_constructor(item_name, ty.normalized) + { + return Ok((kind, def_id)); + } + let guar = self .dcx() .span_delayed_bug(span, "method resolution should've emitted an error"); @@ -766,6 +775,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) } + /// When path resolution fails for a value (e.g. `Self::Associated` or `Self::Err(42)`), + /// check if we're inside an impl and the name refers to an associated type that is a + /// struct (unit or tuple). If so, return its constructor so the path can be used as a + /// struct expression. (Fix for #71054 and #120871) + fn try_associated_type_constructor( + &self, + item_name: rustc_span::Ident, + self_ty: Ty<'tcx>, + ) -> Option<(DefKind, DefId)> { + let tcx = self.tcx; + let body_owner = tcx.local_def_id_to_hir_id(self.body_id); + let parent_node = tcx.hir_node(tcx.parent_hir_id(body_owner)); + let Node::Item(hir::Item { kind: hir::ItemKind::Impl(..), owner_id, .. }) = parent_node + else { + return None; + }; + let impl_def_id = owner_id.def_id; + let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity(); + let self_types_match = impl_self_ty == self_ty + || matches!( + (impl_self_ty.ty_adt_def(), self_ty.ty_adt_def()), + (Some(a), Some(b)) if a.did() == b.did() + ); + if !self_types_match { + return None; + } + let impl_did = impl_def_id.to_def_id(); + let candidate = tcx.associated_items(impl_def_id).find_by_ident_and_kind( + tcx, + item_name, + ty::AssocTag::Type, + impl_did, + )?; + let adt_def = tcx.type_of(candidate.def_id).skip_binder().ty_adt_def()?; + if !adt_def.is_struct() { + return None; + } + let variant = adt_def.non_enum_variant(); + let ctor_kind = variant.ctor_kind()?; + Some((DefKind::Ctor(CtorOf::Struct, ctor_kind), variant.ctor_def_id()?)) + } + /// Given a `HirId`, return the `HirId` of the enclosing function and its `FnDecl`. pub(crate) fn get_fn_decl( &self, diff --git a/tests/ui/associated-types/associated-type-call.fixed b/tests/ui/associated-types/associated-type-call.fixed deleted file mode 100644 index d450b3b82c96f..0000000000000 --- a/tests/ui/associated-types/associated-type-call.fixed +++ /dev/null @@ -1,22 +0,0 @@ -// issue: -// -//@ run-rustfix -#![allow(unused)] -struct T(); - -trait Trait { - type Assoc; - - fn f(); -} - -impl Trait for () { - type Assoc = T; - - fn f() { - T(); - //~^ ERROR no associated item named `Assoc` found for unit type `()` in the current scope - } -} - -fn main() {} diff --git a/tests/ui/associated-types/associated-type-call.rs b/tests/ui/associated-types/associated-type-call.rs index ffe540c329eb9..38d4913a2d3e5 100644 --- a/tests/ui/associated-types/associated-type-call.rs +++ b/tests/ui/associated-types/associated-type-call.rs @@ -1,7 +1,10 @@ // issue: +// Also related to #71054 / #120871: `Self::Assoc()` now resolves when Assoc is an +// associated type set to a unit struct. // -//@ run-rustfix +//@ check-pass #![allow(unused)] + struct T(); trait Trait { @@ -15,7 +18,6 @@ impl Trait for () { fn f() { ::Assoc(); - //~^ ERROR no associated item named `Assoc` found for unit type `()` in the current scope } } diff --git a/tests/ui/associated-types/associated-type-call.stderr b/tests/ui/associated-types/associated-type-call.stderr deleted file mode 100644 index eaef775e30406..0000000000000 --- a/tests/ui/associated-types/associated-type-call.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0599]: no associated item named `Assoc` found for unit type `()` in the current scope - --> $DIR/associated-type-call.rs:17:17 - | -LL | ::Assoc(); - | ^^^^^ associated item not found in `()` - | -help: to construct a value of type `T`, use the explicit path - | -LL - ::Assoc(); -LL + T(); - | - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0599`. diff --git a/tests/ui/associated-types/invalid-ctor.fixed b/tests/ui/associated-types/invalid-ctor.fixed deleted file mode 100644 index eba3820de0c1c..0000000000000 --- a/tests/ui/associated-types/invalid-ctor.fixed +++ /dev/null @@ -1,22 +0,0 @@ -//@ run-rustfix - -#![allow(unused)] - -struct Constructor(i32); - -trait Trait { - type Out; - - fn mk() -> Self::Out; -} - -impl Trait for () { - type Out = Constructor; - - fn mk() -> Self::Out { - Constructor(1) - //~^ ERROR no associated item named `Out` found for unit type `()` - } -} - -fn main() {} diff --git a/tests/ui/associated-types/invalid-ctor.rs b/tests/ui/associated-types/invalid-ctor.rs index 73335c065c2ae..4681637dde2aa 100644 --- a/tests/ui/associated-types/invalid-ctor.rs +++ b/tests/ui/associated-types/invalid-ctor.rs @@ -1,5 +1,7 @@ -//@ run-rustfix - +// Related to #71054 / #120871: `Self::Out(1)` now resolves when Out is an associated +// type set to a tuple struct. +// +//@ check-pass #![allow(unused)] struct Constructor(i32); @@ -15,7 +17,6 @@ impl Trait for () { fn mk() -> Self::Out { Self::Out(1) - //~^ ERROR no associated item named `Out` found for unit type `()` } } diff --git a/tests/ui/associated-types/invalid-ctor.stderr b/tests/ui/associated-types/invalid-ctor.stderr deleted file mode 100644 index 0b3bf316f60fa..0000000000000 --- a/tests/ui/associated-types/invalid-ctor.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0599]: no associated item named `Out` found for unit type `()` in the current scope - --> $DIR/invalid-ctor.rs:17:15 - | -LL | Self::Out(1) - | ^^^ associated item not found in `()` - | -help: to construct a value of type `Constructor`, use the explicit path - | -LL - Self::Out(1) -LL + Constructor(1) - | - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0599`. diff --git a/tests/ui/issues/self-assoc-type-struct-ctor.rs b/tests/ui/issues/self-assoc-type-struct-ctor.rs new file mode 100644 index 0000000000000..0943812314cf6 --- /dev/null +++ b/tests/ui/issues/self-assoc-type-struct-ctor.rs @@ -0,0 +1,78 @@ +// Self::Assoc and Self::Assoc(...) when Assoc is an associated type set to a struct. +// Unit struct: https://github.com/rust-lang/rust/issues/71054 +// Tuple struct: https://github.com/rust-lang/rust/issues/120871 +// +//@ run-pass +#![allow(unused)] + +// --- Unit struct constructor (issue #71054) --- + +trait Trait { + type Associated; + + fn instance() -> Self::Associated; +} + +struct Associated; +struct Struct; + +impl Trait for Struct { + type Associated = Associated; + + fn instance() -> Self::Associated { + Self::Associated + } +} + +trait Trait2 { + type Assoc; + + fn get() -> Self::Assoc; +} + +struct Unit; +struct S2; + +impl Trait2 for S2 { + type Assoc = Unit; + + fn get() -> Self::Assoc { + Self::Assoc {} + } +} + +fn _use_outer_scope() -> Associated { + Associated +} + +// --- Tuple struct constructor (issue #120871) --- + +struct MyValue; +struct MyError(u32); + +impl MyError { + fn new(n: u32) -> Self { + Self(n) + } +} + +impl std::str::FromStr for MyValue { + type Err = MyError; + + fn from_str(s: &str) -> Result { + match s { + "tuple" => Err(Self::Err(42)), + "brace" => Err(Self::Err { 0: 42 }), + "method" => Err(Self::Err::new(42)), + "direct" => Err(MyError(42)), + _ => Err(Self::Err(0)), + } + } +} + +fn main() { + assert_eq!("tuple".parse::().err().unwrap().0, 42); + assert_eq!("brace".parse::().err().unwrap().0, 42); + assert_eq!("method".parse::().err().unwrap().0, 42); + assert_eq!("direct".parse::().err().unwrap().0, 42); +}