From 2c31b0deb13d9d49ea4b651b81765b9acb446d5a Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 2 Jan 2026 15:59:03 +0900 Subject: [PATCH 01/30] check if redundant args spans belong to the same context --- .../errors/wrong_number_of_generic_args.rs | 3 ++ .../generics/wrong-number-of-args-in-macro.rs | 15 +++++++ .../wrong-number-of-args-in-macro.stderr | 44 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 tests/ui/generics/wrong-number-of-args-in-macro.rs create mode 100644 tests/ui/generics/wrong-number-of-args-in-macro.stderr diff --git a/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs b/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs index d835a7bbb8d29..2b7854769b426 100644 --- a/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs +++ b/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs @@ -988,6 +988,9 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { gen_arg_spans[self.num_expected_type_or_const_args() - 1] }; let span_hi_redundant_type_or_const_args = gen_arg_spans[gen_arg_spans.len() - 1]; + if !span_lo_redundant_type_or_const_args.eq_ctxt(span_hi_redundant_type_or_const_args) { + return; + } let span_redundant_type_or_const_args = span_lo_redundant_type_or_const_args .shrink_to_hi() .to(span_hi_redundant_type_or_const_args); diff --git a/tests/ui/generics/wrong-number-of-args-in-macro.rs b/tests/ui/generics/wrong-number-of-args-in-macro.rs new file mode 100644 index 0000000000000..953e05434d25a --- /dev/null +++ b/tests/ui/generics/wrong-number-of-args-in-macro.rs @@ -0,0 +1,15 @@ +// Regression test for #149559 + +struct Foo; //~ ERROR type parameter `T` is never used + +macro_rules! foo_ty { + ($a:ty, $b:ty) => { + Foo + //~^ ERROR cannot find type `a` in this scope + //~| ERROR struct takes 1 generic argument but 2 generic arguments were supplied + }; +} + +fn foo<'a, 'b>() -> foo_ty!(&'b (), &'b ()) {} + +fn main() {} diff --git a/tests/ui/generics/wrong-number-of-args-in-macro.stderr b/tests/ui/generics/wrong-number-of-args-in-macro.stderr new file mode 100644 index 0000000000000..62a5eb9414ed5 --- /dev/null +++ b/tests/ui/generics/wrong-number-of-args-in-macro.stderr @@ -0,0 +1,44 @@ +error[E0425]: cannot find type `a` in this scope + --> $DIR/wrong-number-of-args-in-macro.rs:7:13 + | +LL | Foo + | ^ not found in this scope +... +LL | fn foo<'a, 'b>() -> foo_ty!(&'b (), &'b ()) {} + | ----------------------- in this macro invocation + | + = note: this error originates in the macro `foo_ty` (in Nightly builds, run with -Z macro-backtrace for more info) +help: you might be missing a type parameter + | +LL | fn foo<'a, 'b, a>() -> foo_ty!(&'b (), &'b ()) {} + | +++ + +error[E0107]: struct takes 1 generic argument but 2 generic arguments were supplied + --> $DIR/wrong-number-of-args-in-macro.rs:7:9 + | +LL | Foo + | ^^^ expected 1 generic argument +... +LL | fn foo<'a, 'b>() -> foo_ty!(&'b (), &'b ()) {} + | ----------------------- in this macro invocation + | +note: struct defined here, with 1 generic parameter: `T` + --> $DIR/wrong-number-of-args-in-macro.rs:3:8 + | +LL | struct Foo; + | ^^^ - + = note: this error originates in the macro `foo_ty` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0392]: type parameter `T` is never used + --> $DIR/wrong-number-of-args-in-macro.rs:3:12 + | +LL | struct Foo; + | ^ unused type parameter + | + = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData` + = help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0107, E0392, E0425. +For more information about an error, try `rustc --explain E0107`. From 31531b3665bba1a98740de4b9264901302b7255d Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 2 Jan 2026 17:26:41 +0800 Subject: [PATCH 02/30] Add regression tests for keyword-in-identifier-position recovery ICE ... in macro invocations. Issue: . --- .../kw-in-const-item-pos-recovery-149692.rs | 11 +++++++ ...w-in-const-item-pos-recovery-149692.stderr | 11 +++++++ .../macro/kw-in-item-pos-recovery-149692.rs | 19 ++++++++++++ .../kw-in-item-pos-recovery-149692.stderr | 29 +++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 tests/ui/parser/macro/kw-in-const-item-pos-recovery-149692.rs create mode 100644 tests/ui/parser/macro/kw-in-const-item-pos-recovery-149692.stderr create mode 100644 tests/ui/parser/macro/kw-in-item-pos-recovery-149692.rs create mode 100644 tests/ui/parser/macro/kw-in-item-pos-recovery-149692.stderr diff --git a/tests/ui/parser/macro/kw-in-const-item-pos-recovery-149692.rs b/tests/ui/parser/macro/kw-in-const-item-pos-recovery-149692.rs new file mode 100644 index 0000000000000..58bb62bc4bf86 --- /dev/null +++ b/tests/ui/parser/macro/kw-in-const-item-pos-recovery-149692.rs @@ -0,0 +1,11 @@ +//! More test coverage for ; this test is +//! specifically for `const` items. + +macro_rules! m { + (const $id:item()) => {} +} + +m!(const Self()); +//~^ ERROR expected one of `!` or `::`, found `(` + +fn main() {} diff --git a/tests/ui/parser/macro/kw-in-const-item-pos-recovery-149692.stderr b/tests/ui/parser/macro/kw-in-const-item-pos-recovery-149692.stderr new file mode 100644 index 0000000000000..f9b73109dbb49 --- /dev/null +++ b/tests/ui/parser/macro/kw-in-const-item-pos-recovery-149692.stderr @@ -0,0 +1,11 @@ +error: expected one of `!` or `::`, found `(` + --> $DIR/kw-in-const-item-pos-recovery-149692.rs:8:14 + | +LL | (const $id:item()) => {} + | -------- while parsing argument for this `item` macro fragment +... +LL | m!(const Self()); + | ^ expected one of `!` or `::` + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/macro/kw-in-item-pos-recovery-149692.rs b/tests/ui/parser/macro/kw-in-item-pos-recovery-149692.rs new file mode 100644 index 0000000000000..223864e332966 --- /dev/null +++ b/tests/ui/parser/macro/kw-in-item-pos-recovery-149692.rs @@ -0,0 +1,19 @@ +//! Regression test for a diagnostic ICE where we tried to recover a keyword as the identifier when +//! we are already trying to recover a missing keyword before item. +//! +//! See . + +macro_rules! m { + ($id:item()) => {} +} + +m!(Self()); +//~^ ERROR expected one of `!` or `::`, found `(` + +m!(Self{}); +//~^ ERROR expected one of `!` or `::`, found `{` + +m!(crate()); +//~^ ERROR expected one of `!` or `::`, found `(` + +fn main() {} diff --git a/tests/ui/parser/macro/kw-in-item-pos-recovery-149692.stderr b/tests/ui/parser/macro/kw-in-item-pos-recovery-149692.stderr new file mode 100644 index 0000000000000..a65214b0d1f93 --- /dev/null +++ b/tests/ui/parser/macro/kw-in-item-pos-recovery-149692.stderr @@ -0,0 +1,29 @@ +error: expected one of `!` or `::`, found `(` + --> $DIR/kw-in-item-pos-recovery-149692.rs:10:8 + | +LL | ($id:item()) => {} + | -------- while parsing argument for this `item` macro fragment +... +LL | m!(Self()); + | ^ expected one of `!` or `::` + +error: expected one of `!` or `::`, found `{` + --> $DIR/kw-in-item-pos-recovery-149692.rs:13:8 + | +LL | ($id:item()) => {} + | -------- while parsing argument for this `item` macro fragment +... +LL | m!(Self{}); + | ^ expected one of `!` or `::` + +error: expected one of `!` or `::`, found `(` + --> $DIR/kw-in-item-pos-recovery-149692.rs:16:9 + | +LL | ($id:item()) => {} + | -------- while parsing argument for this `item` macro fragment +... +LL | m!(crate()); + | ^ expected one of `!` or `::` + +error: aborting due to 3 previous errors + From 79c47278f1ec8198cb8c0ed53eda46fc43b16a0d Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Fri, 2 Jan 2026 17:27:35 +0800 Subject: [PATCH 03/30] Don't try to recover keyword as non-keyword identifier There's no sensible recovery scheme here, giving up the recovery is the right thing to do. --- compiler/rustc_parse/src/parser/item.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 26f60c96aed6b..ea713b3cdade2 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -408,6 +408,7 @@ impl<'a> Parser<'a> { let insert_span = ident_span.shrink_to_lo(); let ident = if self.token.is_ident() + && self.token.is_non_reserved_ident() && (!is_const || self.look_ahead(1, |t| *t == token::OpenParen)) && self.look_ahead(1, |t| { matches!(t.kind, token::Lt | token::OpenBrace | token::OpenParen) From 308c6076ec614ce401f77e38670391fb0b5c7ff6 Mon Sep 17 00:00:00 2001 From: dianne Date: Thu, 8 Jan 2026 08:44:04 -0800 Subject: [PATCH 04/30] remove borrowck handling for inline const patterns --- compiler/rustc_borrowck/src/type_check/mod.rs | 67 +++---------------- 1 file changed, 9 insertions(+), 58 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index e5aad4cdbd06c..d2464c7e99ee5 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -19,6 +19,7 @@ use rustc_infer::infer::{ BoundRegionConversionTime, InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin, }; use rustc_infer::traits::PredicateObligations; +use rustc_middle::bug; use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::traits::query::NoSolution; @@ -28,7 +29,6 @@ use rustc_middle::ty::{ self, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, CoroutineArgsExt, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt, UserArgs, UserTypeAnnotationIndex, fold_regions, }; -use rustc_middle::{bug, span_bug}; use rustc_mir_dataflow::move_paths::MoveData; use rustc_mir_dataflow::points::DenseLocationMap; use rustc_span::def_id::CRATE_DEF_ID; @@ -387,18 +387,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { #[instrument(skip(self), level = "debug")] fn check_user_type_annotations(&mut self) { debug!(?self.user_type_annotations); - let tcx = self.tcx(); for user_annotation in self.user_type_annotations { let CanonicalUserTypeAnnotation { span, ref user_ty, inferred_ty } = *user_annotation; let annotation = self.instantiate_canonical(span, user_ty); - if let ty::UserTypeKind::TypeOf(def, args) = annotation.kind - && let DefKind::InlineConst = tcx.def_kind(def) - { - assert!(annotation.bounds.is_empty()); - self.check_inline_const(inferred_ty, def.expect_local(), args, span); - } else { - self.ascribe_user_type(inferred_ty, annotation, span); - } + self.ascribe_user_type(inferred_ty, annotation, span); } } @@ -560,36 +552,6 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { self.constraints.liveness_constraints.add_location(region, location); } } - - fn check_inline_const( - &mut self, - inferred_ty: Ty<'tcx>, - def_id: LocalDefId, - args: UserArgs<'tcx>, - span: Span, - ) { - assert!(args.user_self_ty.is_none()); - let tcx = self.tcx(); - let const_ty = tcx.type_of(def_id).instantiate(tcx, args.args); - if let Err(terr) = - self.eq_types(const_ty, inferred_ty, Locations::All(span), ConstraintCategory::Boring) - { - span_bug!( - span, - "bad inline const pattern: ({:?} = {:?}) {:?}", - const_ty, - inferred_ty, - terr - ); - } - let args = self.infcx.resolve_vars_if_possible(args.args); - let predicates = self.prove_closure_bounds(tcx, def_id, args, Locations::All(span)); - self.normalize_and_prove_instantiated_predicates( - def_id.to_def_id(), - predicates, - Locations::All(span), - ); - } } impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { @@ -1731,12 +1693,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { let def_id = uv.def; if tcx.def_kind(def_id) == DefKind::InlineConst { let def_id = def_id.expect_local(); - let predicates = self.prove_closure_bounds( - tcx, - def_id, - uv.args, - location.to_locations(), - ); + let predicates = self.prove_closure_bounds(tcx, def_id, uv.args, location); self.normalize_and_prove_instantiated_predicates( def_id.to_def_id(), predicates, @@ -2519,15 +2476,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // clauses on the struct. AggregateKind::Closure(def_id, args) | AggregateKind::CoroutineClosure(def_id, args) - | AggregateKind::Coroutine(def_id, args) => ( - def_id, - self.prove_closure_bounds( - tcx, - def_id.expect_local(), - args, - location.to_locations(), - ), - ), + | AggregateKind::Coroutine(def_id, args) => { + (def_id, self.prove_closure_bounds(tcx, def_id.expect_local(), args, location)) + } AggregateKind::Array(_) | AggregateKind::Tuple | AggregateKind::RawPtr(..) => { (CRATE_DEF_ID.to_def_id(), ty::InstantiatedPredicates::empty()) @@ -2546,12 +2497,12 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { tcx: TyCtxt<'tcx>, def_id: LocalDefId, args: GenericArgsRef<'tcx>, - locations: Locations, + location: Location, ) -> ty::InstantiatedPredicates<'tcx> { let root_def_id = self.root_cx.root_def_id(); // We will have to handle propagated closure requirements for this closure, // but need to defer this until the nested body has been fully borrow checked. - self.deferred_closure_requirements.push((def_id, args, locations)); + self.deferred_closure_requirements.push((def_id, args, location.to_locations())); // Equate closure args to regions inherited from `root_def_id`. Fixes #98589. let typeck_root_args = ty::GenericArgs::identity_for_item(tcx, root_def_id); @@ -2575,7 +2526,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { if let Err(_) = self.eq_args( typeck_root_args, parent_args, - locations, + location.to_locations(), ConstraintCategory::BoringNoLocation, ) { span_mirbug!( From 98d3026c416a2a588bf271414a4695436cd7ef25 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Thu, 1 Jan 2026 20:19:21 -0800 Subject: [PATCH 05/30] Resolve intra-doc links to variant fields behind type aliases Previously, these failed to resolve, despite them working for struct fields or enum variants that were behind aliases. However, there is now an inconsistency where enum variant fields behind an alias resolve to the entry on the alias's page, while enum variants and struct fields resolve to the page of the backing ADT. --- .../passes/collect_intra_doc_links.rs | 35 ++++++++++--------- .../intra-doc/adt-through-alias.rs | 25 +++++++++++++ 2 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 tests/rustdoc-html/intra-doc/adt-through-alias.rs diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 3abf0fee3959a..44affba9a8c07 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -308,24 +308,27 @@ impl<'tcx> LinkCollector<'_, 'tcx> { let ty_res = self.resolve_path(path, TypeNS, item_id, module_id).ok_or_else(no_res)?; match ty_res { - Res::Def(DefKind::Enum, did) => match tcx.type_of(did).instantiate_identity().kind() { - ty::Adt(def, _) if def.is_enum() => { - if let Some(variant) = def.variants().iter().find(|v| v.name == variant_name) - && let Some(field) = - variant.fields.iter().find(|f| f.name == variant_field_name) - { - Ok((ty_res, field.did)) - } else { - Err(UnresolvedPath { - item_id, - module_id, - partial_res: Some(Res::Def(DefKind::Enum, def.did())), - unresolved: variant_field_name.to_string().into(), - }) + Res::Def(DefKind::Enum | DefKind::TyAlias, did) => { + match tcx.type_of(did).instantiate_identity().kind() { + ty::Adt(def, _) if def.is_enum() => { + if let Some(variant) = + def.variants().iter().find(|v| v.name == variant_name) + && let Some(field) = + variant.fields.iter().find(|f| f.name == variant_field_name) + { + Ok((ty_res, field.did)) + } else { + Err(UnresolvedPath { + item_id, + module_id, + partial_res: Some(Res::Def(DefKind::Enum, def.did())), + unresolved: variant_field_name.to_string().into(), + }) + } } + _ => unreachable!(), } - _ => unreachable!(), - }, + } _ => Err(UnresolvedPath { item_id, module_id, diff --git a/tests/rustdoc-html/intra-doc/adt-through-alias.rs b/tests/rustdoc-html/intra-doc/adt-through-alias.rs new file mode 100644 index 0000000000000..58e0f37edbabd --- /dev/null +++ b/tests/rustdoc-html/intra-doc/adt-through-alias.rs @@ -0,0 +1,25 @@ +#![crate_name = "foo"] + +//! [`TheStructAlias::the_field`] +//! [`TheEnumAlias::TheVariant`] +//! [`TheEnumAlias::TheVariant::the_field`] + +// FIXME: this should resolve to the alias's version +//@ has foo/index.html '//a[@href="struct.TheStruct.html#structfield.the_field"]' 'TheStructAlias::the_field' +// FIXME: this should resolve to the alias's version +//@ has foo/index.html '//a[@href="enum.TheEnum.html#variant.TheVariant"]' 'TheEnumAlias::TheVariant' +//@ has foo/index.html '//a[@href="type.TheEnumAlias.html#variant.TheVariant.field.the_field"]' 'TheEnumAlias::TheVariant::the_field' + +pub struct TheStruct { + pub the_field: i32, +} + +pub type TheStructAlias = TheStruct; + +pub enum TheEnum { + TheVariant { the_field: i32 }, +} + +pub type TheEnumAlias = TheEnum; + +fn main() {} From a90e0418c69e2f46000e918c404d4490cd5f2b4e Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Thu, 1 Jan 2026 20:29:43 -0800 Subject: [PATCH 06/30] rustdoc: Refactor `def_id_to_res` as `ty_to_res` The old name and API were confusing. In my opinion, looking up the type at the call site is clearer. --- src/librustdoc/passes/collect_intra_doc_links.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 44affba9a8c07..512f0f418702a 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -384,7 +384,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { }; match tcx.def_kind(self_id) { - DefKind::Impl { .. } => self.def_id_to_res(self_id), + DefKind::Impl { .. } => self.ty_to_res(tcx.type_of(self_id).instantiate_identity()), DefKind::Use => None, def_kind => Some(Res::Def(def_kind, self_id)), } @@ -506,12 +506,12 @@ impl<'tcx> LinkCollector<'_, 'tcx> { } } - /// Convert a DefId to a Res, where possible. + /// Convert a Ty to a Res, where possible. /// /// This is used for resolving type aliases. - fn def_id_to_res(&self, ty_id: DefId) -> Option { + fn ty_to_res(&self, ty: Ty<'tcx>) -> Option { use PrimitiveType::*; - Some(match *self.cx.tcx.type_of(ty_id).instantiate_identity().kind() { + Some(match *ty.kind() { ty::Bool => Res::Primitive(Bool), ty::Char => Res::Primitive(Char), ty::Int(ity) => Res::Primitive(ity.into()), @@ -614,7 +614,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { // Resolve the link on the type the alias points to. // FIXME: if the associated item is defined directly on the type alias, // it will show up on its documentation page, we should link there instead. - let Some(res) = self.def_id_to_res(did) else { return Vec::new() }; + let Some(res) = self.ty_to_res(tcx.type_of(did).instantiate_identity()) else { return Vec::new() }; self.resolve_associated_item(res, item_name, ns, disambiguator, module_id) } Res::Def( From 945f3f91712b2ccbfce0bde5c1d0f4ad36a69876 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Thu, 1 Jan 2026 23:46:46 -0800 Subject: [PATCH 07/30] rustdoc: Use trait as root `Res` instead of assoc item All the other parts of this function return the Res for the containing page, but for some reason, this returns the associated item itself. It doesn't seem to affect the behavior of rustdoc because e.g. the href functions use the parent if the DefId is for an assoc item. But it's clearer and simpler to be consistent. --- src/librustdoc/passes/collect_intra_doc_links.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 512f0f418702a..4233f0903b232 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -614,7 +614,9 @@ impl<'tcx> LinkCollector<'_, 'tcx> { // Resolve the link on the type the alias points to. // FIXME: if the associated item is defined directly on the type alias, // it will show up on its documentation page, we should link there instead. - let Some(res) = self.ty_to_res(tcx.type_of(did).instantiate_identity()) else { return Vec::new() }; + let Some(res) = self.ty_to_res(tcx.type_of(did).instantiate_identity()) else { + return Vec::new(); + }; self.resolve_associated_item(res, item_name, ns, disambiguator, module_id) } Res::Def( @@ -720,10 +722,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { Ident::with_dummy_span(item_name), ns, ) - .map(|item| { - let res = Res::Def(item.as_def_kind(), item.def_id); - (res, item.def_id) - }) + .map(|item| (root_res, item.def_id)) .collect::>(), _ => Vec::new(), } From f503b894e912532299fd7669844efbdebaedcfd7 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Thu, 1 Jan 2026 23:54:43 -0800 Subject: [PATCH 08/30] rustdoc: Extract helper function for resolving assoc items on ADTs --- .../passes/collect_intra_doc_links.rs | 200 ++++++++++-------- 1 file changed, 106 insertions(+), 94 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 4233f0903b232..cb3fd72bc1963 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -622,100 +622,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { Res::Def( def_kind @ (DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::ForeignTy), did, - ) => { - debug!("looking for associated item named {item_name} for item {did:?}"); - // Checks if item_name is a variant of the `SomeItem` enum - if ns == TypeNS && def_kind == DefKind::Enum { - match tcx.type_of(did).instantiate_identity().kind() { - ty::Adt(adt_def, _) => { - for variant in adt_def.variants() { - if variant.name == item_name { - return vec![(root_res, variant.def_id)]; - } - } - } - _ => unreachable!(), - } - } - - let search_for_field = || { - let (DefKind::Struct | DefKind::Union) = def_kind else { return vec![] }; - debug!("looking for fields named {item_name} for {did:?}"); - // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?) - // NOTE: it's different from variant_field because it only resolves struct fields, - // not variant fields (2 path segments, not 3). - // - // We need to handle struct (and union) fields in this code because - // syntactically their paths are identical to associated item paths: - // `module::Type::field` and `module::Type::Assoc`. - // - // On the other hand, variant fields can't be mistaken for associated - // items because they look like this: `module::Type::Variant::field`. - // - // Variants themselves don't need to be handled here, even though - // they also look like associated items (`module::Type::Variant`), - // because they are real Rust syntax (unlike the intra-doc links - // field syntax) and are handled by the compiler's resolver. - let ty::Adt(def, _) = tcx.type_of(did).instantiate_identity().kind() else { - unreachable!() - }; - def.non_enum_variant() - .fields - .iter() - .filter(|field| field.name == item_name) - .map(|field| (root_res, field.did)) - .collect::>() - }; - - if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator { - return search_for_field(); - } - - // Checks if item_name belongs to `impl SomeItem` - let mut assoc_items: Vec<_> = tcx - .inherent_impls(did) - .iter() - .flat_map(|&imp| { - filter_assoc_items_by_name_and_namespace( - tcx, - imp, - Ident::with_dummy_span(item_name), - ns, - ) - }) - .map(|item| (root_res, item.def_id)) - .collect(); - - if assoc_items.is_empty() { - // Check if item_name belongs to `impl SomeTrait for SomeItem` - // FIXME(#74563): This gives precedence to `impl SomeItem`: - // Although having both would be ambiguous, use impl version for compatibility's sake. - // To handle that properly resolve() would have to support - // something like [`ambi_fn`](::ambi_fn) - assoc_items = resolve_associated_trait_item( - tcx.type_of(did).instantiate_identity(), - module_id, - item_name, - ns, - self.cx, - ) - .into_iter() - .map(|item| (root_res, item.def_id)) - .collect::>(); - } - - debug!("got associated item {assoc_items:?}"); - - if !assoc_items.is_empty() { - return assoc_items; - } - - if ns != Namespace::ValueNS { - return Vec::new(); - } - - search_for_field() - } + ) => self.resolve_assoc_on_adt(), Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace( tcx, did, @@ -727,6 +634,111 @@ impl<'tcx> LinkCollector<'_, 'tcx> { _ => Vec::new(), } } + + fn resolve_assoc_on_adt( + &mut self, + adt_def_kind: DefKind, + adt_def_id: DefId, + item_name: Symbol, + ns: Namespace, + disambiguator: Option, + module_id: DefId, + ) -> Vec<(Res, DefId)> { + let tcx = self.cx.tcx; + let root_res = Res::Def(adt_def_kind, adt_def_id); + debug!("looking for associated item named {item_name} for item {adt_def_id:?}"); + // Checks if item_name is a variant of the `SomeItem` enum + if ns == TypeNS && adt_def_kind == DefKind::Enum { + match tcx.type_of(adt_def_id).instantiate_identity().kind() { + ty::Adt(adt_def, _) => { + for variant in adt_def.variants() { + if variant.name == item_name { + return vec![(root_res, variant.def_id)]; + } + } + } + _ => unreachable!(), + } + } + + let search_for_field = || { + let (DefKind::Struct | DefKind::Union) = adt_def_kind else { return vec![] }; + debug!("looking for fields named {item_name} for {adt_def_id:?}"); + // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?) + // NOTE: it's different from variant_field because it only resolves struct fields, + // not variant fields (2 path segments, not 3). + // + // We need to handle struct (and union) fields in this code because + // syntactically their paths are identical to associated item paths: + // `module::Type::field` and `module::Type::Assoc`. + // + // On the other hand, variant fields can't be mistaken for associated + // items because they look like this: `module::Type::Variant::field`. + // + // Variants themselves don't need to be handled here, even though + // they also look like associated items (`module::Type::Variant`), + // because they are real Rust syntax (unlike the intra-doc links + // field syntax) and are handled by the compiler's resolver. + let ty::Adt(def, _) = tcx.type_of(adt_def_id).instantiate_identity().kind() else { + unreachable!() + }; + def.non_enum_variant() + .fields + .iter() + .filter(|field| field.name == item_name) + .map(|field| (root_res, field.did)) + .collect::>() + }; + + if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator { + return search_for_field(); + } + + // Checks if item_name belongs to `impl SomeItem` + let mut assoc_items: Vec<_> = tcx + .inherent_impls(adt_def_id) + .iter() + .flat_map(|&imp| { + filter_assoc_items_by_name_and_namespace( + tcx, + imp, + Ident::with_dummy_span(item_name), + ns, + ) + }) + .map(|item| (root_res, item.def_id)) + .collect(); + + if assoc_items.is_empty() { + // Check if item_name belongs to `impl SomeTrait for SomeItem` + // FIXME(#74563): This gives precedence to `impl SomeItem`: + // Although having both would be ambiguous, use impl version for compatibility's sake. + // To handle that properly resolve() would have to support + // something like [`ambi_fn`](::ambi_fn) + assoc_items = resolve_associated_trait_item( + tcx.type_of(adt_def_id).instantiate_identity(), + module_id, + item_name, + ns, + self.cx, + ) + .into_iter() + .map(|item| (root_res, item.def_id)) + .collect::>(); + } + + debug!("got associated item {assoc_items:?}"); + + if !assoc_items.is_empty() { + return assoc_items; + } + + if ns != Namespace::ValueNS { + return Vec::new(); + } + + search_for_field() + } } fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { From 2c98517faea2f0dc5b96282c2677cdb4628abd34 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Fri, 2 Jan 2026 00:36:30 -0800 Subject: [PATCH 09/30] Refactor out many free functions from intra-doc link code --- .../passes/collect_intra_doc_links.rs | 564 +++++++++--------- 1 file changed, 272 insertions(+), 292 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index cb3fd72bc1963..553d8636c85ba 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -338,58 +338,6 @@ impl<'tcx> LinkCollector<'_, 'tcx> { } } - /// Given a primitive type, try to resolve an associated item. - fn resolve_primitive_associated_item( - &self, - prim_ty: PrimitiveType, - ns: Namespace, - item_name: Symbol, - ) -> Vec<(Res, DefId)> { - let tcx = self.cx.tcx; - - prim_ty - .impls(tcx) - .flat_map(|impl_| { - filter_assoc_items_by_name_and_namespace( - tcx, - impl_, - Ident::with_dummy_span(item_name), - ns, - ) - .map(|item| (Res::Primitive(prim_ty), item.def_id)) - }) - .collect::>() - } - - fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: DefId) -> Option { - if ns != TypeNS || path_str != "Self" { - return None; - } - - let tcx = self.cx.tcx; - let self_id = match tcx.def_kind(item_id) { - def_kind @ (DefKind::AssocFn - | DefKind::AssocConst - | DefKind::AssocTy - | DefKind::Variant - | DefKind::Field) => { - let parent_def_id = tcx.parent(item_id); - if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant { - tcx.parent(parent_def_id) - } else { - parent_def_id - } - } - _ => item_id, - }; - - match tcx.def_kind(self_id) { - DefKind::Impl { .. } => self.ty_to_res(tcx.type_of(self_id).instantiate_identity()), - DefKind::Use => None, - def_kind => Some(Res::Def(def_kind, self_id)), - } - } - /// Convenience wrapper around `doc_link_resolutions`. /// /// This also handles resolving `true` and `false` as booleans. @@ -402,7 +350,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { item_id: DefId, module_id: DefId, ) -> Option { - if let res @ Some(..) = self.resolve_self_ty(path_str, ns, item_id) { + if let res @ Some(..) = resolve_self_ty(self.cx.tcx, path_str, ns, item_id) { return res; } @@ -432,13 +380,15 @@ impl<'tcx> LinkCollector<'_, 'tcx> { /// Resolves a string as a path within a particular namespace. Returns an /// optional URL fragment in the case of variants and methods. fn resolve<'path>( - &mut self, + &self, path_str: &'path str, ns: Namespace, disambiguator: Option, item_id: DefId, module_id: DefId, ) -> Result)>, UnresolvedPath<'path>> { + let tcx = self.cx.tcx; + if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) { return Ok(match res { Res::Def( @@ -484,7 +434,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { match resolve_primitive(path_root, TypeNS) .or_else(|| self.resolve_path(path_root, TypeNS, item_id, module_id)) .map(|ty_res| { - self.resolve_associated_item(ty_res, item_name, ns, disambiguator, module_id) + resolve_associated_item(tcx, ty_res, item_name, ns, disambiguator, module_id) .into_iter() .map(|(res, def_id)| (res, Some(def_id))) .collect::>() @@ -505,244 +455,280 @@ impl<'tcx> LinkCollector<'_, 'tcx> { } } } +} - /// Convert a Ty to a Res, where possible. - /// - /// This is used for resolving type aliases. - fn ty_to_res(&self, ty: Ty<'tcx>) -> Option { - use PrimitiveType::*; - Some(match *ty.kind() { - ty::Bool => Res::Primitive(Bool), - ty::Char => Res::Primitive(Char), - ty::Int(ity) => Res::Primitive(ity.into()), - ty::Uint(uty) => Res::Primitive(uty.into()), - ty::Float(fty) => Res::Primitive(fty.into()), - ty::Str => Res::Primitive(Str), - ty::Tuple(tys) if tys.is_empty() => Res::Primitive(Unit), - ty::Tuple(_) => Res::Primitive(Tuple), - ty::Pat(..) => Res::Primitive(Pat), - ty::Array(..) => Res::Primitive(Array), - ty::Slice(_) => Res::Primitive(Slice), - ty::RawPtr(_, _) => Res::Primitive(RawPointer), - ty::Ref(..) => Res::Primitive(Reference), - ty::FnDef(..) => panic!("type alias to a function definition"), - ty::FnPtr(..) => Res::Primitive(Fn), - ty::Never => Res::Primitive(Never), - ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => { - Res::from_def_id(self.cx.tcx, did) - } - ty::Alias(..) - | ty::Closure(..) - | ty::CoroutineClosure(..) - | ty::Coroutine(..) - | ty::CoroutineWitness(..) - | ty::Dynamic(..) - | ty::UnsafeBinder(_) - | ty::Param(_) - | ty::Bound(..) - | ty::Placeholder(_) - | ty::Infer(_) - | ty::Error(_) => return None, - }) - } - - /// Convert a PrimitiveType to a Ty, where possible. - /// - /// This is used for resolving trait impls for primitives - fn primitive_type_to_ty(&mut self, prim: PrimitiveType) -> Option> { - use PrimitiveType::*; - let tcx = self.cx.tcx; +fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { + assoc_item.map_or(base, |def_id| Res::from_def_id(tcx, def_id)) +} - // FIXME: Only simple types are supported here, see if we can support - // other types such as Tuple, Array, Slice, etc. - // See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455 - Some(match prim { - Bool => tcx.types.bool, - Str => tcx.types.str_, - Char => tcx.types.char, - Never => tcx.types.never, - I8 => tcx.types.i8, - I16 => tcx.types.i16, - I32 => tcx.types.i32, - I64 => tcx.types.i64, - I128 => tcx.types.i128, - Isize => tcx.types.isize, - F16 => tcx.types.f16, - F32 => tcx.types.f32, - F64 => tcx.types.f64, - F128 => tcx.types.f128, - U8 => tcx.types.u8, - U16 => tcx.types.u16, - U32 => tcx.types.u32, - U64 => tcx.types.u64, - U128 => tcx.types.u128, - Usize => tcx.types.usize, - _ => return None, +/// Given a primitive type, try to resolve an associated item. +fn resolve_primitive_associated_item<'tcx>( + tcx: TyCtxt<'tcx>, + prim_ty: PrimitiveType, + ns: Namespace, + item_ident: Ident, +) -> Vec<(Res, DefId)> { + prim_ty + .impls(tcx) + .flat_map(|impl_| { + filter_assoc_items_by_name_and_namespace(tcx, impl_, item_ident, ns) + .map(|item| (Res::Primitive(prim_ty), item.def_id)) }) - } + .collect::>() +} - /// Resolve an associated item, returning its containing page's `Res` - /// and the fragment targeting the associated item on its page. - fn resolve_associated_item( - &mut self, - root_res: Res, - item_name: Symbol, - ns: Namespace, - disambiguator: Option, - module_id: DefId, - ) -> Vec<(Res, DefId)> { - let tcx = self.cx.tcx; +fn resolve_self_ty<'tcx>( + tcx: TyCtxt<'tcx>, + path_str: &str, + ns: Namespace, + item_id: DefId, +) -> Option { + if ns != TypeNS || path_str != "Self" { + return None; + } - match root_res { - Res::Primitive(prim) => { - let items = self.resolve_primitive_associated_item(prim, ns, item_name); - if !items.is_empty() { - items - // Inherent associated items take precedence over items that come from trait impls. - } else { - self.primitive_type_to_ty(prim) - .map(|ty| { - resolve_associated_trait_item(ty, module_id, item_name, ns, self.cx) - .iter() - .map(|item| (root_res, item.def_id)) - .collect::>() - }) - .unwrap_or_default() - } - } - Res::Def(DefKind::TyAlias, did) => { - // Resolve the link on the type the alias points to. - // FIXME: if the associated item is defined directly on the type alias, - // it will show up on its documentation page, we should link there instead. - let Some(res) = self.ty_to_res(tcx.type_of(did).instantiate_identity()) else { - return Vec::new(); - }; - self.resolve_associated_item(res, item_name, ns, disambiguator, module_id) + let self_id = match tcx.def_kind(item_id) { + def_kind @ (DefKind::AssocFn + | DefKind::AssocConst + | DefKind::AssocTy + | DefKind::Variant + | DefKind::Field) => { + let parent_def_id = tcx.parent(item_id); + if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant { + tcx.parent(parent_def_id) + } else { + parent_def_id } - Res::Def( - def_kind @ (DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::ForeignTy), - did, - ) => self.resolve_assoc_on_adt(), - Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace( - tcx, - did, - Ident::with_dummy_span(item_name), - ns, - ) - .map(|item| (root_res, item.def_id)) - .collect::>(), - _ => Vec::new(), } + _ => item_id, + }; + + match tcx.def_kind(self_id) { + DefKind::Impl { .. } => ty_to_res(tcx, tcx.type_of(self_id).instantiate_identity()), + DefKind::Use => None, + def_kind => Some(Res::Def(def_kind, self_id)), } +} - fn resolve_assoc_on_adt( - &mut self, - adt_def_kind: DefKind, - adt_def_id: DefId, - item_name: Symbol, - ns: Namespace, - disambiguator: Option, - module_id: DefId, - ) -> Vec<(Res, DefId)> { - let tcx = self.cx.tcx; - let root_res = Res::Def(adt_def_kind, adt_def_id); - debug!("looking for associated item named {item_name} for item {adt_def_id:?}"); - // Checks if item_name is a variant of the `SomeItem` enum - if ns == TypeNS && adt_def_kind == DefKind::Enum { - match tcx.type_of(adt_def_id).instantiate_identity().kind() { - ty::Adt(adt_def, _) => { - for variant in adt_def.variants() { - if variant.name == item_name { - return vec![(root_res, variant.def_id)]; - } - } - } - _ => unreachable!(), - } +/// Convert a Ty to a Res, where possible. +/// +/// This is used for resolving type aliases. +fn ty_to_res<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option { + use PrimitiveType::*; + Some(match *ty.kind() { + ty::Bool => Res::Primitive(Bool), + ty::Char => Res::Primitive(Char), + ty::Int(ity) => Res::Primitive(ity.into()), + ty::Uint(uty) => Res::Primitive(uty.into()), + ty::Float(fty) => Res::Primitive(fty.into()), + ty::Str => Res::Primitive(Str), + ty::Tuple(tys) if tys.is_empty() => Res::Primitive(Unit), + ty::Tuple(_) => Res::Primitive(Tuple), + ty::Pat(..) => Res::Primitive(Pat), + ty::Array(..) => Res::Primitive(Array), + ty::Slice(_) => Res::Primitive(Slice), + ty::RawPtr(_, _) => Res::Primitive(RawPointer), + ty::Ref(..) => Res::Primitive(Reference), + ty::FnDef(..) => panic!("type alias to a function definition"), + ty::FnPtr(..) => Res::Primitive(Fn), + ty::Never => Res::Primitive(Never), + ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => { + Res::from_def_id(tcx, did) } + ty::Alias(..) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) + | ty::Dynamic(..) + | ty::UnsafeBinder(_) + | ty::Param(_) + | ty::Bound(..) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(_) => return None, + }) +} - let search_for_field = || { - let (DefKind::Struct | DefKind::Union) = adt_def_kind else { return vec![] }; - debug!("looking for fields named {item_name} for {adt_def_id:?}"); - // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?) - // NOTE: it's different from variant_field because it only resolves struct fields, - // not variant fields (2 path segments, not 3). - // - // We need to handle struct (and union) fields in this code because - // syntactically their paths are identical to associated item paths: - // `module::Type::field` and `module::Type::Assoc`. - // - // On the other hand, variant fields can't be mistaken for associated - // items because they look like this: `module::Type::Variant::field`. - // - // Variants themselves don't need to be handled here, even though - // they also look like associated items (`module::Type::Variant`), - // because they are real Rust syntax (unlike the intra-doc links - // field syntax) and are handled by the compiler's resolver. - let ty::Adt(def, _) = tcx.type_of(adt_def_id).instantiate_identity().kind() else { - unreachable!() - }; - def.non_enum_variant() - .fields - .iter() - .filter(|field| field.name == item_name) - .map(|field| (root_res, field.did)) - .collect::>() - }; +/// Convert a PrimitiveType to a Ty, where possible. +/// +/// This is used for resolving trait impls for primitives +fn primitive_type_to_ty<'tcx>(tcx: TyCtxt<'tcx>, prim: PrimitiveType) -> Option> { + use PrimitiveType::*; + + // FIXME: Only simple types are supported here, see if we can support + // other types such as Tuple, Array, Slice, etc. + // See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455 + Some(match prim { + Bool => tcx.types.bool, + Str => tcx.types.str_, + Char => tcx.types.char, + Never => tcx.types.never, + I8 => tcx.types.i8, + I16 => tcx.types.i16, + I32 => tcx.types.i32, + I64 => tcx.types.i64, + I128 => tcx.types.i128, + Isize => tcx.types.isize, + F16 => tcx.types.f16, + F32 => tcx.types.f32, + F64 => tcx.types.f64, + F128 => tcx.types.f128, + U8 => tcx.types.u8, + U16 => tcx.types.u16, + U32 => tcx.types.u32, + U64 => tcx.types.u64, + U128 => tcx.types.u128, + Usize => tcx.types.usize, + _ => return None, + }) +} - if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator { - return search_for_field(); +/// Resolve an associated item, returning its containing page's `Res` +/// and the fragment targeting the associated item on its page. +fn resolve_associated_item<'tcx>( + tcx: TyCtxt<'tcx>, + root_res: Res, + item_name: Symbol, + ns: Namespace, + disambiguator: Option, + module_id: DefId, +) -> Vec<(Res, DefId)> { + let item_ident = Ident::with_dummy_span(item_name); + + match root_res { + Res::Primitive(prim) => { + let items = resolve_primitive_associated_item(tcx, prim, ns, item_ident); + if !items.is_empty() { + items + // Inherent associated items take precedence over items that come from trait impls. + } else { + primitive_type_to_ty(tcx, prim) + .map(|ty| { + resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx) + .iter() + .map(|item| (root_res, item.def_id)) + .collect::>() + }) + .unwrap_or_default() + } + } + Res::Def(DefKind::TyAlias, did) => { + // Resolve the link on the type the alias points to. + // FIXME: if the associated item is defined directly on the type alias, + // it will show up on its documentation page, we should link there instead. + let Some(res) = ty_to_res(tcx, tcx.type_of(did).instantiate_identity()) else { + return Vec::new(); + }; + resolve_associated_item(tcx, res, item_name, ns, disambiguator, module_id) + } + Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, did) => { + resolve_assoc_on_adt(tcx, did, item_name, ns, disambiguator, module_id) } + Res::Def(DefKind::ForeignTy, did) => { + resolve_assoc_on_simple_type(tcx, did, item_ident, ns, module_id) + } + Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace( + tcx, + did, + Ident::with_dummy_span(item_name), + ns, + ) + .map(|item| (root_res, item.def_id)) + .collect::>(), + _ => Vec::new(), + } +} - // Checks if item_name belongs to `impl SomeItem` - let mut assoc_items: Vec<_> = tcx - .inherent_impls(adt_def_id) - .iter() - .flat_map(|&imp| { - filter_assoc_items_by_name_and_namespace( - tcx, - imp, - Ident::with_dummy_span(item_name), - ns, - ) - }) - .map(|item| (root_res, item.def_id)) - .collect(); +fn resolve_assoc_on_adt<'tcx>( + tcx: TyCtxt<'tcx>, + adt_def_id: DefId, + item_name: Symbol, + ns: Namespace, + disambiguator: Option, + module_id: DefId, +) -> Vec<(Res, DefId)> { + debug!("looking for associated item named {item_name} for item {adt_def_id:?}"); + let root_res = Res::from_def_id(tcx, adt_def_id); + let adt_ty = tcx.type_of(adt_def_id).instantiate_identity(); + let adt_def = adt_ty.ty_adt_def().expect("must be ADT"); + let item_ident = Ident::with_dummy_span(item_name); + // Checks if item_name is a variant of the `SomeItem` enum + if ns == TypeNS && adt_def.is_enum() { + for variant in adt_def.variants() { + if variant.name == item_name { + return vec![(root_res, variant.def_id)]; + } + } + } - if assoc_items.is_empty() { - // Check if item_name belongs to `impl SomeTrait for SomeItem` - // FIXME(#74563): This gives precedence to `impl SomeItem`: - // Although having both would be ambiguous, use impl version for compatibility's sake. - // To handle that properly resolve() would have to support - // something like [`ambi_fn`](::ambi_fn) - assoc_items = resolve_associated_trait_item( - tcx.type_of(adt_def_id).instantiate_identity(), - module_id, - item_name, - ns, - self.cx, - ) + if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator + && (adt_def.is_struct() || adt_def.is_union()) + { + return resolve_structfield(adt_def, item_name) .into_iter() - .map(|item| (root_res, item.def_id)) - .collect::>(); - } + .map(|did| (root_res, did)) + .collect(); + } - debug!("got associated item {assoc_items:?}"); + let assoc_items = resolve_assoc_on_simple_type(tcx, adt_def_id, item_ident, ns, module_id); + if !assoc_items.is_empty() { + return assoc_items; + } - if !assoc_items.is_empty() { - return assoc_items; - } + if ns == Namespace::ValueNS && (adt_def.is_struct() || adt_def.is_union()) { + return resolve_structfield(adt_def, item_name) + .into_iter() + .map(|did| (root_res, did)) + .collect(); + } - if ns != Namespace::ValueNS { - return Vec::new(); - } + vec![] +} - search_for_field() +/// "Simple" i.e. an ADT, foreign type, etc. -- not a type alias, primitive type, or other trickier type. +fn resolve_assoc_on_simple_type<'tcx>( + tcx: TyCtxt<'tcx>, + ty_def_id: DefId, + item_ident: Ident, + ns: Namespace, + module_id: DefId, +) -> Vec<(Res, DefId)> { + let root_res = Res::from_def_id(tcx, ty_def_id); + // Checks if item_name belongs to `impl SomeItem` + let inherent_assoc_items: Vec<_> = tcx + .inherent_impls(ty_def_id) + .iter() + .flat_map(|&imp| filter_assoc_items_by_name_and_namespace(tcx, imp, item_ident, ns)) + .map(|item| (root_res, item.def_id)) + .collect(); + debug!("got inherent assoc items {inherent_assoc_items:?}"); + if !inherent_assoc_items.is_empty() { + return inherent_assoc_items; } + + // Check if item_name belongs to `impl SomeTrait for SomeItem` + // FIXME(#74563): This gives precedence to `impl SomeItem`: + // Although having both would be ambiguous, use impl version for compatibility's sake. + // To handle that properly resolve() would have to support + // something like [`ambi_fn`](::ambi_fn) + let ty = tcx.type_of(ty_def_id).instantiate_identity(); + let trait_assoc_items = resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx) + .into_iter() + .map(|item| (root_res, item.def_id)) + .collect::>(); + debug!("got trait assoc items {trait_assoc_items:?}"); + trait_assoc_items } -fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { - assoc_item.map_or(base, |def_id| Res::from_def_id(tcx, def_id)) +fn resolve_structfield<'tcx>(adt_def: ty::AdtDef<'tcx>, item_name: Symbol) -> Option { + debug!("looking for fields named {item_name} for {adt_def:?}"); + adt_def + .non_enum_variant() + .fields + .iter() + .find(|field| field.name == item_name) + .map(|field| field.did) } /// Look to see if a resolved item has an associated item named `item_name`. @@ -750,12 +736,12 @@ fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { /// Given `[std::io::Error::source]`, where `source` is unresolved, this would /// find `std::error::Error::source` and return /// `::source`. -fn resolve_associated_trait_item<'a>( - ty: Ty<'a>, +fn resolve_associated_trait_item<'tcx>( + ty: Ty<'tcx>, module: DefId, - item_name: Symbol, + item_ident: Ident, ns: Namespace, - cx: &mut DocContext<'a>, + tcx: TyCtxt<'tcx>, ) -> Vec { // FIXME: this should also consider blanket impls (`impl X for T`). Unfortunately // `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the @@ -763,22 +749,17 @@ fn resolve_associated_trait_item<'a>( // Next consider explicit impls: `impl MyTrait for MyType` // Give precedence to inherent impls. - let traits = trait_impls_for(cx, ty, module); - let tcx = cx.tcx; + let traits = trait_impls_for(tcx, ty, module); debug!("considering traits {traits:?}"); let candidates = traits .iter() .flat_map(|&(impl_, trait_)| { - filter_assoc_items_by_name_and_namespace( - tcx, - trait_, - Ident::with_dummy_span(item_name), - ns, + filter_assoc_items_by_name_and_namespace(tcx, trait_, item_ident, ns).map( + move |trait_assoc| { + trait_assoc_to_impl_assoc_item(tcx, impl_, trait_assoc.def_id) + .unwrap_or(*trait_assoc) + }, ) - .map(move |trait_assoc| { - trait_assoc_to_impl_assoc_item(tcx, impl_, trait_assoc.def_id) - .unwrap_or(*trait_assoc) - }) }) .collect::>(); // FIXME(#74563): warn about ambiguity @@ -813,13 +794,12 @@ fn trait_assoc_to_impl_assoc_item<'tcx>( /// /// NOTE: this cannot be a query because more traits could be available when more crates are compiled! /// So it is not stable to serialize cross-crate. -#[instrument(level = "debug", skip(cx))] -fn trait_impls_for<'a>( - cx: &mut DocContext<'a>, - ty: Ty<'a>, +#[instrument(level = "debug", skip(tcx))] +fn trait_impls_for<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, module: DefId, ) -> FxIndexSet<(DefId, DefId)> { - let tcx = cx.tcx; let mut impls = FxIndexSet::default(); for &trait_ in tcx.doc_link_traits_in_scope(module) { @@ -1535,7 +1515,7 @@ impl LinkCollector<'_, '_> { } None => { // Try everything! - let mut candidate = |ns| { + let candidate = |ns| { self.resolve(path_str, ns, None, item_id, module_id) .map_err(ResolutionFailure::NotResolved) }; @@ -1921,7 +1901,7 @@ fn report_diagnostic( /// handled earlier. For example, if passed `Item::Crate(std)` and `path_str` /// `std::io::Error::x`, this will resolve `std::io::Error`. fn resolution_failure( - collector: &mut LinkCollector<'_, '_>, + collector: &LinkCollector<'_, '_>, diag_info: DiagnosticInfo<'_>, path_str: &str, disambiguator: Option, From 2b618ed6da5f0e55b2c00e32c767ba3dec6d7951 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Fri, 2 Jan 2026 00:57:49 -0800 Subject: [PATCH 10/30] rustdoc: Correctly resolve variant and struct fields on alias --- .../passes/collect_intra_doc_links.rs | 86 +++++++++++++------ .../intra-doc/adt-through-alias.rs | 54 +++++++++++- .../intra-doc/associated-items.rs | 9 +- 3 files changed, 113 insertions(+), 36 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 553d8636c85ba..9e86781224d38 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -462,7 +462,7 @@ fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { } /// Given a primitive type, try to resolve an associated item. -fn resolve_primitive_associated_item<'tcx>( +fn resolve_primitive_inherent_assoc_item<'tcx>( tcx: TyCtxt<'tcx>, prim_ty: PrimitiveType, ns: Namespace, @@ -597,33 +597,30 @@ fn resolve_associated_item<'tcx>( let item_ident = Ident::with_dummy_span(item_name); match root_res { - Res::Primitive(prim) => { - let items = resolve_primitive_associated_item(tcx, prim, ns, item_ident); - if !items.is_empty() { - items - // Inherent associated items take precedence over items that come from trait impls. - } else { - primitive_type_to_ty(tcx, prim) - .map(|ty| { - resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx) - .iter() - .map(|item| (root_res, item.def_id)) - .collect::>() - }) - .unwrap_or_default() - } - } - Res::Def(DefKind::TyAlias, did) => { + Res::Def(DefKind::TyAlias, alias_did) => { // Resolve the link on the type the alias points to. // FIXME: if the associated item is defined directly on the type alias, // it will show up on its documentation page, we should link there instead. - let Some(res) = ty_to_res(tcx, tcx.type_of(did).instantiate_identity()) else { - return Vec::new(); + let Some(aliased_res) = ty_to_res(tcx, tcx.type_of(alias_did).instantiate_identity()) + else { + return vec![]; }; - resolve_associated_item(tcx, res, item_name, ns, disambiguator, module_id) + let aliased_items = + resolve_associated_item(tcx, aliased_res, item_name, ns, disambiguator, module_id); + aliased_items + .into_iter() + .map(|(res, assoc_did)| { + if is_assoc_item_on_alias_page(tcx, assoc_did) { + (root_res, assoc_did) + } else { + (res, assoc_did) + } + }) + .collect() } + Res::Primitive(prim) => resolve_assoc_on_primitive(tcx, prim, ns, item_ident, module_id), Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, did) => { - resolve_assoc_on_adt(tcx, did, item_name, ns, disambiguator, module_id) + resolve_assoc_on_adt(tcx, did, item_ident, ns, disambiguator, module_id) } Res::Def(DefKind::ForeignTy, did) => { resolve_assoc_on_simple_type(tcx, did, item_ident, ns, module_id) @@ -640,23 +637,56 @@ fn resolve_associated_item<'tcx>( } } +// FIXME: make this fully complete by also including ALL inherent impls +// and trait impls BUT ONLY if on alias directly +fn is_assoc_item_on_alias_page<'tcx>(tcx: TyCtxt<'tcx>, assoc_did: DefId) -> bool { + match tcx.def_kind(assoc_did) { + // Variants and fields always have docs on the alias page. + DefKind::Variant | DefKind::Field => true, + _ => false, + } +} + +fn resolve_assoc_on_primitive<'tcx>( + tcx: TyCtxt<'tcx>, + prim: PrimitiveType, + ns: Namespace, + item_ident: Ident, + module_id: DefId, +) -> Vec<(Res, DefId)> { + let root_res = Res::Primitive(prim); + let items = resolve_primitive_inherent_assoc_item(tcx, prim, ns, item_ident); + if !items.is_empty() { + items + // Inherent associated items take precedence over items that come from trait impls. + } else { + primitive_type_to_ty(tcx, prim) + .map(|ty| { + resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx) + .iter() + .map(|item| (root_res, item.def_id)) + .collect::>() + }) + .unwrap_or_default() + } +} + fn resolve_assoc_on_adt<'tcx>( tcx: TyCtxt<'tcx>, adt_def_id: DefId, - item_name: Symbol, + item_ident: Ident, ns: Namespace, disambiguator: Option, module_id: DefId, ) -> Vec<(Res, DefId)> { - debug!("looking for associated item named {item_name} for item {adt_def_id:?}"); + debug!("looking for associated item named {item_ident} for item {adt_def_id:?}"); let root_res = Res::from_def_id(tcx, adt_def_id); let adt_ty = tcx.type_of(adt_def_id).instantiate_identity(); let adt_def = adt_ty.ty_adt_def().expect("must be ADT"); - let item_ident = Ident::with_dummy_span(item_name); // Checks if item_name is a variant of the `SomeItem` enum if ns == TypeNS && adt_def.is_enum() { for variant in adt_def.variants() { - if variant.name == item_name { + if variant.name == item_ident.name { return vec![(root_res, variant.def_id)]; } } @@ -665,7 +695,7 @@ fn resolve_assoc_on_adt<'tcx>( if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator && (adt_def.is_struct() || adt_def.is_union()) { - return resolve_structfield(adt_def, item_name) + return resolve_structfield(adt_def, item_ident.name) .into_iter() .map(|did| (root_res, did)) .collect(); @@ -677,7 +707,7 @@ fn resolve_assoc_on_adt<'tcx>( } if ns == Namespace::ValueNS && (adt_def.is_struct() || adt_def.is_union()) { - return resolve_structfield(adt_def, item_name) + return resolve_structfield(adt_def, item_ident.name) .into_iter() .map(|did| (root_res, did)) .collect(); diff --git a/tests/rustdoc-html/intra-doc/adt-through-alias.rs b/tests/rustdoc-html/intra-doc/adt-through-alias.rs index 58e0f37edbabd..6d5454bbaf200 100644 --- a/tests/rustdoc-html/intra-doc/adt-through-alias.rs +++ b/tests/rustdoc-html/intra-doc/adt-through-alias.rs @@ -3,12 +3,35 @@ //! [`TheStructAlias::the_field`] //! [`TheEnumAlias::TheVariant`] //! [`TheEnumAlias::TheVariant::the_field`] +//! [`TheUnionAlias::f1`] +//! +//! [`TheStruct::trait_`] +//! [`TheStructAlias::trait_`] +//! [`TheEnum::trait_`] +//! [`TheEnumAlias::trait_`] +//! +//! [`TheStruct::inherent`] +//! [`TheStructAlias::inherent`] +//! [`TheEnum::inherent`] +//! [`TheEnumAlias::inherent`] -// FIXME: this should resolve to the alias's version -//@ has foo/index.html '//a[@href="struct.TheStruct.html#structfield.the_field"]' 'TheStructAlias::the_field' -// FIXME: this should resolve to the alias's version -//@ has foo/index.html '//a[@href="enum.TheEnum.html#variant.TheVariant"]' 'TheEnumAlias::TheVariant' +//@ has foo/index.html '//a[@href="type.TheStructAlias.html#structfield.the_field"]' 'TheStructAlias::the_field' +//@ has foo/index.html '//a[@href="type.TheEnumAlias.html#variant.TheVariant"]' 'TheEnumAlias::TheVariant' //@ has foo/index.html '//a[@href="type.TheEnumAlias.html#variant.TheVariant.field.the_field"]' 'TheEnumAlias::TheVariant::the_field' +//@ has foo/index.html '//a[@href="type.TheUnionAlias.html#structfield.f1"]' 'TheUnionAlias::f1' + +//@ has foo/index.html '//a[@href="struct.TheStruct.html#method.trait_"]' 'TheStruct::trait_' +//@ has foo/index.html '//a[@href="struct.TheStruct.html#method.trait_"]' 'TheStructAlias::trait_' +//@ has foo/index.html '//a[@href="enum.TheEnum.html#method.trait_"]' 'TheEnum::trait_' +// FIXME: this one should resolve to alias since it's impl Trait for TheEnumAlias +//@ has foo/index.html '//a[@href="enum.TheEnum.html#method.trait_"]' 'TheEnumAlias::trait_' + +//@ has foo/index.html '//a[@href="struct.TheStruct.html#method.inherent"]' 'TheStruct::inherent' +// FIXME: this one should resolve to alias +//@ has foo/index.html '//a[@href="struct.TheStruct.html#method.inherent"]' 'TheStructAlias::inherent' +//@ has foo/index.html '//a[@href="enum.TheEnum.html#method.inherent"]' 'TheEnum::inherent' +// FIXME: this one should resolve to alias +//@ has foo/index.html '//a[@href="enum.TheEnum.html#method.inherent"]' 'TheEnumAlias::inherent' pub struct TheStruct { pub the_field: i32, @@ -22,4 +45,27 @@ pub enum TheEnum { pub type TheEnumAlias = TheEnum; +pub trait Trait { + fn trait_() {} +} + +impl Trait for TheStruct {} + +impl Trait for TheEnumAlias {} + +impl TheStruct { + pub fn inherent() {} +} + +impl TheEnumAlias { + pub fn inherent() {} +} + +pub union TheUnion { + pub f1: usize, + pub f2: isize, +} + +pub type TheUnionAlias = TheUnion; + fn main() {} diff --git a/tests/rustdoc-html/intra-doc/associated-items.rs b/tests/rustdoc-html/intra-doc/associated-items.rs index 84cfd06111df7..b3bed196aded0 100644 --- a/tests/rustdoc-html/intra-doc/associated-items.rs +++ b/tests/rustdoc-html/intra-doc/associated-items.rs @@ -13,7 +13,9 @@ pub fn foo() {} //@ has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.method"]' 'link from struct' //@ has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.clone"]' 'MyStruct::clone' //@ has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#associatedtype.Input"]' 'MyStruct::Input' -pub struct MyStruct { foo: () } +pub struct MyStruct { + foo: (), +} impl Clone for MyStruct { fn clone(&self) -> Self { @@ -31,8 +33,7 @@ impl T for MyStruct { /// [link from method][MyStruct::method] on method //@ has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.method"]' 'link from method' - fn method(i: usize) { - } + fn method(i: usize) {} } /// Ambiguity between which trait to use @@ -57,7 +58,7 @@ impl T2 for S { fn ambiguous_method() {} } -//@ has associated_items/enum.MyEnum.html '//a/@href' 'enum.MyEnum.html#variant.MyVariant' +//@ has associated_items/enum.MyEnum.html '//a/@href' 'type.MyEnumAlias.html#variant.MyVariant' /// Link to [MyEnumAlias::MyVariant] pub enum MyEnum { MyVariant, From 8a8b31a4d1e971044b7904acd711191d7f6b200c Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Mon, 12 Jan 2026 06:53:19 +0100 Subject: [PATCH 11/30] compiler: Make Externally Implementable Item (eii) macros "semiopaque" Otherwise eiis defined by std will produce large amounts of `missing stability attribute` errors. --- compiler/rustc_builtin_macros/src/eii.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_builtin_macros/src/eii.rs b/compiler/rustc_builtin_macros/src/eii.rs index 0ebd3dc826d48..b1cd35c0433d8 100644 --- a/compiler/rustc_builtin_macros/src/eii.rs +++ b/compiler/rustc_builtin_macros/src/eii.rs @@ -425,6 +425,9 @@ fn generate_attribute_macro_to_implement( // errors for eii's in std. macro_attrs.extend_from_slice(attrs_from_decl); + // Avoid "missing stability attribute" errors for eiis in std. See #146993. + macro_attrs.push(ecx.attr_name_value_str(sym::rustc_macro_transparency, sym::semiopaque, span)); + // #[builtin_macro(eii_shared_macro)] macro_attrs.push(ecx.attr_nested_word(sym::rustc_builtin_macro, sym::eii_shared_macro, span)); From 467a2d2a1a8654524be29dbadd34c89d9616d8dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Sun, 11 Jan 2026 16:09:18 +0100 Subject: [PATCH 12/30] use self instead of super --- compiler/rustc_builtin_macros/src/eii.rs | 54 ++++-------------------- 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/eii.rs b/compiler/rustc_builtin_macros/src/eii.rs index cec7599d68e9c..f72b78fe7f99d 100644 --- a/compiler/rustc_builtin_macros/src/eii.rs +++ b/compiler/rustc_builtin_macros/src/eii.rs @@ -105,10 +105,10 @@ fn eii_( let item_span = func.sig.span; let foreign_item_name = func.ident; - let mut return_items = Vec::new(); + let mut module_items = Vec::new(); if func.body.is_some() { - return_items.push(generate_default_impl( + module_items.push(generate_default_impl( ecx, &func, impl_unsafe, @@ -119,7 +119,7 @@ fn eii_( )) } - return_items.push(generate_foreign_item( + module_items.push(generate_foreign_item( ecx, eii_attr_span, item_span, @@ -127,7 +127,7 @@ fn eii_( vis, &attrs_from_decl, )); - return_items.push(generate_attribute_macro_to_implement( + module_items.push(generate_attribute_macro_to_implement( ecx, eii_attr_span, macro_name, @@ -136,7 +136,7 @@ fn eii_( &attrs_from_decl, )); - return_items.into_iter().map(wrap_item).collect() + module_items.into_iter().map(wrap_item).collect() } /// Decide on the name of the macro that can be used to implement the EII. @@ -213,29 +213,13 @@ fn generate_default_impl( known_eii_macro_resolution: Some(ast::EiiDecl { foreign_item: ecx.path( foreign_item_name.span, - // prefix super to escape the `dflt` module generated below - vec![Ident::from_str_and_span("super", foreign_item_name.span), foreign_item_name], + // prefix super to explicitly escape the const block generated below + vec![Ident::from_str_and_span("self", foreign_item_name.span), foreign_item_name], ), impl_unsafe, }), }); - let item_mod = |span: Span, name: Ident, items: ThinVec>| { - ecx.item( - item_span, - ThinVec::new(), - ItemKind::Mod( - ast::Safety::Default, - name, - ast::ModKind::Loaded( - items, - ast::Inline::Yes, - ast::ModSpans { inner_span: span, inject_use_span: span }, - ), - ), - ) - }; - let anon_mod = |span: Span, stmts: ThinVec| { let unit = ecx.ty(item_span, ast::TyKind::Tup(ThinVec::new())); let underscore = Ident::new(kw::Underscore, item_span); @@ -248,33 +232,13 @@ fn generate_default_impl( }; // const _: () = { - // mod dflt { - // use super::*; - // - // } + // // } anon_mod( item_span, thin_vec![ecx.stmt_item( item_span, - item_mod( - item_span, - Ident::from_str_and_span("dflt", item_span), - thin_vec![ - ecx.item( - item_span, - thin_vec![ecx.attr_nested_word(sym::allow, sym::unused_imports, item_span)], - ItemKind::Use(ast::UseTree { - prefix: ast::Path::from_ident(Ident::from_str_and_span( - "super", item_span, - )), - kind: ast::UseTreeKind::Glob, - span: item_span, - }) - ), - ecx.item(item_span, attrs, ItemKind::Fn(Box::new(default_func))) - ] - ) + ecx.item(item_span, attrs, ItemKind::Fn(Box::new(default_func))) ),], ) } From df55233c6d290bf23f499d846a51897982b7bee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Sun, 11 Jan 2026 16:18:49 +0100 Subject: [PATCH 13/30] disallow in statement position --- compiler/rustc_builtin_macros/messages.ftl | 2 + compiler/rustc_builtin_macros/src/eii.rs | 41 ++++++++++++--------- compiler/rustc_builtin_macros/src/errors.rs | 10 +++++ 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index 1501bd6c73e29..f9ffddf790847 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -161,6 +161,8 @@ builtin_macros_eii_only_once = `#[{$name}]` can only be specified once builtin_macros_eii_shared_macro_expected_function = `#[{$name}]` is only valid on functions builtin_macros_eii_shared_macro_expected_max_one_argument = `#[{$name}]` expected no arguments or a single argument: `#[{$name}(default)]` +builtin_macros_eii_shared_macro_in_statement_position = `#[{$name}]` can only be used on functions inside a module + .label = `#[{$name}]` is used on this item, which is part of another item's local scope builtin_macros_env_not_defined = environment variable `{$var}` not defined at compile time .cargo = Cargo sets build script variables at run time. Use `std::env::var({$var_expr})` instead diff --git a/compiler/rustc_builtin_macros/src/eii.rs b/compiler/rustc_builtin_macros/src/eii.rs index f72b78fe7f99d..43e9dfe0092e7 100644 --- a/compiler/rustc_builtin_macros/src/eii.rs +++ b/compiler/rustc_builtin_macros/src/eii.rs @@ -1,8 +1,7 @@ use rustc_ast::token::{Delimiter, TokenKind}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_ast::{ - Attribute, DUMMY_NODE_ID, EiiDecl, EiiImpl, ItemKind, MetaItem, Path, Stmt, StmtKind, - Visibility, ast, + Attribute, DUMMY_NODE_ID, EiiDecl, EiiImpl, ItemKind, MetaItem, Path, StmtKind, Visibility, ast, }; use rustc_ast_pretty::pprust::path_to_string; use rustc_expand::base::{Annotatable, ExtCtxt}; @@ -12,6 +11,7 @@ use thin_vec::{ThinVec, thin_vec}; use crate::errors::{ EiiExternTargetExpectedList, EiiExternTargetExpectedMacro, EiiExternTargetExpectedUnsafe, EiiMacroExpectedMaxOneArgument, EiiOnlyOnce, EiiSharedMacroExpectedFunction, + EiiSharedMacroInStatementPosition, }; /// ```rust @@ -55,29 +55,29 @@ fn eii_( ecx: &mut ExtCtxt<'_>, eii_attr_span: Span, meta_item: &ast::MetaItem, - item: Annotatable, + orig_item: Annotatable, impl_unsafe: bool, ) -> Vec { let eii_attr_span = ecx.with_def_site_ctxt(eii_attr_span); - let (item, wrap_item): (_, &dyn Fn(_) -> _) = if let Annotatable::Item(item) = item { - (item, &Annotatable::Item) - } else if let Annotatable::Stmt(ref stmt) = item + let item = if let Annotatable::Item(item) = orig_item { + item + } else if let Annotatable::Stmt(ref stmt) = orig_item && let StmtKind::Item(ref item) = stmt.kind + && let ItemKind::Fn(ref f) = item.kind { - (item.clone(), &|item| { - Annotatable::Stmt(Box::new(Stmt { - id: DUMMY_NODE_ID, - kind: StmtKind::Item(item), - span: eii_attr_span, - })) - }) + ecx.dcx().emit_err(EiiSharedMacroInStatementPosition { + span: eii_attr_span.to(item.span), + name: path_to_string(&meta_item.path), + item_span: f.ident.span, + }); + return vec![orig_item]; } else { ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { span: eii_attr_span, name: path_to_string(&meta_item.path), }); - return vec![item]; + return vec![orig_item]; }; let ast::Item { attrs, id: _, span: _, vis, kind: ItemKind::Fn(func), tokens: _ } = @@ -87,7 +87,7 @@ fn eii_( span: eii_attr_span, name: path_to_string(&meta_item.path), }); - return vec![wrap_item(item)]; + return vec![Annotatable::Item(item)]; }; // only clone what we need let attrs = attrs.clone(); @@ -98,7 +98,9 @@ fn eii_( filter_attrs_for_multiple_eii_attr(ecx, attrs, eii_attr_span, &meta_item.path); let Ok(macro_name) = name_for_impl_macro(ecx, &func, &meta_item) else { - return vec![wrap_item(item)]; + // we don't need to wrap in Annotatable::Stmt conditionally since + // EII can't be used on items in statement position + return vec![Annotatable::Item(item)]; }; // span of the declaring item without attributes @@ -136,7 +138,9 @@ fn eii_( &attrs_from_decl, )); - module_items.into_iter().map(wrap_item).collect() + // we don't need to wrap in Annotatable::Stmt conditionally since + // EII can't be used on items in statement position + module_items.into_iter().map(Annotatable::Item).collect() } /// Decide on the name of the macro that can be used to implement the EII. @@ -213,7 +217,8 @@ fn generate_default_impl( known_eii_macro_resolution: Some(ast::EiiDecl { foreign_item: ecx.path( foreign_item_name.span, - // prefix super to explicitly escape the const block generated below + // prefix self to explicitly escape the const block generated below + // NOTE: this is why EIIs can't be used on statements vec![Ident::from_str_and_span("self", foreign_item_name.span), foreign_item_name], ), impl_unsafe, diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index 5d4c4e340fa1f..a9ce41c9be76e 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -1039,6 +1039,16 @@ pub(crate) struct EiiSharedMacroExpectedFunction { pub name: String, } +#[derive(Diagnostic)] +#[diag(builtin_macros_eii_shared_macro_in_statement_position)] +pub(crate) struct EiiSharedMacroInStatementPosition { + #[primary_span] + pub span: Span, + pub name: String, + #[label] + pub item_span: Span, +} + #[derive(Diagnostic)] #[diag(builtin_macros_eii_only_once)] pub(crate) struct EiiOnlyOnce { From 8dd701cec77fec63902e78c575dd34e2f715dfa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Sun, 11 Jan 2026 16:25:10 +0100 Subject: [PATCH 14/30] add test for rejecting EIIs in statement position --- tests/ui/eii/error_statement_position.rs | 15 ++++++++------- tests/ui/eii/error_statement_position.stderr | 13 +++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/ui/eii/error_statement_position.rs b/tests/ui/eii/error_statement_position.rs index cf81e7e6a8b23..75d87595b9e39 100644 --- a/tests/ui/eii/error_statement_position.rs +++ b/tests/ui/eii/error_statement_position.rs @@ -1,11 +1,6 @@ #![feature(extern_item_impls)] -// EIIs can, despite not being super useful, be declared in statement position -// nested inside items. Items in statement position, when expanded as part of a macro, -// need to be wrapped slightly differently (in an `ast::Statement`). -// We did this on the happy path (no errors), but when there was an error, we'd -// replace it with *just* an `ast::Item` not wrapped in an `ast::Statement`. -// This caused an ICE (https://github.com/rust-lang/rust/issues/149980). -// this test fails to build, but demonstrates that no ICE is produced. +// EIIs cannot be used in statement position. +// This is also a regression test for an ICE (https://github.com/rust-lang/rust/issues/149980). fn main() { struct Bar; @@ -13,4 +8,10 @@ fn main() { #[eii] //~^ ERROR `#[eii]` is only valid on functions impl Bar {} + + + // Even on functions, eiis in statement position are rejected + #[eii] + //~^ ERROR `#[eii]` can only be used on functions inside a module + fn foo() {} } diff --git a/tests/ui/eii/error_statement_position.stderr b/tests/ui/eii/error_statement_position.stderr index 01b7394ef00fa..f14e6c33e64f5 100644 --- a/tests/ui/eii/error_statement_position.stderr +++ b/tests/ui/eii/error_statement_position.stderr @@ -1,8 +1,17 @@ error: `#[eii]` is only valid on functions - --> $DIR/error_statement_position.rs:13:5 + --> $DIR/error_statement_position.rs:8:5 | LL | #[eii] | ^^^^^^ -error: aborting due to 1 previous error +error: `#[eii]` can only be used on functions inside a module + --> $DIR/error_statement_position.rs:14:5 + | +LL | #[eii] + | ^^^^^^ +LL | +LL | fn foo() {} + | --- `#[eii]` is used on this item, which is part of another item's local scope + +error: aborting due to 2 previous errors From d0b760369f46eeb8ded4d7420d67d7bdc9f02dd1 Mon Sep 17 00:00:00 2001 From: mu001999 Date: Sat, 10 Jan 2026 17:06:21 +0800 Subject: [PATCH 15/30] Remove all usage of FeedConstTy::No --- .../src/hir_ty_lowering/bounds.rs | 34 ++++----- .../src/hir_ty_lowering/mod.rs | 53 +++++++++++--- compiler/rustc_hir_typeck/src/expr.rs | 5 +- src/librustdoc/clean/mod.rs | 70 ++++++++++++++++--- 4 files changed, 126 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 72d21371f66cc..0a874b881801d 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -510,7 +510,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // Create the generic arguments for the associated type or constant by joining the // parent arguments (the arguments of the trait) and the own arguments (the ones of // the associated item itself) and construct an alias type using them. - let alias_term = candidate.map_bound(|trait_ref| { + candidate.map_bound(|trait_ref| { let item_segment = hir::PathSegment { ident: constraint.ident, hir_id: constraint.hir_id, @@ -528,20 +528,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { debug!(?alias_args); ty::AliasTerm::new_from_args(tcx, assoc_item.def_id, alias_args) - }); - - // Provide the resolved type of the associated constant to `type_of(AnonConst)`. - if let Some(const_arg) = constraint.ct() - && let hir::ConstArgKind::Anon(anon_const) = const_arg.kind - { - let ty = alias_term - .map_bound(|alias| tcx.type_of(alias.def_id).instantiate(tcx, alias.args)); - let ty = - check_assoc_const_binding_type(self, constraint.ident, ty, constraint.hir_id); - tcx.feed_anon_const_type(anon_const.def_id, ty::EarlyBinder::bind(ty)); - } - - alias_term + }) }; match constraint.kind { @@ -555,7 +542,20 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { hir::AssocItemConstraintKind::Equality { term } => { let term = match term { hir::Term::Ty(ty) => self.lower_ty(ty).into(), - hir::Term::Const(ct) => self.lower_const_arg(ct, FeedConstTy::No).into(), + hir::Term::Const(ct) => { + // Provide the resolved type of the associated constant + let ty = projection_term.map_bound(|alias| { + tcx.type_of(alias.def_id).instantiate(tcx, alias.args) + }); + let ty = check_assoc_const_binding_type( + self, + constraint.ident, + ty, + constraint.hir_id, + ); + + self.lower_const_arg(ct, FeedConstTy::WithTy(ty)).into() + } }; // Find any late-bound regions declared in `ty` that are not @@ -871,7 +871,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { /// probably gate this behind another feature flag. /// /// [^1]: . -fn check_assoc_const_binding_type<'tcx>( +pub(crate) fn check_assoc_const_binding_type<'tcx>( cx: &dyn HirTyLowerer<'tcx>, assoc_const: Ident, ty: ty::Binder<'tcx, Ty<'tcx>>, diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index d2cbf89336d8b..be5ac21e2ca84 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -1269,10 +1269,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let mut where_bounds = vec![]; for bound in [bound, bound2].into_iter().chain(matching_candidates) { let bound_id = bound.def_id(); - let bound_span = tcx - .associated_items(bound_id) - .find_by_ident_and_kind(tcx, assoc_ident, assoc_tag, bound_id) - .and_then(|item| tcx.hir_span_if_local(item.def_id)); + let assoc_item = tcx.associated_items(bound_id).find_by_ident_and_kind( + tcx, + assoc_ident, + assoc_tag, + bound_id, + ); + let bound_span = assoc_item.and_then(|item| tcx.hir_span_if_local(item.def_id)); if let Some(bound_span) = bound_span { err.span_label( @@ -1285,7 +1288,41 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let term: ty::Term<'_> = match term { hir::Term::Ty(ty) => self.lower_ty(ty).into(), hir::Term::Const(ct) => { - self.lower_const_arg(ct, FeedConstTy::No).into() + let assoc_item = + assoc_item.expect("assoc_item should be present"); + let projection_term = bound.map_bound(|trait_ref| { + let item_segment = hir::PathSegment { + ident: constraint.ident, + hir_id: constraint.hir_id, + res: Res::Err, + args: Some(constraint.gen_args), + infer_args: false, + }; + + let alias_args = self.lower_generic_args_of_assoc_item( + constraint.ident.span, + assoc_item.def_id, + &item_segment, + trait_ref.args, + ); + ty::AliasTerm::new_from_args( + tcx, + assoc_item.def_id, + alias_args, + ) + }); + + let ty = projection_term.map_bound(|alias| { + tcx.type_of(alias.def_id).instantiate(tcx, alias.args) + }); + let ty = bounds::check_assoc_const_binding_type( + self, + constraint.ident, + ty, + constraint.hir_id, + ); + + self.lower_const_arg(ct, FeedConstTy::WithTy(ty)).into() } }; if term.references_error() { @@ -2993,7 +3030,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .unwrap_or_else(|guar| Ty::new_error(tcx, guar)) } hir::TyKind::Array(ty, length) => { - let length = self.lower_const_arg(length, FeedConstTy::No); + let length = self.lower_const_arg(length, FeedConstTy::WithTy(tcx.types.usize)); Ty::new_array_with_const_len(tcx, self.lower_ty(ty), length) } hir::TyKind::Infer(()) => { @@ -3033,8 +3070,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // Keep this list of types in sync with the list of types that // the `RangePattern` trait is implemented for. ty::Int(_) | ty::Uint(_) | ty::Char => { - let start = self.lower_const_arg(start, FeedConstTy::No); - let end = self.lower_const_arg(end, FeedConstTy::No); + let start = self.lower_const_arg(start, FeedConstTy::WithTy(ty)); + let end = self.lower_const_arg(end, FeedConstTy::WithTy(ty)); Ok(ty::PatternKind::Range { start, end }) } _ => Err(self diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 9d7e09b020a75..d7366887d85c9 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1749,7 +1749,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let count_span = count.span; let count = self.try_structurally_resolve_const( count_span, - self.normalize(count_span, self.lower_const_arg(count, FeedConstTy::No)), + self.normalize( + count_span, + self.lower_const_arg(count, FeedConstTy::WithTy(tcx.types.usize)), + ), ); if let Some(count) = count.try_to_target_usize(tcx) { diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 597a85f397695..ac17014ebfdeb 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -469,11 +469,23 @@ fn clean_middle_term<'tcx>( } } -fn clean_hir_term<'tcx>(term: &hir::Term<'tcx>, cx: &mut DocContext<'tcx>) -> Term { +fn clean_hir_term<'tcx>( + assoc_item: Option, + term: &hir::Term<'tcx>, + span: rustc_span::Span, + cx: &mut DocContext<'tcx>, +) -> Term { match term { hir::Term::Ty(ty) => Term::Type(clean_ty(ty, cx)), hir::Term::Const(c) => { - let ct = lower_const_arg_for_rustdoc(cx.tcx, c, FeedConstTy::No); + let ty = if let Some(assoc_item) = assoc_item { + // FIXME(generic_const_items): this should instantiate with the alias item's args + cx.tcx.type_of(assoc_item).instantiate_identity() + } else { + Ty::new_error_with_message(cx.tcx, span, "cannot find the associated constant") + }; + + let ct = lower_const_arg_for_rustdoc(cx.tcx, c, FeedConstTy::WithTy(ty)); Term::Constant(clean_middle_const(ty::Binder::dummy(ct), cx)) } } @@ -650,7 +662,14 @@ fn clean_generic_param<'tcx>( GenericParamDefKind::Const { ty: Box::new(clean_ty(ty, cx)), default: default.map(|ct| { - Box::new(lower_const_arg_for_rustdoc(cx.tcx, ct, FeedConstTy::No).to_string()) + Box::new( + lower_const_arg_for_rustdoc( + cx.tcx, + ct, + FeedConstTy::WithTy(lower_ty(cx.tcx, ty)), + ) + .to_string(), + ) }), }, ), @@ -1531,7 +1550,7 @@ fn first_non_private_clean_path<'tcx>( && path_last.args.is_some() { assert!(new_path_last.args.is_empty()); - new_path_last.args = clean_generic_args(path_last_args, cx); + new_path_last.args = clean_generic_args(None, path_last_args, cx); } new_clean_path } @@ -1812,7 +1831,11 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T let length = match const_arg.kind { hir::ConstArgKind::Infer(..) | hir::ConstArgKind::Error(..) => "_".to_string(), hir::ConstArgKind::Anon(hir::AnonConst { def_id, .. }) => { - let ct = lower_const_arg_for_rustdoc(cx.tcx, const_arg, FeedConstTy::No); + let ct = lower_const_arg_for_rustdoc( + cx.tcx, + const_arg, + FeedConstTy::WithTy(cx.tcx.types.usize), + ); let typing_env = ty::TypingEnv::post_analysis(cx.tcx, *def_id); let ct = cx.tcx.normalize_erasing_regions(typing_env, ct); print_const(cx, ct) @@ -1823,7 +1846,11 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T | hir::ConstArgKind::Tup(..) | hir::ConstArgKind::Array(..) | hir::ConstArgKind::Literal(..) => { - let ct = lower_const_arg_for_rustdoc(cx.tcx, const_arg, FeedConstTy::No); + let ct = lower_const_arg_for_rustdoc( + cx.tcx, + const_arg, + FeedConstTy::WithTy(cx.tcx.types.usize), + ); print_const(cx, ct) } }; @@ -2516,6 +2543,7 @@ fn clean_path<'tcx>(path: &hir::Path<'tcx>, cx: &mut DocContext<'tcx>) -> Path { } fn clean_generic_args<'tcx>( + trait_did: Option, generic_args: &hir::GenericArgs<'tcx>, cx: &mut DocContext<'tcx>, ) -> GenericArgs { @@ -2539,7 +2567,13 @@ fn clean_generic_args<'tcx>( let constraints = generic_args .constraints .iter() - .map(|c| clean_assoc_item_constraint(c, cx)) + .map(|c| { + clean_assoc_item_constraint( + trait_did.expect("only trait ref has constraints"), + c, + cx, + ) + }) .collect::>(); GenericArgs::AngleBracketed { args, constraints } } @@ -2562,7 +2596,11 @@ fn clean_path_segment<'tcx>( path: &hir::PathSegment<'tcx>, cx: &mut DocContext<'tcx>, ) -> PathSegment { - PathSegment { name: path.ident.name, args: clean_generic_args(path.args(), cx) } + let trait_did = match path.res { + hir::def::Res::Def(DefKind::Trait | DefKind::TraitAlias, did) => Some(did), + _ => None, + }; + PathSegment { name: path.ident.name, args: clean_generic_args(trait_did, path.args(), cx) } } fn clean_bare_fn_ty<'tcx>( @@ -3126,17 +3164,29 @@ fn clean_maybe_renamed_foreign_item<'tcx>( } fn clean_assoc_item_constraint<'tcx>( + trait_did: DefId, constraint: &hir::AssocItemConstraint<'tcx>, cx: &mut DocContext<'tcx>, ) -> AssocItemConstraint { AssocItemConstraint { assoc: PathSegment { name: constraint.ident.name, - args: clean_generic_args(constraint.gen_args, cx), + args: clean_generic_args(None, constraint.gen_args, cx), }, kind: match constraint.kind { hir::AssocItemConstraintKind::Equality { ref term } => { - AssocItemConstraintKind::Equality { term: clean_hir_term(term, cx) } + let assoc_tag = match term { + hir::Term::Ty(_) => ty::AssocTag::Type, + hir::Term::Const(_) => ty::AssocTag::Const, + }; + let assoc_item = cx + .tcx + .associated_items(trait_did) + .find_by_ident_and_kind(cx.tcx, constraint.ident, assoc_tag, trait_did) + .map(|item| item.def_id); + AssocItemConstraintKind::Equality { + term: clean_hir_term(assoc_item, term, constraint.span, cx), + } } hir::AssocItemConstraintKind::Bound { bounds } => AssocItemConstraintKind::Bound { bounds: bounds.iter().filter_map(|b| clean_generic_bound(b, cx)).collect(), From e27fcfd28eb6b185894e38d1f9fbde216f026f3e Mon Sep 17 00:00:00 2001 From: mu001999 Date: Sun, 11 Jan 2026 18:43:25 +0800 Subject: [PATCH 16/30] Remove FeedConstTy --- compiler/rustc_hir_analysis/src/collect.rs | 8 +- .../src/hir_ty_lowering/bounds.rs | 5 +- .../src/hir_ty_lowering/mod.rs | 89 +++++-------------- compiler/rustc_hir_analysis/src/lib.rs | 6 +- compiler/rustc_hir_typeck/src/expr.rs | 7 +- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 13 +-- .../rustc_hir_typeck/src/method/confirm.rs | 7 +- src/librustdoc/clean/mod.rs | 22 +---- 8 files changed, 48 insertions(+), 109 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index bacdf0049806e..2c02534093870 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -47,9 +47,7 @@ use rustc_trait_selection::traits::{ use tracing::{debug, instrument}; use crate::errors; -use crate::hir_ty_lowering::{ - FeedConstTy, HirTyLowerer, InherentAssocCandidate, RegionInferReason, -}; +use crate::hir_ty_lowering::{HirTyLowerer, InherentAssocCandidate, RegionInferReason}; pub(crate) mod dump; mod generics_of; @@ -1499,7 +1497,7 @@ fn const_param_default<'tcx>( let ct = icx .lowerer() - .lower_const_arg(default_ct, FeedConstTy::with_type_of(tcx, def_id, identity_args)); + .lower_const_arg(default_ct, tcx.type_of(def_id).instantiate(tcx, identity_args)); ty::EarlyBinder::bind(ct) } @@ -1557,7 +1555,7 @@ fn const_of_item<'tcx>( let identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); let ct = icx .lowerer() - .lower_const_arg(ct_arg, FeedConstTy::with_type_of(tcx, def_id.to_def_id(), identity_args)); + .lower_const_arg(ct_arg, tcx.type_of(def_id.to_def_id()).instantiate(tcx, identity_args)); if let Err(e) = icx.check_tainted_by_errors() && !ct.references_error() { diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 0a874b881801d..d6441702b268c 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -20,7 +20,7 @@ use tracing::{debug, instrument}; use crate::errors; use crate::hir_ty_lowering::{ - AssocItemQSelf, FeedConstTy, GenericsArgsErrExtend, HirTyLowerer, ImpliedBoundsContext, + AssocItemQSelf, GenericsArgsErrExtend, HirTyLowerer, ImpliedBoundsContext, OverlappingAsssocItemConstraints, PredicateFilter, RegionInferReason, }; @@ -543,7 +543,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let term = match term { hir::Term::Ty(ty) => self.lower_ty(ty).into(), hir::Term::Const(ct) => { - // Provide the resolved type of the associated constant let ty = projection_term.map_bound(|alias| { tcx.type_of(alias.def_id).instantiate(tcx, alias.args) }); @@ -554,7 +553,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { constraint.hir_id, ); - self.lower_const_arg(ct, FeedConstTy::WithTy(ty)).into() + self.lower_const_arg(ct, ty).into() } }; diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index be5ac21e2ca84..3dcc0232b301e 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -253,35 +253,6 @@ impl AssocItemQSelf { } } -/// In some cases, [`hir::ConstArg`]s that are being used in the type system -/// through const generics need to have their type "fed" to them -/// using the query system. -/// -/// Use this enum with `::lower_const_arg` to instruct it with the -/// desired behavior. -#[derive(Debug, Clone, Copy)] -pub enum FeedConstTy<'tcx> { - /// Feed the type to the (anno) const arg. - WithTy(Ty<'tcx>), - /// Don't feed the type. - No, -} - -impl<'tcx> FeedConstTy<'tcx> { - /// The `DefId` belongs to the const param that we are supplying - /// this (anon) const arg to. - /// - /// The list of generic args is used to instantiate the parameters - /// used by the type of the const param specified by `DefId`. - pub fn with_type_of( - tcx: TyCtxt<'tcx>, - def_id: DefId, - generic_args: &[ty::GenericArg<'tcx>], - ) -> Self { - Self::WithTy(tcx.type_of(def_id).instantiate(tcx, generic_args)) - } -} - #[derive(Debug, Clone, Copy)] enum LowerTypeRelativePathMode { Type(PermitVariants), @@ -733,7 +704,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // Ambig portions of `ConstArg` are handled in the match arm below .lower_const_arg( ct.as_unambig_ct(), - FeedConstTy::with_type_of(tcx, param.def_id, preceding_args), + tcx.type_of(param.def_id).instantiate(tcx, preceding_args), ) .into(), (&GenericParamDefKind::Const { .. }, GenericArg::Infer(inf)) => { @@ -1322,7 +1293,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { constraint.hir_id, ); - self.lower_const_arg(ct, FeedConstTy::WithTy(ty)).into() + self.lower_const_arg(ct, ty).into() } }; if term.references_error() { @@ -2347,16 +2318,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { /// Lower a [`hir::ConstArg`] to a (type-level) [`ty::Const`](Const). #[instrument(skip(self), level = "debug")] - pub fn lower_const_arg( - &self, - const_arg: &hir::ConstArg<'tcx>, - feed: FeedConstTy<'tcx>, - ) -> Const<'tcx> { + pub fn lower_const_arg(&self, const_arg: &hir::ConstArg<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { let tcx = self.tcx(); - if let FeedConstTy::WithTy(anon_const_type) = feed - && let hir::ConstArgKind::Anon(anon) = &const_arg.kind - { + if let hir::ConstArgKind::Anon(anon) = &const_arg.kind { // FIXME(generic_const_parameter_types): Ideally we remove these errors below when // we have the ability to intermix typeck of anon const const args with the parent // bodies typeck. @@ -2366,7 +2331,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // hir typeck was using equality but mir borrowck wound up using subtyping as that could // result in a non-infer in hir typeck but a region variable in borrowck. if tcx.features().generic_const_parameter_types() - && (anon_const_type.has_free_regions() || anon_const_type.has_erased_regions()) + && (ty.has_free_regions() || ty.has_erased_regions()) { let e = self.dcx().span_err( const_arg.span, @@ -2378,7 +2343,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // We must error if the instantiated type has any inference variables as we will // use this type to feed the `type_of` and query results must not contain inference // variables otherwise we will ICE. - if anon_const_type.has_non_region_infer() { + if ty.has_non_region_infer() { let e = self.dcx().span_err( const_arg.span, "anonymous constants with inferred types are not yet supported", @@ -2388,7 +2353,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } // We error when the type contains unsubstituted generics since we do not currently // give the anon const any of the generics from the parent. - if anon_const_type.has_non_region_param() { + if ty.has_non_region_param() { let e = self.dcx().span_err( const_arg.span, "anonymous constants referencing generics are not yet supported", @@ -2397,12 +2362,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { return ty::Const::new_error(tcx, e); } - tcx.feed_anon_const_type(anon.def_id, ty::EarlyBinder::bind(anon_const_type)); + tcx.feed_anon_const_type(anon.def_id, ty::EarlyBinder::bind(ty)); } let hir_id = const_arg.hir_id; match const_arg.kind { - hir::ConstArgKind::Tup(exprs) => self.lower_const_arg_tup(exprs, feed, const_arg.span), + hir::ConstArgKind::Tup(exprs) => self.lower_const_arg_tup(exprs, ty, const_arg.span), hir::ConstArgKind::Path(hir::QPath::Resolved(maybe_qself, path)) => { debug!(?maybe_qself, ?path); let opt_self_ty = maybe_qself.as_ref().map(|qself| self.lower_ty(qself)); @@ -2426,16 +2391,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { hir::ConstArgKind::TupleCall(qpath, args) => { self.lower_const_arg_tuple_call(hir_id, qpath, args, const_arg.span) } - hir::ConstArgKind::Array(array_expr) => self.lower_const_arg_array(array_expr, feed), + hir::ConstArgKind::Array(array_expr) => self.lower_const_arg_array(array_expr, ty), hir::ConstArgKind::Anon(anon) => self.lower_const_arg_anon(anon), hir::ConstArgKind::Infer(()) => self.ct_infer(None, const_arg.span), hir::ConstArgKind::Error(e) => ty::Const::new_error(tcx, e), - hir::ConstArgKind::Literal(kind) if let FeedConstTy::WithTy(anon_const_type) = feed => { - self.lower_const_arg_literal(&kind, anon_const_type, const_arg.span) - } - hir::ConstArgKind::Literal(..) => { - let e = self.dcx().span_err(const_arg.span, "literal of unknown type"); - ty::Const::new_error(tcx, e) + hir::ConstArgKind::Literal(kind) => { + self.lower_const_arg_literal(&kind, ty, const_arg.span) } } } @@ -2443,14 +2404,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { fn lower_const_arg_array( &self, array_expr: &'tcx hir::ConstArgArrayExpr<'tcx>, - feed: FeedConstTy<'tcx>, + ty: Ty<'tcx>, ) -> Const<'tcx> { let tcx = self.tcx(); - let FeedConstTy::WithTy(ty) = feed else { - return Const::new_error_with_message(tcx, array_expr.span, "unsupported const array"); - }; - let ty::Array(elem_ty, _) = ty.kind() else { return Const::new_error_with_message( tcx, @@ -2462,7 +2419,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let elems = array_expr .elems .iter() - .map(|elem| self.lower_const_arg(elem, FeedConstTy::WithTy(*elem_ty))) + .map(|elem| self.lower_const_arg(elem, *elem_ty)) .collect::>(); let valtree = ty::ValTree::from_branches(tcx, elems); @@ -2545,7 +2502,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .iter() .zip(args) .map(|(field_def, arg)| { - self.lower_const_arg(arg, FeedConstTy::with_type_of(tcx, field_def.did, adt_args)) + self.lower_const_arg(arg, tcx.type_of(field_def.did).instantiate(tcx, adt_args)) }) .collect::>(); @@ -2564,15 +2521,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { fn lower_const_arg_tup( &self, exprs: &'tcx [&'tcx hir::ConstArg<'tcx>], - feed: FeedConstTy<'tcx>, + ty: Ty<'tcx>, span: Span, ) -> Const<'tcx> { let tcx = self.tcx(); - let FeedConstTy::WithTy(ty) = feed else { - return Const::new_error_with_message(tcx, span, "const tuple lack type information"); - }; - let ty::Tuple(tys) = ty.kind() else { let e = tcx.dcx().span_err(span, format!("expected `{}`, found const tuple", ty)); return Const::new_error(tcx, e); @@ -2581,7 +2534,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let exprs = exprs .iter() .zip(tys.iter()) - .map(|(expr, ty)| self.lower_const_arg(expr, FeedConstTy::WithTy(ty))) + .map(|(expr, ty)| self.lower_const_arg(expr, ty)) .collect::>(); let valtree = ty::ValTree::from_branches(tcx, exprs); @@ -2668,7 +2621,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { self.lower_const_arg( expr.expr, - FeedConstTy::with_type_of(tcx, field_def.did, adt_args), + tcx.type_of(field_def.did).instantiate(tcx, adt_args), ) } None => { @@ -3030,7 +2983,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .unwrap_or_else(|guar| Ty::new_error(tcx, guar)) } hir::TyKind::Array(ty, length) => { - let length = self.lower_const_arg(length, FeedConstTy::WithTy(tcx.types.usize)); + let length = self.lower_const_arg(length, tcx.types.usize); Ty::new_array_with_const_len(tcx, self.lower_ty(ty), length) } hir::TyKind::Infer(()) => { @@ -3070,8 +3023,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // Keep this list of types in sync with the list of types that // the `RangePattern` trait is implemented for. ty::Int(_) | ty::Uint(_) | ty::Char => { - let start = self.lower_const_arg(start, FeedConstTy::WithTy(ty)); - let end = self.lower_const_arg(end, FeedConstTy::WithTy(ty)); + let start = self.lower_const_arg(start, ty); + let end = self.lower_const_arg(end, ty); Ok(ty::PatternKind::Range { start, end }) } _ => Err(self diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs index 7296ba6f964a4..a51355adf72fe 100644 --- a/compiler/rustc_hir_analysis/src/lib.rs +++ b/compiler/rustc_hir_analysis/src/lib.rs @@ -101,7 +101,7 @@ use rustc_span::{ErrorGuaranteed, Span}; use rustc_trait_selection::traits; pub use crate::collect::suggest_impl_trait; -use crate::hir_ty_lowering::{FeedConstTy, HirTyLowerer}; +use crate::hir_ty_lowering::HirTyLowerer; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } @@ -301,8 +301,8 @@ pub fn lower_ty<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: &hir::Ty<'tcx>) -> Ty<'tcx> { pub fn lower_const_arg_for_rustdoc<'tcx>( tcx: TyCtxt<'tcx>, hir_ct: &hir::ConstArg<'tcx>, - feed: FeedConstTy<'tcx>, + ty: Ty<'tcx>, ) -> Const<'tcx> { let env_def_id = tcx.hir_get_parent_item(hir_ct.hir_id); - collect::ItemCtxt::new(tcx, env_def_id.def_id).lowerer().lower_const_arg(hir_ct, feed) + collect::ItemCtxt::new(tcx, env_def_id.def_id).lowerer().lower_const_arg(hir_ct, ty) } diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index d7366887d85c9..9f3ff0b2d03c2 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -21,7 +21,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; use rustc_hir::{ExprKind, HirId, QPath, find_attr, is_range_literal}; use rustc_hir_analysis::NoVariantNamed; -use rustc_hir_analysis::hir_ty_lowering::{FeedConstTy, HirTyLowerer as _}; +use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer as _; use rustc_infer::infer::{self, DefineOpaqueTypes, InferOk, RegionVariableOrigin}; use rustc_infer::traits::query::NoSolution; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase}; @@ -1749,10 +1749,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let count_span = count.span; let count = self.try_structurally_resolve_const( count_span, - self.normalize( - count_span, - self.lower_const_arg(count, FeedConstTy::WithTy(tcx.types.usize)), - ), + self.normalize(count_span, self.lower_const_arg(count, tcx.types.usize)), ); if let Some(count) = count.try_to_target_usize(tcx) { diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index a66ff2a23c250..91f91d9114449 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -14,8 +14,8 @@ use rustc_hir_analysis::hir_ty_lowering::generics::{ check_generic_arg_count_for_call, lower_generic_args, }; use rustc_hir_analysis::hir_ty_lowering::{ - ExplicitLateBound, FeedConstTy, GenericArgCountMismatch, GenericArgCountResult, - GenericArgsLowerer, GenericPathSegment, HirTyLowerer, IsMethodCall, RegionInferReason, + ExplicitLateBound, GenericArgCountMismatch, GenericArgCountResult, GenericArgsLowerer, + GenericPathSegment, HirTyLowerer, IsMethodCall, RegionInferReason, }; use rustc_infer::infer::canonical::{Canonical, OriginalQueryValues, QueryResponse}; use rustc_infer::infer::{DefineOpaqueTypes, InferResult}; @@ -525,9 +525,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn lower_const_arg( &self, const_arg: &'tcx hir::ConstArg<'tcx>, - feed: FeedConstTy<'tcx>, + ty: Ty<'tcx>, ) -> ty::Const<'tcx> { - let ct = self.lowerer().lower_const_arg(const_arg, feed); + let ct = self.lowerer().lower_const_arg(const_arg, ty); self.register_wf_obligation( ct.into(), self.tcx.hir_span(const_arg.hir_id), @@ -1228,7 +1228,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Ambiguous parts of `ConstArg` are handled in the match arms below .lower_const_arg( ct.as_unambig_ct(), - FeedConstTy::with_type_of(self.fcx.tcx, param.def_id, preceding_args), + self.fcx + .tcx + .type_of(param.def_id) + .instantiate(self.fcx.tcx, preceding_args), ) .into(), (&GenericParamDefKind::Const { .. }, GenericArg::Infer(inf)) => { diff --git a/compiler/rustc_hir_typeck/src/method/confirm.rs b/compiler/rustc_hir_typeck/src/method/confirm.rs index aab4e39855557..270f011b2b15f 100644 --- a/compiler/rustc_hir_typeck/src/method/confirm.rs +++ b/compiler/rustc_hir_typeck/src/method/confirm.rs @@ -7,7 +7,7 @@ use rustc_hir_analysis::hir_ty_lowering::generics::{ check_generic_arg_count_for_call, lower_generic_args, }; use rustc_hir_analysis::hir_ty_lowering::{ - FeedConstTy, GenericArgsLowerer, HirTyLowerer, IsMethodCall, RegionInferReason, + GenericArgsLowerer, HirTyLowerer, IsMethodCall, RegionInferReason, }; use rustc_infer::infer::{ BoundRegionConversionTime, DefineOpaqueTypes, InferOk, RegionVariableOrigin, @@ -447,7 +447,10 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // We handle the ambig portions of `ConstArg` in the match arms below .lower_const_arg( ct.as_unambig_ct(), - FeedConstTy::with_type_of(self.cfcx.tcx, param.def_id, preceding_args), + self.cfcx + .tcx + .type_of(param.def_id) + .instantiate(self.cfcx.tcx, preceding_args), ) .into(), (GenericParamDefKind::Const { .. }, GenericArg::Infer(inf)) => { diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index ac17014ebfdeb..c7c23f75d6943 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -43,7 +43,6 @@ use rustc_hir::attrs::{AttributeKind, DocAttribute, DocInline}; use rustc_hir::def::{CtorKind, DefKind, MacroKinds, Res}; use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE, LocalDefId}; use rustc_hir::{LangItem, PredicateOrigin, find_attr}; -use rustc_hir_analysis::hir_ty_lowering::FeedConstTy; use rustc_hir_analysis::{lower_const_arg_for_rustdoc, lower_ty}; use rustc_middle::metadata::Reexport; use rustc_middle::middle::resolve_bound_vars as rbv; @@ -485,7 +484,7 @@ fn clean_hir_term<'tcx>( Ty::new_error_with_message(cx.tcx, span, "cannot find the associated constant") }; - let ct = lower_const_arg_for_rustdoc(cx.tcx, c, FeedConstTy::WithTy(ty)); + let ct = lower_const_arg_for_rustdoc(cx.tcx, c, ty); Term::Constant(clean_middle_const(ty::Binder::dummy(ct), cx)) } } @@ -663,12 +662,7 @@ fn clean_generic_param<'tcx>( ty: Box::new(clean_ty(ty, cx)), default: default.map(|ct| { Box::new( - lower_const_arg_for_rustdoc( - cx.tcx, - ct, - FeedConstTy::WithTy(lower_ty(cx.tcx, ty)), - ) - .to_string(), + lower_const_arg_for_rustdoc(cx.tcx, ct, lower_ty(cx.tcx, ty)).to_string(), ) }), }, @@ -1831,11 +1825,7 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T let length = match const_arg.kind { hir::ConstArgKind::Infer(..) | hir::ConstArgKind::Error(..) => "_".to_string(), hir::ConstArgKind::Anon(hir::AnonConst { def_id, .. }) => { - let ct = lower_const_arg_for_rustdoc( - cx.tcx, - const_arg, - FeedConstTy::WithTy(cx.tcx.types.usize), - ); + let ct = lower_const_arg_for_rustdoc(cx.tcx, const_arg, cx.tcx.types.usize); let typing_env = ty::TypingEnv::post_analysis(cx.tcx, *def_id); let ct = cx.tcx.normalize_erasing_regions(typing_env, ct); print_const(cx, ct) @@ -1846,11 +1836,7 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T | hir::ConstArgKind::Tup(..) | hir::ConstArgKind::Array(..) | hir::ConstArgKind::Literal(..) => { - let ct = lower_const_arg_for_rustdoc( - cx.tcx, - const_arg, - FeedConstTy::WithTy(cx.tcx.types.usize), - ); + let ct = lower_const_arg_for_rustdoc(cx.tcx, const_arg, cx.tcx.types.usize); print_const(cx, ct) } }; From 37f20b7631c6a6c712f0c19a91531623282d6507 Mon Sep 17 00:00:00 2001 From: mu001999 Date: Sun, 11 Jan 2026 21:02:00 +0800 Subject: [PATCH 17/30] Bless tests --- .../associated-const-bindings/ambiguity.stderr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ui/const-generics/associated-const-bindings/ambiguity.stderr b/tests/ui/const-generics/associated-const-bindings/ambiguity.stderr index a14afe9d6923b..806708f18d654 100644 --- a/tests/ui/const-generics/associated-const-bindings/ambiguity.stderr +++ b/tests/ui/const-generics/associated-const-bindings/ambiguity.stderr @@ -27,6 +27,12 @@ LL | const C: &'static str; ... LL | fn take1(_: impl Trait1) {} | ^^^^^^^ ambiguous associated constant `C` + | + = help: consider introducing a new type parameter `T` and adding `where` constraints: + where + T: Trait1, + T: Parent2::C = "?", + T: Parent1::C = "?" error: aborting due to 2 previous errors From 80bd069138feba6452059852785bb913a7dba66c Mon Sep 17 00:00:00 2001 From: mu001999 Date: Mon, 12 Jan 2026 23:50:32 +0800 Subject: [PATCH 18/30] Fix review comments --- .../rustc_hir_analysis/src/hir_ty_lowering/mod.rs | 2 ++ src/librustdoc/clean/mod.rs | 14 +++----------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 3dcc0232b301e..65d2d61e6fc45 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -1283,6 +1283,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ) }); + // FIXME(mgca): code duplication with other places we lower + // the rhs' of associated const bindings let ty = projection_term.map_bound(|alias| { tcx.type_of(alias.def_id).instantiate(tcx, alias.args) }); diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index c7c23f75d6943..0ad9d2fdbe804 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -471,19 +471,13 @@ fn clean_middle_term<'tcx>( fn clean_hir_term<'tcx>( assoc_item: Option, term: &hir::Term<'tcx>, - span: rustc_span::Span, cx: &mut DocContext<'tcx>, ) -> Term { match term { hir::Term::Ty(ty) => Term::Type(clean_ty(ty, cx)), hir::Term::Const(c) => { - let ty = if let Some(assoc_item) = assoc_item { - // FIXME(generic_const_items): this should instantiate with the alias item's args - cx.tcx.type_of(assoc_item).instantiate_identity() - } else { - Ty::new_error_with_message(cx.tcx, span, "cannot find the associated constant") - }; - + // FIXME(generic_const_items): this should instantiate with the alias item's args + let ty = cx.tcx.type_of(assoc_item.unwrap()).instantiate_identity(); let ct = lower_const_arg_for_rustdoc(cx.tcx, c, ty); Term::Constant(clean_middle_const(ty::Binder::dummy(ct), cx)) } @@ -3170,9 +3164,7 @@ fn clean_assoc_item_constraint<'tcx>( .associated_items(trait_did) .find_by_ident_and_kind(cx.tcx, constraint.ident, assoc_tag, trait_did) .map(|item| item.def_id); - AssocItemConstraintKind::Equality { - term: clean_hir_term(assoc_item, term, constraint.span, cx), - } + AssocItemConstraintKind::Equality { term: clean_hir_term(assoc_item, term, cx) } } hir::AssocItemConstraintKind::Bound { bounds } => AssocItemConstraintKind::Bound { bounds: bounds.iter().filter_map(|b| clean_generic_bound(b, cx)).collect(), From 12b4b72715b5814ab48fc4c78169fd6cef0ccc25 Mon Sep 17 00:00:00 2001 From: mu001999 Date: Tue, 13 Jan 2026 20:53:48 +0800 Subject: [PATCH 19/30] Add test for issue 151048 --- .../mgca/tuple_expr_arg_bad-issue-151048.rs | 8 ++++++++ .../mgca/tuple_expr_arg_bad-issue-151048.stderr | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/ui/const-generics/mgca/tuple_expr_arg_bad-issue-151048.rs create mode 100644 tests/ui/const-generics/mgca/tuple_expr_arg_bad-issue-151048.stderr diff --git a/tests/ui/const-generics/mgca/tuple_expr_arg_bad-issue-151048.rs b/tests/ui/const-generics/mgca/tuple_expr_arg_bad-issue-151048.rs new file mode 100644 index 0000000000000..4aecd30e86bbb --- /dev/null +++ b/tests/ui/const-generics/mgca/tuple_expr_arg_bad-issue-151048.rs @@ -0,0 +1,8 @@ +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +struct Y { + stuff: [u8; { ([1, 2], 3, [4, 5]) }], //~ ERROR expected `usize`, found const tuple +} + +fn main() {} diff --git a/tests/ui/const-generics/mgca/tuple_expr_arg_bad-issue-151048.stderr b/tests/ui/const-generics/mgca/tuple_expr_arg_bad-issue-151048.stderr new file mode 100644 index 0000000000000..468bf703d90d4 --- /dev/null +++ b/tests/ui/const-generics/mgca/tuple_expr_arg_bad-issue-151048.stderr @@ -0,0 +1,8 @@ +error: expected `usize`, found const tuple + --> $DIR/tuple_expr_arg_bad-issue-151048.rs:5:19 + | +LL | stuff: [u8; { ([1, 2], 3, [4, 5]) }], + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 2b209a69cf97c18ecef9043abf3ec8b6fe9867b9 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:21:16 +0000 Subject: [PATCH 20/30] Avoid serde dependency in build_helper when not necessary --- src/bootstrap/Cargo.toml | 2 +- src/build_helper/Cargo.toml | 7 +++++-- src/build_helper/src/lib.rs | 1 + src/ci/citool/Cargo.toml | 2 +- src/tools/opt-dist/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index e1725db60cfca..e66c0576e9ee5 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -6,7 +6,7 @@ build = "build.rs" default-run = "bootstrap" [features] -build-metrics = ["sysinfo"] +build-metrics = ["dep:sysinfo", "build_helper/metrics"] tracing = ["dep:tracing", "dep:tracing-chrome", "dep:tracing-subscriber", "dep:chrono", "dep:tempfile"] [lib] diff --git a/src/build_helper/Cargo.toml b/src/build_helper/Cargo.toml index 66894e1abc40e..2ec44fe2730b9 100644 --- a/src/build_helper/Cargo.toml +++ b/src/build_helper/Cargo.toml @@ -6,5 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = "1" -serde_derive = "1" +serde = { version = "1", optional = true } +serde_derive = { version = "1", optional = true } + +[features] +metrics = ["dep:serde", "dep:serde_derive"] diff --git a/src/build_helper/src/lib.rs b/src/build_helper/src/lib.rs index 266eedc624582..e23a158ac0598 100644 --- a/src/build_helper/src/lib.rs +++ b/src/build_helper/src/lib.rs @@ -4,6 +4,7 @@ pub mod ci; pub mod drop_bomb; pub mod fs; pub mod git; +#[cfg(feature = "metrics")] pub mod metrics; pub mod npm; pub mod stage0_parser; diff --git a/src/ci/citool/Cargo.toml b/src/ci/citool/Cargo.toml index 468d8c8a02aa7..539edf60033a5 100644 --- a/src/ci/citool/Cargo.toml +++ b/src/ci/citool/Cargo.toml @@ -15,7 +15,7 @@ serde_yaml = "0.9" serde_json = "1" ureq = { version = "3", features = ["json"] } -build_helper = { path = "../../build_helper" } +build_helper = { path = "../../build_helper", features = ["metrics"] } [dev-dependencies] insta = "1" diff --git a/src/tools/opt-dist/Cargo.toml b/src/tools/opt-dist/Cargo.toml index f4051ae67d7c0..66c19183f3433 100644 --- a/src/tools/opt-dist/Cargo.toml +++ b/src/tools/opt-dist/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -build_helper = { path = "../../build_helper" } +build_helper = { path = "../../build_helper", features = ["metrics"] } env_logger = "0.11" log = "0.4" anyhow = "1" From 7d80e7d720162fed8223407e91912683631c93f2 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Wed, 14 Jan 2026 23:12:57 +0900 Subject: [PATCH 21/30] rustc_target: Remove unused Arch::PowerPC64LE target_arch for powerpc64le- targets is "powerpc64". --- compiler/rustc_codegen_llvm/src/mono_item.rs | 2 +- compiler/rustc_codegen_llvm/src/va_arg.rs | 9 --------- compiler/rustc_span/src/symbol.rs | 1 - compiler/rustc_target/src/asm/mod.rs | 2 +- compiler/rustc_target/src/callconv/mod.rs | 2 +- compiler/rustc_target/src/spec/mod.rs | 7 ++----- compiler/rustc_target/src/target_features.rs | 8 +------- src/tools/miri/src/shims/alloc.rs | 1 - 8 files changed, 6 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/mono_item.rs b/compiler/rustc_codegen_llvm/src/mono_item.rs index 1878f4043ee84..838db689e7292 100644 --- a/compiler/rustc_codegen_llvm/src/mono_item.rs +++ b/compiler/rustc_codegen_llvm/src/mono_item.rs @@ -144,7 +144,7 @@ impl CodegenCx<'_, '_> { } // PowerPC64 prefers TOC indirection to avoid copy relocations. - if matches!(self.tcx.sess.target.arch, Arch::PowerPC64 | Arch::PowerPC64LE) { + if self.tcx.sess.target.arch == Arch::PowerPC64 { return false; } diff --git a/compiler/rustc_codegen_llvm/src/va_arg.rs b/compiler/rustc_codegen_llvm/src/va_arg.rs index 688f461e7478a..c7da2457ada5c 100644 --- a/compiler/rustc_codegen_llvm/src/va_arg.rs +++ b/compiler/rustc_codegen_llvm/src/va_arg.rs @@ -1064,15 +1064,6 @@ pub(super) fn emit_va_arg<'ll, 'tcx>( AllowHigherAlign::Yes, ForceRightAdjust::Yes, ), - Arch::PowerPC64LE => emit_ptr_va_arg( - bx, - addr, - target_ty, - PassMode::Direct, - SlotSize::Bytes8, - AllowHigherAlign::Yes, - ForceRightAdjust::No, - ), Arch::LoongArch32 => emit_ptr_va_arg( bx, addr, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b473d36a45fcb..c4f77cedbe09e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1723,7 +1723,6 @@ symbols! { postfix_match, powerpc, powerpc64, - powerpc64le, powerpc_target_feature, powf16, powf32, diff --git a/compiler/rustc_target/src/asm/mod.rs b/compiler/rustc_target/src/asm/mod.rs index 0078866ab9503..a10699bbce884 100644 --- a/compiler/rustc_target/src/asm/mod.rs +++ b/compiler/rustc_target/src/asm/mod.rs @@ -261,7 +261,7 @@ impl InlineAsmArch { Arch::Mips | Arch::Mips32r6 => Some(Self::Mips), Arch::Mips64 | Arch::Mips64r6 => Some(Self::Mips64), Arch::PowerPC => Some(Self::PowerPC), - Arch::PowerPC64 | Arch::PowerPC64LE => Some(Self::PowerPC64), + Arch::PowerPC64 => Some(Self::PowerPC64), Arch::S390x => Some(Self::S390x), Arch::Sparc => Some(Self::Sparc), Arch::Sparc64 => Some(Self::Sparc64), diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index 6faa57252ca2a..6c8e0e181c4a4 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -702,7 +702,7 @@ impl<'a, Ty> FnAbi<'a, Ty> { Arch::RiscV32 | Arch::RiscV64 => riscv::compute_abi_info(cx, self), Arch::Wasm32 | Arch::Wasm64 => wasm::compute_abi_info(cx, self), Arch::Bpf => bpf::compute_abi_info(cx, self), - arch @ (Arch::PowerPC64LE | Arch::SpirV | Arch::Other(_)) => { + arch @ (Arch::SpirV | Arch::Other(_)) => { panic!("no lowering implemented for {arch}") } } diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 89c9fdc935cc5..57effe3a86689 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1873,7 +1873,6 @@ crate::target_spec_enum! { Nvptx64 = "nvptx64", PowerPC = "powerpc", PowerPC64 = "powerpc64", - PowerPC64LE = "powerpc64le", RiscV32 = "riscv32", RiscV64 = "riscv64", S390x = "s390x", @@ -1911,7 +1910,6 @@ impl Arch { Self::Nvptx64 => sym::nvptx64, Self::PowerPC => sym::powerpc, Self::PowerPC64 => sym::powerpc64, - Self::PowerPC64LE => sym::powerpc64le, Self::RiscV32 => sym::riscv32, Self::RiscV64 => sym::riscv64, Self::S390x => sym::s390x, @@ -1940,8 +1938,8 @@ impl Arch { AArch64 | AmdGpu | Arm | Arm64EC | Avr | CSky | Hexagon | LoongArch32 | LoongArch64 | M68k | Mips | Mips32r6 | Mips64 | Mips64r6 | Msp430 | Nvptx64 | PowerPC - | PowerPC64 | PowerPC64LE | RiscV32 | RiscV64 | S390x | Sparc | Sparc64 | Wasm32 - | Wasm64 | X86 | X86_64 | Xtensa => true, + | PowerPC64 | RiscV32 | RiscV64 | S390x | Sparc | Sparc64 | Wasm32 | Wasm64 | X86 + | X86_64 | Xtensa => true, } } } @@ -3436,7 +3434,6 @@ impl Target { Arch::Arm64EC => (Architecture::Aarch64, Some(object::SubArchitecture::Arm64EC)), Arch::AmdGpu | Arch::Nvptx64 - | Arch::PowerPC64LE | Arch::SpirV | Arch::Wasm32 | Arch::Wasm64 diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index 40cc4f40a3336..4eba426dda59f 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -986,7 +986,6 @@ impl Target { Arch::AmdGpu | Arch::Avr | Arch::Msp430 - | Arch::PowerPC64LE | Arch::SpirV | Arch::Xtensa | Arch::Other(_) => &[], @@ -1015,12 +1014,7 @@ impl Target { Arch::CSky => CSKY_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI, // FIXME: for some tier3 targets, we are overly cautious and always give warnings // when passing args in vector registers. - Arch::Avr - | Arch::Msp430 - | Arch::PowerPC64LE - | Arch::SpirV - | Arch::Xtensa - | Arch::Other(_) => &[], + Arch::Avr | Arch::Msp430 | Arch::SpirV | Arch::Xtensa | Arch::Other(_) => &[], } } diff --git a/src/tools/miri/src/shims/alloc.rs b/src/tools/miri/src/shims/alloc.rs index 94649dde47361..b4d53c36d19b3 100644 --- a/src/tools/miri/src/shims/alloc.rs +++ b/src/tools/miri/src/shims/alloc.rs @@ -52,7 +52,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | Arch::Bpf | Arch::Msp430 | Arch::Nvptx64 - | Arch::PowerPC64LE | Arch::SpirV | Arch::Other(_)) => bug!("unsupported target architecture for malloc: `{arch}`"), }; From c1bcae06384b1e6e0c1ddb25970df5eb77024084 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Mon, 12 Jan 2026 10:45:24 -0500 Subject: [PATCH 22/30] Fix WASI threading regression with minimal invasive change Recent changes made WASI targets use the Unix threading implementation, but WASI does not support threading. When the Unix code tries to call pthread_create, it fails with EAGAIN, causing libraries like rayon to panic when trying to initialize their global thread pool. This fix adds an early return in Thread::new() that checks for WASI and returns UNSUPPORTED_PLATFORM. This approach: - Continues using most of the unix.rs code path (less invasive) - Only requires a small cfg check at the start of Thread::new() - Can be easily removed once wasi-sdk is updated with the proper fix The real fix is being tracked in `WebAssembly/wasi-libc#716` which will change the error code returned by pthread_create to properly indicate unsupported operations. Once that propagates to wasi-sdk, this workaround can be removed. Fixes the regression where rayon-based code (e.g., lopdf in PDF handling) panicked on WASI after nightly-2025-12-10. Before fix: pthread_create returns EAGAIN (error code 6) ThreadPoolBuildError { kind: IOError(Os { code: 6, kind: WouldBlock, message: "Resource temporarily unavailable" }) } After fix: Thread::new returns Err(io::Error::UNSUPPORTED_PLATFORM) Libraries can gracefully handle the lack of threading support --- library/std/src/sys/thread/unix.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index d0396ed713009..f0cfdb956392d 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -45,6 +45,15 @@ impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub unsafe fn new(stack: usize, init: Box) -> io::Result { + // FIXME: remove this block once wasi-sdk is updated with the fix from + // https://github.com/WebAssembly/wasi-libc/pull/716 + // WASI does not support threading via pthreads. While wasi-libc provides + // pthread stubs, pthread_create returns EAGAIN, which causes confusing + // errors. We return UNSUPPORTED_PLATFORM directly instead. + if cfg!(target_os = "wasi") { + return Err(io::Error::UNSUPPORTED_PLATFORM); + } + let data = init; let mut attr: mem::MaybeUninit = mem::MaybeUninit::uninit(); assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0); From 00384df08069939166b91813bca8b3c67d47d850 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:47:11 +0100 Subject: [PATCH 23/30] Delete `MetaItemOrLitParser::Err` --- compiler/rustc_attr_parsing/src/attributes/cfg.rs | 1 - compiler/rustc_attr_parsing/src/attributes/doc.rs | 6 ------ compiler/rustc_attr_parsing/src/parser.rs | 8 +++----- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index ccf0a394afd0e..dcb74dda81af6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -94,7 +94,6 @@ pub fn parse_cfg_entry( LitKind::Bool(b) => CfgEntry::Bool(b, lit.span), _ => return Err(cx.expected_identifier(lit.span)), }, - MetaItemOrLitParser::Err(_, err) => return Err(*err), }) } diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 6cc4ac35eadb1..409102a79c06a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -514,9 +514,6 @@ impl DocParser { MetaItemOrLitParser::Lit(lit) => { cx.unexpected_literal(lit.span); } - MetaItemOrLitParser::Err(..) => { - // already had an error here, move on. - } } } } @@ -600,9 +597,6 @@ impl DocParser { MetaItemOrLitParser::Lit(lit) => { cx.expected_name_value(lit.span, None); } - MetaItemOrLitParser::Err(..) => { - // already had an error here, move on. - } } } } diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 68265649d1823..ebf12dd1dfdec 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -18,7 +18,7 @@ use rustc_parse::exp; use rustc_parse::parser::{ForceCollect, Parser, PathStyle, token_descr}; use rustc_session::errors::{create_lit_error, report_lit_error}; use rustc_session::parse::ParseSess; -use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, sym}; +use rustc_span::{Ident, Span, Symbol, sym}; use thin_vec::ThinVec; use crate::ShouldEmit; @@ -192,7 +192,6 @@ impl ArgParser { pub enum MetaItemOrLitParser { MetaItemParser(MetaItemParser), Lit(MetaItemLit), - Err(Span, ErrorGuaranteed), } impl MetaItemOrLitParser { @@ -210,21 +209,20 @@ impl MetaItemOrLitParser { generic_meta_item_parser.span() } MetaItemOrLitParser::Lit(meta_item_lit) => meta_item_lit.span, - MetaItemOrLitParser::Err(span, _) => *span, } } pub fn lit(&self) -> Option<&MetaItemLit> { match self { MetaItemOrLitParser::Lit(meta_item_lit) => Some(meta_item_lit), - _ => None, + MetaItemOrLitParser::MetaItemParser(_) => None, } } pub fn meta_item(&self) -> Option<&MetaItemParser> { match self { MetaItemOrLitParser::MetaItemParser(parser) => Some(parser), - _ => None, + MetaItemOrLitParser::Lit(_) => None, } } } From 8b52c73b3ec29dadf5df8dc02aeea5aeea778fd7 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sat, 10 Jan 2026 19:21:38 +0300 Subject: [PATCH 24/30] resolve: Relax some asserts in glob overwriting and add tests --- compiler/rustc_resolve/src/imports.rs | 6 ++--- tests/ui/imports/overwrite-deep-glob.rs | 22 ++++++++++++++++ tests/ui/imports/overwrite-deep-glob.stderr | 12 +++++++++ tests/ui/imports/overwrite-different-ambig.rs | 25 +++++++++++++++++++ tests/ui/imports/overwrite-different-vis.rs | 21 ++++++++++++++++ 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 tests/ui/imports/overwrite-deep-glob.rs create mode 100644 tests/ui/imports/overwrite-deep-glob.stderr create mode 100644 tests/ui/imports/overwrite-different-ambig.rs create mode 100644 tests/ui/imports/overwrite-different-vis.rs diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index e525abd00f998..7c0cbcf1d5228 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -300,13 +300,13 @@ fn remove_same_import<'ra>(d1: Decl<'ra>, d2: Decl<'ra>) -> (Decl<'ra>, Decl<'ra if let DeclKind::Import { import: import1, source_decl: d1_next } = d1.kind && let DeclKind::Import { import: import2, source_decl: d2_next } = d2.kind && import1 == import2 - && d1.warn_ambiguity.get() == d2.warn_ambiguity.get() { assert_eq!(d1.ambiguity.get(), d2.ambiguity.get()); - assert!(!d1.warn_ambiguity.get()); assert_eq!(d1.expansion, d2.expansion); assert_eq!(d1.span, d2.span); - assert_eq!(d1.vis(), d2.vis()); + // Visibility of the new import declaration may be different, + // because it already incorporates the visibility of the source binding. + // `warn_ambiguity` of a re-fetched glob can also change in both directions. remove_same_import(d1_next, d2_next) } else { (d1, d2) diff --git a/tests/ui/imports/overwrite-deep-glob.rs b/tests/ui/imports/overwrite-deep-glob.rs new file mode 100644 index 0000000000000..261b22f6e0a07 --- /dev/null +++ b/tests/ui/imports/overwrite-deep-glob.rs @@ -0,0 +1,22 @@ +//@ check-pass + +mod openssl { + pub use self::handwritten::*; + + mod handwritten { + mod m1 { + pub struct S {} + } + mod m2 { + #[derive(Default)] + pub struct S {} + } + + pub use self::m1::*; //~ WARN ambiguous glob re-exports + pub use self::m2::*; + } +} + +pub use openssl::*; + +fn main() {} diff --git a/tests/ui/imports/overwrite-deep-glob.stderr b/tests/ui/imports/overwrite-deep-glob.stderr new file mode 100644 index 0000000000000..093478c57c93a --- /dev/null +++ b/tests/ui/imports/overwrite-deep-glob.stderr @@ -0,0 +1,12 @@ +warning: ambiguous glob re-exports + --> $DIR/overwrite-deep-glob.rs:15:17 + | +LL | pub use self::m1::*; + | ^^^^^^^^^^^ the name `S` in the type namespace is first re-exported here +LL | pub use self::m2::*; + | ----------- but the name `S` in the type namespace is also re-exported here + | + = note: `#[warn(ambiguous_glob_reexports)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/imports/overwrite-different-ambig.rs b/tests/ui/imports/overwrite-different-ambig.rs new file mode 100644 index 0000000000000..9aee63e189731 --- /dev/null +++ b/tests/ui/imports/overwrite-different-ambig.rs @@ -0,0 +1,25 @@ +//@ check-pass +//@ edition:2024 + +mod a { + mod b { + mod c { + pub struct E; + } + mod d { + mod c { + pub struct E; + } + mod d { + #[derive(Debug)] + pub struct E; + } + pub use c::*; + use d::*; + } + use c::*; + use d::*; + } +} + +fn main() {} diff --git a/tests/ui/imports/overwrite-different-vis.rs b/tests/ui/imports/overwrite-different-vis.rs new file mode 100644 index 0000000000000..edcc441bcb77e --- /dev/null +++ b/tests/ui/imports/overwrite-different-vis.rs @@ -0,0 +1,21 @@ +//@ check-pass + +mod b { + pub mod http { + pub struct HeaderMap; + } + + pub(crate) use self::http::*; + #[derive(Debug)] + pub struct HeaderMap; +} + +mod a { + pub use crate::b::*; + + fn check_type() { + let _: HeaderMap = crate::b::HeaderMap; + } +} + +fn main() {} From 81ef42d5b7c4de7e8fa621fff4dc8f4627a581dc Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sun, 11 Jan 2026 01:31:10 +0300 Subject: [PATCH 25/30] resolve: Consistently use old decls before new decls in interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The opposite ordering was a consistent source of confusion during debuggingю `report_conflict` actually used an incorrect order due to similar confusion. --- compiler/rustc_resolve/src/diagnostics.rs | 4 ++-- compiler/rustc_resolve/src/imports.rs | 8 ++++---- tests/ui/hygiene/cross-crate-redefine.rs | 2 +- tests/ui/hygiene/cross-crate-redefine.stderr | 7 +++---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index b80011e8c0cb7..9f13ba1201f7a 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -212,12 +212,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { &mut self, ident: Ident, ns: Namespace, - new_binding: Decl<'ra>, old_binding: Decl<'ra>, + new_binding: Decl<'ra>, ) { // Error on the second of two conflicting names if old_binding.span.lo() > new_binding.span.lo() { - return self.report_conflict(ident, ns, old_binding, new_binding); + return self.report_conflict(ident, ns, new_binding, old_binding); } let container = match old_binding.parent_module.unwrap().kind { diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 7c0cbcf1d5228..c3ea408deb891 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -348,8 +348,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { /// decide which one to keep. fn select_glob_decl( &self, - glob_decl: Decl<'ra>, old_glob_decl: Decl<'ra>, + glob_decl: Decl<'ra>, warn_ambiguity: bool, ) -> Decl<'ra> { assert!(glob_decl.is_glob_import()); @@ -369,7 +369,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // with the re-fetched decls. // This is probably incorrect in corner cases, and the outdated decls still get // propagated to other places and get stuck there, but that's what we have at the moment. - let (deep_decl, old_deep_decl) = remove_same_import(glob_decl, old_glob_decl); + let (old_deep_decl, deep_decl) = remove_same_import(old_glob_decl, glob_decl); if deep_decl != glob_decl { // Some import layers have been removed, need to overwrite. assert_ne!(old_deep_decl, old_glob_decl); @@ -436,7 +436,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { match (old_decl.is_glob_import(), decl.is_glob_import()) { (true, true) => { resolution.glob_decl = - Some(this.select_glob_decl(decl, old_decl, warn_ambiguity)); + Some(this.select_glob_decl(old_decl, decl, warn_ambiguity)); } (old_glob @ true, false) | (old_glob @ false, true) => { let (glob_decl, non_glob_decl) = @@ -446,7 +446,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { && old_glob_decl != glob_decl { resolution.glob_decl = - Some(this.select_glob_decl(glob_decl, old_glob_decl, false)); + Some(this.select_glob_decl(old_glob_decl, glob_decl, false)); } else { resolution.glob_decl = Some(glob_decl); } diff --git a/tests/ui/hygiene/cross-crate-redefine.rs b/tests/ui/hygiene/cross-crate-redefine.rs index e42c5e3de0641..a87c933391d46 100644 --- a/tests/ui/hygiene/cross-crate-redefine.rs +++ b/tests/ui/hygiene/cross-crate-redefine.rs @@ -8,7 +8,7 @@ extern crate use_by_macro; use use_by_macro::*; my_struct!(define); -//~^ ERROR the name `MyStruct` is defined multiple times my_struct!(define); +//~^ ERROR the name `MyStruct` is defined multiple times fn main() {} diff --git a/tests/ui/hygiene/cross-crate-redefine.stderr b/tests/ui/hygiene/cross-crate-redefine.stderr index c0fd3f4b7ebf2..8ad7d6d7b0891 100644 --- a/tests/ui/hygiene/cross-crate-redefine.stderr +++ b/tests/ui/hygiene/cross-crate-redefine.stderr @@ -1,11 +1,10 @@ error[E0428]: the name `MyStruct` is defined multiple times - --> $DIR/cross-crate-redefine.rs:10:1 + --> $DIR/cross-crate-redefine.rs:11:1 | -LL | my_struct!(define); - | ^^^^^^^^^^^^^^^^^^ `MyStruct` redefined here -LL | LL | my_struct!(define); | ------------------ previous definition of the type `MyStruct` here +LL | my_struct!(define); + | ^^^^^^^^^^^^^^^^^^ `MyStruct` redefined here | = note: `MyStruct` must be defined only once in the type namespace of this module = note: this error originates in the macro `my_struct` (in Nightly builds, run with -Z macro-backtrace for more info) From 1c3841b372a248c0e8c405b051050dbef68f1527 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sun, 11 Jan 2026 22:44:37 +0300 Subject: [PATCH 26/30] Add a test for issue 150977 --- .../overwrite-different-warn-ambiguity.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/ui/imports/overwrite-different-warn-ambiguity.rs diff --git a/tests/ui/imports/overwrite-different-warn-ambiguity.rs b/tests/ui/imports/overwrite-different-warn-ambiguity.rs new file mode 100644 index 0000000000000..f843208b37d68 --- /dev/null +++ b/tests/ui/imports/overwrite-different-warn-ambiguity.rs @@ -0,0 +1,28 @@ +//@ check-pass +//@ edition:2024 + +mod framing { + mod public_message_in { + mod public_message { + mod public_message { + pub struct ConfirmedTranscriptHashInput; + } + mod public_message_in { + use super::*; + #[derive(Debug)] + pub struct ConfirmedTranscriptHashInput; + } + pub use public_message::*; + use public_message_in::*; + } + mod public_message_in { + #[derive(Debug)] + pub struct ConfirmedTranscriptHashInput; + } + pub use public_message::*; + use public_message_in::*; + } + use public_message_in::*; +} + +fn main() {} From 83c5f2c19409deb44778bc6ad36a33f3b9ce0f57 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sun, 11 Jan 2026 23:16:42 +0300 Subject: [PATCH 27/30] resolve: Relax one more assert in glob overwriting and add a test Also avoid losing some glob ambiguities when re-fetching globs --- compiler/rustc_resolve/src/imports.rs | 9 +++- .../ui/imports/overwrite-different-ambig-2.rs | 24 +++++++++ .../overwrite-different-ambig-2.stderr | 49 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/ui/imports/overwrite-different-ambig-2.rs create mode 100644 tests/ui/imports/overwrite-different-ambig-2.stderr diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index c3ea408deb891..451779ae32c6b 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -301,9 +301,12 @@ fn remove_same_import<'ra>(d1: Decl<'ra>, d2: Decl<'ra>) -> (Decl<'ra>, Decl<'ra && let DeclKind::Import { import: import2, source_decl: d2_next } = d2.kind && import1 == import2 { - assert_eq!(d1.ambiguity.get(), d2.ambiguity.get()); assert_eq!(d1.expansion, d2.expansion); assert_eq!(d1.span, d2.span); + if d1.ambiguity.get() != d2.ambiguity.get() { + assert!(d1.ambiguity.get().is_some()); + assert!(d2.ambiguity.get().is_none()); + } // Visibility of the new import declaration may be different, // because it already incorporates the visibility of the source binding. // `warn_ambiguity` of a re-fetched glob can also change in both directions. @@ -377,6 +380,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // assert_ne!(old_deep_decl, deep_decl); // assert!(old_deep_decl.is_glob_import()); assert!(!deep_decl.is_glob_import()); + if old_glob_decl.ambiguity.get().is_some() && glob_decl.ambiguity.get().is_none() { + // Do not lose glob ambiguities when re-fetching the glob. + glob_decl.ambiguity.set_unchecked(old_glob_decl.ambiguity.get()); + } if glob_decl.is_ambiguity_recursive() { glob_decl.warn_ambiguity.set_unchecked(true); } diff --git a/tests/ui/imports/overwrite-different-ambig-2.rs b/tests/ui/imports/overwrite-different-ambig-2.rs new file mode 100644 index 0000000000000..1b6d20e24d309 --- /dev/null +++ b/tests/ui/imports/overwrite-different-ambig-2.rs @@ -0,0 +1,24 @@ +mod m1 { + mod inner { + pub struct S {} + } + pub use self::inner::*; + + #[derive(Debug)] + pub struct S {} +} + +mod m2 { + pub struct S {} +} + +// First we have a glob ambiguity in this glob (with `m2::*`). +// Then we re-fetch `m1::*` because non-glob `m1::S` materializes from derive, +// and we need to make sure that the glob ambiguity is not lost during re-fetching. +use m1::*; +use m2::*; + +fn main() { + let _: m1::S = S {}; //~ ERROR `S` is ambiguous + //~| WARN this was previously accepted +} diff --git a/tests/ui/imports/overwrite-different-ambig-2.stderr b/tests/ui/imports/overwrite-different-ambig-2.stderr new file mode 100644 index 0000000000000..e75f552d119c1 --- /dev/null +++ b/tests/ui/imports/overwrite-different-ambig-2.stderr @@ -0,0 +1,49 @@ +error: `S` is ambiguous + --> $DIR/overwrite-different-ambig-2.rs:22:20 + | +LL | let _: m1::S = S {}; + | ^ ambiguous name + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #114095 + = note: ambiguous because of multiple glob imports of a name in the same module +note: `S` could refer to the struct imported here + --> $DIR/overwrite-different-ambig-2.rs:18:5 + | +LL | use m1::*; + | ^^^^^ + = help: consider adding an explicit import of `S` to disambiguate +note: `S` could also refer to the struct imported here + --> $DIR/overwrite-different-ambig-2.rs:19:5 + | +LL | use m2::*; + | ^^^^^ + = help: consider adding an explicit import of `S` to disambiguate + = note: `#[deny(ambiguous_glob_imports)]` (part of `#[deny(future_incompatible)]`) on by default + +error: aborting due to 1 previous error + +Future incompatibility report: Future breakage diagnostic: +error: `S` is ambiguous + --> $DIR/overwrite-different-ambig-2.rs:22:20 + | +LL | let _: m1::S = S {}; + | ^ ambiguous name + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #114095 + = note: ambiguous because of multiple glob imports of a name in the same module +note: `S` could refer to the struct imported here + --> $DIR/overwrite-different-ambig-2.rs:18:5 + | +LL | use m1::*; + | ^^^^^ + = help: consider adding an explicit import of `S` to disambiguate +note: `S` could also refer to the struct imported here + --> $DIR/overwrite-different-ambig-2.rs:19:5 + | +LL | use m2::*; + | ^^^^^ + = help: consider adding an explicit import of `S` to disambiguate + = note: `#[deny(ambiguous_glob_imports)]` (part of `#[deny(future_incompatible)]`) on by default + From 9f1cf9efe0eaa25e76243e23a695b9a042c16c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 18:01:50 +0100 Subject: [PATCH 28/30] Add temporary new bors e-mail address to the mailmap To match it to bors in thanks. --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 4c254b396b538..948f1ab14fdea 100644 --- a/.mailmap +++ b/.mailmap @@ -96,6 +96,7 @@ boolean_coercion Boris Egorov bors bors[bot] <26634292+bors[bot]@users.noreply.github.com> bors bors[bot] +bors <122020455+rust-bors[bot]@users.noreply.github.com> BoxyUwU BoxyUwU Braden Nelson From 92db7b2b5ab89a37a2d7bdd9c8a25fd8be31e303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 14 Jan 2026 02:22:54 +0000 Subject: [PATCH 29/30] Recover parse gracefully from `` When a const param doesn't have a `: Type`, recover the parser state and provide a structured suggestion. This not only provides guidance on what was missing, but it also makes subsuequent errors to be emitted that would otherwise be silenced. ``` error: expected `:`, found `>` --> $DIR/incorrect-const-param.rs:26:16 | LL | impl From<[T; N]> for VecWrapper | ^ expected `:` | help: you might have meant to write the type of the const parameter here | LL | impl From<[T; N]> for VecWrapper | ++++++++++++ ``` --- compiler/rustc_parse/src/parser/generics.rs | 26 ++++++- .../const-generics/incorrect-const-param.rs | 45 ++++++++++++ .../incorrect-const-param.stderr | 70 +++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 tests/ui/const-generics/incorrect-const-param.rs create mode 100644 tests/ui/const-generics/incorrect-const-param.stderr diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 2c76c0fe3583c..ef6c9cc344cea 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -109,7 +109,31 @@ impl<'a> Parser<'a> { self.expect_keyword(exp!(Const))?; let ident = self.parse_ident()?; - self.expect(exp!(Colon))?; + if let Err(mut err) = self.expect(exp!(Colon)) { + return if self.token.kind == token::Comma || self.token.kind == token::Gt { + // Recover parse from `` where the type is missing. + let span = const_span.to(ident.span); + err.span_suggestion_verbose( + ident.span.shrink_to_hi(), + "you likely meant to write the type of the const parameter here", + ": /* Type */".to_string(), + Applicability::HasPlaceholders, + ); + let kind = TyKind::Err(err.emit()); + let ty = self.mk_ty(span, kind); + Ok(GenericParam { + ident, + id: ast::DUMMY_NODE_ID, + attrs: preceding_attrs, + bounds: Vec::new(), + kind: GenericParamKind::Const { ty, span, default: None }, + is_placeholder: false, + colon_span: None, + }) + } else { + Err(err) + }; + } let ty = self.parse_ty()?; // Parse optional const generics default value. diff --git a/tests/ui/const-generics/incorrect-const-param.rs b/tests/ui/const-generics/incorrect-const-param.rs new file mode 100644 index 0000000000000..5f1d8ca2ae990 --- /dev/null +++ b/tests/ui/const-generics/incorrect-const-param.rs @@ -0,0 +1,45 @@ +// #84327 + +struct VecWrapper(Vec); + +// Correct +impl From<[T; N]> for VecWrapper +where + T: Clone, +{ + fn from(slice: [T; N]) -> Self { + VecWrapper(slice.to_vec()) + } +} + +// Forgot const +impl From<[T; N]> for VecWrapper //~ ERROR expected value, found type parameter `N` +where //~^ ERROR expected trait, found builtin type `usize` + T: Clone, +{ + fn from(slice: [T; N]) -> Self { //~ ERROR expected value, found type parameter `N` + VecWrapper(slice.to_vec()) + } +} + +// Forgot type +impl From<[T; N]> for VecWrapper //~ ERROR expected `:`, found `>` +where + T: Clone, +{ + fn from(slice: [T; N]) -> Self { + VecWrapper(slice.to_vec()) + } +} + +// Forgot const and type +impl From<[T; N]> for VecWrapper //~ ERROR expected value, found type parameter `N` +where + T: Clone, +{ + fn from(slice: [T; N]) -> Self { //~ ERROR expected value, found type parameter `N` + VecWrapper(slice.to_vec()) + } +} + +fn main() {} diff --git a/tests/ui/const-generics/incorrect-const-param.stderr b/tests/ui/const-generics/incorrect-const-param.stderr new file mode 100644 index 0000000000000..c5cf54500ee01 --- /dev/null +++ b/tests/ui/const-generics/incorrect-const-param.stderr @@ -0,0 +1,70 @@ +error: expected `:`, found `>` + --> $DIR/incorrect-const-param.rs:26:16 + | +LL | impl From<[T; N]> for VecWrapper + | ^ expected `:` + | +help: you likely meant to write the type of the const parameter here + | +LL | impl From<[T; N]> for VecWrapper + | ++++++++++++ + +error[E0423]: expected value, found type parameter `N` + --> $DIR/incorrect-const-param.rs:16:28 + | +LL | impl From<[T; N]> for VecWrapper + | - ^ not a value + | | + | found this type parameter + +error[E0404]: expected trait, found builtin type `usize` + --> $DIR/incorrect-const-param.rs:16:12 + | +LL | impl From<[T; N]> for VecWrapper + | ^^^^^ not a trait + | +help: you might have meant to write a const parameter here + | +LL | impl From<[T; N]> for VecWrapper + | +++++ + +error[E0423]: expected value, found type parameter `N` + --> $DIR/incorrect-const-param.rs:20:24 + | +LL | impl From<[T; N]> for VecWrapper + | - found this type parameter +... +LL | fn from(slice: [T; N]) -> Self { + | ^ not a value + +error[E0423]: expected value, found type parameter `N` + --> $DIR/incorrect-const-param.rs:36:21 + | +LL | impl From<[T; N]> for VecWrapper + | - ^ not a value + | | + | found this type parameter + | +help: you might have meant to write a const parameter here + | +LL | impl From<[T; N]> for VecWrapper + | +++++ ++++++++++++ + +error[E0423]: expected value, found type parameter `N` + --> $DIR/incorrect-const-param.rs:40:24 + | +LL | impl From<[T; N]> for VecWrapper + | - found this type parameter +... +LL | fn from(slice: [T; N]) -> Self { + | ^ not a value + | +help: you might have meant to write a const parameter here + | +LL | impl From<[T; N]> for VecWrapper + | +++++ ++++++++++++ + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0404, E0423. +For more information about an error, try `rustc --explain E0404`. From 436fb87eeb833f8986ca520311e91f32ed0c32e2 Mon Sep 17 00:00:00 2001 From: Zijie Zhao Date: Wed, 14 Jan 2026 14:20:45 -0600 Subject: [PATCH 30/30] Rename `rust.use-lld` to `rust.bootstrap-override-lld` in INSTALL.md --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 98eb825cd10f6..dd6c64b3df5f6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -97,7 +97,7 @@ See [the rustc-dev-guide for more info][sysllvm]. --set llvm.ninja=false \ --set rust.debug-assertions=false \ --set rust.jemalloc \ - --set rust.use-lld=true \ + --set rust.bootstrap-override-lld=true \ --set rust.lto=thin \ --set rust.codegen-units=1 ```