Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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,
Expand Down
22 changes: 0 additions & 22 deletions tests/ui/associated-types/associated-type-call.fixed

This file was deleted.

6 changes: 4 additions & 2 deletions tests/ui/associated-types/associated-type-call.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// issue: <https://github.com/rust-lang/rust/issues/142473>
// 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 {
Expand All @@ -15,7 +18,6 @@ impl Trait for () {

fn f() {
<Self>::Assoc();
//~^ ERROR no associated item named `Assoc` found for unit type `()` in the current scope
}
}

Expand Down
15 changes: 0 additions & 15 deletions tests/ui/associated-types/associated-type-call.stderr

This file was deleted.

22 changes: 0 additions & 22 deletions tests/ui/associated-types/invalid-ctor.fixed

This file was deleted.

7 changes: 4 additions & 3 deletions tests/ui/associated-types/invalid-ctor.rs
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -15,7 +17,6 @@ impl Trait for () {

fn mk() -> Self::Out {
Self::Out(1)
//~^ ERROR no associated item named `Out` found for unit type `()`
}
}

Expand Down
15 changes: 0 additions & 15 deletions tests/ui/associated-types/invalid-ctor.stderr

This file was deleted.

78 changes: 78 additions & 0 deletions tests/ui/issues/self-assoc-type-struct-ctor.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Self::Err> {
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::<MyValue>().err().unwrap().0, 42);
assert_eq!("brace".parse::<MyValue>().err().unwrap().0, 42);
assert_eq!("method".parse::<MyValue>().err().unwrap().0, 42);
assert_eq!("direct".parse::<MyValue>().err().unwrap().0, 42);
}
Loading