From fc157f65cb3c18367db5d7e0fc01b96ea4f40b86 Mon Sep 17 00:00:00 2001 From: LaneAsade Date: Fri, 27 Mar 2026 15:23:38 +0530 Subject: [PATCH 1/6] Deduplication: Pulled common logic out from lower_const_arg_struct --- .../src/hir_ty_lowering/mod.rs | 72 ++++++++++++++----- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 37 +++------- 2 files changed, 64 insertions(+), 45 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 924967b65c193..ee46b028ba41f 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -120,6 +120,11 @@ pub struct InherentAssocCandidate { pub scope: DefId, } +pub struct ResolvedStructPath<'tcx> { + pub res: Result, + pub ty: Ty<'tcx>, +} + /// A context which can lower type-system entities from the [HIR][hir] to /// the [`rustc_middle::ty`] representation. /// @@ -2558,38 +2563,36 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ty::Const::new_error(tcx, e) }; - let (ty, variant_did) = match qpath { + let ResolvedStructPath { res: opt_res, ty } = + self.lower_path_for_struct_expr(qpath, span, hir_id); + + let variant_did = match qpath { hir::QPath::Resolved(maybe_qself, path) => { debug!(?maybe_qself, ?path); - let opt_self_ty = maybe_qself.as_ref().map(|qself| self.lower_ty(qself)); - let ty = - self.lower_resolved_ty_path(opt_self_ty, path, hir_id, PermitVariants::Yes); let variant_did = match path.res { Res::Def(DefKind::Variant | DefKind::Struct, did) => did, _ => return non_adt_or_variant_res(), }; - (ty, variant_did) + variant_did } hir::QPath::TypeRelative(hir_self_ty, segment) => { debug!(?hir_self_ty, ?segment); - let self_ty = self.lower_ty(hir_self_ty); - let opt_res = self.lower_type_relative_ty_path( - self_ty, - hir_self_ty, - segment, - hir_id, - span, - PermitVariants::Yes, - ); - let (ty, _, res_def_id) = match opt_res { - Ok(r @ (_, DefKind::Variant | DefKind::Struct, _)) => r, + let res_def_id = match opt_res { + Ok(r) + if matches!( + tcx.def_kind(r.def_id()), + DefKind::Variant | DefKind::Struct + ) => + { + r.def_id() + } Ok(_) => return non_adt_or_variant_res(), Err(e) => return ty::Const::new_error(tcx, e), }; - (ty, res_def_id) + res_def_id } }; @@ -2650,6 +2653,41 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ty::Const::new_value(tcx, valtree, ty) } + pub fn lower_path_for_struct_expr( + &self, + qpath: hir::QPath<'tcx>, + path_span: Span, + hir_id: HirId, + ) -> ResolvedStructPath<'tcx> { + match qpath { + hir::QPath::Resolved(ref maybe_qself, path) => { + let self_ty = maybe_qself.as_ref().map(|qself| self.lower_ty(qself)); + let ty = self.lower_resolved_ty_path(self_ty, path, hir_id, PermitVariants::Yes); + ResolvedStructPath { res: Ok(path.res), ty } + } + hir::QPath::TypeRelative(hir_self_ty, segment) => { + let self_ty = self.lower_ty(hir_self_ty); + + let result = self.lower_type_relative_ty_path( + self_ty, + hir_self_ty, + segment, + hir_id, + path_span, + PermitVariants::Yes, + ); + let ty = result + .map(|(ty, _, _)| ty) + .unwrap_or_else(|guar| Ty::new_error(self.tcx(), guar)); + + ResolvedStructPath { + res: result.map(|(_, kind, def_id)| Res::Def(kind, def_id)), + ty, + } + } + } + } + /// Lower a [resolved][hir::QPath::Resolved] path to a (type-level) constant. fn lower_resolved_const_path( &self, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index c07cbfae256dd..9f6ec4592b094 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -10,7 +10,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::intravisit::Visitor; use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath, is_range_literal}; use rustc_hir_analysis::check::potentially_plural_count; -use rustc_hir_analysis::hir_ty_lowering::{HirTyLowerer, PermitVariants}; +use rustc_hir_analysis::hir_ty_lowering::{HirTyLowerer, ResolvedStructPath}; use rustc_index::IndexVec; use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferOk, TypeTrace}; use rustc_middle::ty::adjustment::AllowTwoPhase; @@ -1285,38 +1285,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { path_span: Span, hir_id: HirId, ) -> (Res, LoweredTy<'tcx>) { + let ResolvedStructPath { res: result, ty } = + self.lowerer().lower_path_for_struct_expr(*qpath, path_span, hir_id); match *qpath { - QPath::Resolved(ref maybe_qself, path) => { - let self_ty = maybe_qself.as_ref().map(|qself| self.lower_ty(qself).raw); - let ty = self.lowerer().lower_resolved_ty_path( - self_ty, - path, - hir_id, - PermitVariants::Yes, - ); - (path.res, LoweredTy::from_raw(self, path_span, ty)) - } - QPath::TypeRelative(hir_self_ty, segment) => { - let self_ty = self.lower_ty(hir_self_ty); - - let result = self.lowerer().lower_type_relative_ty_path( - self_ty.raw, - hir_self_ty, - segment, - hir_id, - path_span, - PermitVariants::Yes, - ); - let ty = result - .map(|(ty, _, _)| ty) - .unwrap_or_else(|guar| Ty::new_error(self.tcx(), guar)); + QPath::Resolved(_, path) => (path.res, LoweredTy::from_raw(self, path_span, ty)), + QPath::TypeRelative(_, _) => { let ty = LoweredTy::from_raw(self, path_span, ty); - let result = result.map(|(_, kind, def_id)| (kind, def_id)); + let resolution = + result.map(|res: Res| (self.tcx().def_kind(res.def_id()), res.def_id())); // Write back the new resolution. - self.write_resolution(hir_id, result); + self.write_resolution(hir_id, resolution); - (result.map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)), ty) + (result.unwrap_or(Res::Err), ty) } } } From dfa2e249154f2c57bedf5d50e549e32f1c87d6fc Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:49:45 +0200 Subject: [PATCH 2/6] Enable diagnostic::on_const for local impls --- .../traits/fulfillment_errors.rs | 70 +++++++++---------- .../on_const/auxiliary/const_trait.rs | 10 --- .../on_const/auxiliary/non_const_impl.rs | 15 ++++ .../on_const/it_works_foreign.rs | 15 ++++ .../on_const/it_works_foreign.stderr | 22 ++++++ .../on_const/it_works_local.rs | 25 +++++++ .../on_const/it_works_local.stderr | 15 ++++ 7 files changed, 124 insertions(+), 48 deletions(-) delete mode 100644 tests/ui/diagnostic_namespace/on_const/auxiliary/const_trait.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/auxiliary/non_const_impl.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/it_works_foreign.stderr create mode 100644 tests/ui/diagnostic_namespace/on_const/it_works_local.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/it_works_local.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 3d7db3ed25b0a..3254496cf0e96 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -882,55 +882,49 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let trait_name = self.tcx.item_name(trait_did); if self.tcx.is_const_trait(trait_did) && !self.tcx.is_const_trait_impl(impl_did) { - if let Some(impl_did) = impl_did.as_local() - && let item = self.tcx.hir_expect_item(impl_did) - && let hir::ItemKind::Impl(item) = item.kind - && let Some(of_trait) = item.of_trait - { - // trait is const, impl is local and not const - diag.span_suggestion_verbose( - of_trait.trait_ref.path.span.shrink_to_lo(), - format!("make the `impl` of trait `{trait_name}` `const`"), - "const ".to_string(), - Applicability::MaybeIncorrect, - ); - } else { + if !impl_did.is_local() { diag.span_note( impl_span, format!("trait `{trait_name}` is implemented but not `const`"), ); + } - let (condition_options, format_args) = self.on_unimplemented_components( + if let Some(command) = + find_attr!(self.tcx, impl_did, OnConst {directive, ..} => directive.as_deref()) + .flatten() + { + let (_, format_args) = self.on_unimplemented_components( trait_ref, main_obligation, diag.long_ty_path(), ); + let CustomDiagnostic { message, label, notes, parent_label } = + command.eval(None, &format_args); - if let Some(command) = find_attr!(self.tcx, impl_did, OnConst {directive, ..} => directive.as_deref()).flatten(){ - let note = command.eval( - Some(&condition_options), - &format_args, - ); - let CustomDiagnostic { - message, - label, - notes, - parent_label, - } = note; - - if let Some(message) = message { - diag.primary_message(message); - } - if let Some(label) = label { - diag.span_label(impl_span, label); - } - for note in notes { - diag.note(note); - } - if let Some(parent_label) = parent_label { - diag.span_label(impl_span, parent_label); - } + if let Some(message) = message { + diag.primary_message(message); + } + if let Some(label) = label { + diag.span_label(impl_span, label); + } + for note in notes { + diag.note(note); } + if let Some(parent_label) = parent_label { + diag.span_label(impl_span, parent_label); + } + } else if let Some(impl_did) = impl_did.as_local() + && let item = self.tcx.hir_expect_item(impl_did) + && let hir::ItemKind::Impl(item) = item.kind + && let Some(of_trait) = item.of_trait + { + // trait is const, impl is local and not const + diag.span_suggestion_verbose( + of_trait.trait_ref.path.span.shrink_to_lo(), + format!("make the `impl` of trait `{trait_name}` `const`"), + "const ".to_string(), + Applicability::MaybeIncorrect, + ); } } } diff --git a/tests/ui/diagnostic_namespace/on_const/auxiliary/const_trait.rs b/tests/ui/diagnostic_namespace/on_const/auxiliary/const_trait.rs deleted file mode 100644 index cf854a9072a6a..0000000000000 --- a/tests/ui/diagnostic_namespace/on_const/auxiliary/const_trait.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![feature(diagnostic_on_const)] - -pub struct X; - -#[diagnostic::on_const(message = "message", label = "label", note = "note")] -impl PartialEq for X { - fn eq(&self, _other: &X) -> bool { - true - } -} diff --git a/tests/ui/diagnostic_namespace/on_const/auxiliary/non_const_impl.rs b/tests/ui/diagnostic_namespace/on_const/auxiliary/non_const_impl.rs new file mode 100644 index 0000000000000..3501d0ee9ec57 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/auxiliary/non_const_impl.rs @@ -0,0 +1,15 @@ +#![feature(diagnostic_on_const)] + +pub struct X; + +#[diagnostic::on_const( + message = "their message", + label = "their label", + note = "their note", + note = "their other note" +)] +impl PartialEq for X { + fn eq(&self, _other: &X) -> bool { + true + } +} diff --git a/tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs b/tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs new file mode 100644 index 0000000000000..a56487e71de08 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs @@ -0,0 +1,15 @@ +//@ aux-build: non_const_impl.rs +#![crate_type = "lib"] + +extern crate non_const_impl; + +use non_const_impl::X; + +const _: () = { + let x = X; + x == x; + //~^ ERROR: their message + //~| NOTE: trait `PartialEq` is implemented but not `const` + //~| NOTE: their note + //~| NOTE: their other note +}; diff --git a/tests/ui/diagnostic_namespace/on_const/it_works_foreign.stderr b/tests/ui/diagnostic_namespace/on_const/it_works_foreign.stderr new file mode 100644 index 0000000000000..12d0472443d35 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/it_works_foreign.stderr @@ -0,0 +1,22 @@ +error[E0277]: their message + --> $DIR/it_works_foreign.rs:10:5 + | +LL | x == x; + | ^^^^^^ + | + ::: $DIR/auxiliary/non_const_impl.rs:11:1 + | +LL | impl PartialEq for X { + | -------------------- their label + | +note: trait `PartialEq` is implemented but not `const` + --> $DIR/auxiliary/non_const_impl.rs:11:1 + | +LL | impl PartialEq for X { + | ^^^^^^^^^^^^^^^^^^^^ + = note: their note + = note: their other note + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_const/it_works_local.rs b/tests/ui/diagnostic_namespace/on_const/it_works_local.rs new file mode 100644 index 0000000000000..9e7feb7330d46 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/it_works_local.rs @@ -0,0 +1,25 @@ +#![crate_type = "lib"] +#![feature(diagnostic_on_const)] + +pub struct X; + +#[diagnostic::on_const( + message = "my message", + label = "my label", + note = "my note", + note = "my other note" +)] +impl PartialEq for X { + //~^NOTE: my label + fn eq(&self, _other: &X) -> bool { + true + } +} + +const _: () = { + let x = X; + x == x; + //~^ ERROR: my message + //~| NOTE: my note + //~| NOTE: my other note +}; diff --git a/tests/ui/diagnostic_namespace/on_const/it_works_local.stderr b/tests/ui/diagnostic_namespace/on_const/it_works_local.stderr new file mode 100644 index 0000000000000..14a3d446e1925 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/it_works_local.stderr @@ -0,0 +1,15 @@ +error[E0277]: my message + --> $DIR/it_works_local.rs:21:5 + | +LL | impl PartialEq for X { + | -------------------- my label +... +LL | x == x; + | ^^^^^^ + | + = note: my note + = note: my other note + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From 14ab39c42f6e990d7b41b34e1d3220ab21b6b29a Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:05:35 +0200 Subject: [PATCH 3/6] on_const doesn't support parent_label --- .../src/error_reporting/traits/fulfillment_errors.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 3254496cf0e96..2ab907de7e2d8 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -898,7 +898,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { main_obligation, diag.long_ty_path(), ); - let CustomDiagnostic { message, label, notes, parent_label } = + let CustomDiagnostic { message, label, notes, parent_label: _ } = command.eval(None, &format_args); if let Some(message) = message { @@ -910,9 +910,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { for note in notes { diag.note(note); } - if let Some(parent_label) = parent_label { - diag.span_label(impl_span, parent_label); - } } else if let Some(impl_did) = impl_did.as_local() && let item = self.tcx.hir_expect_item(impl_did) && let hir::ItemKind::Impl(item) = item.kind From 4bc2a5510871ef17eaf9b7d28714f0dee4f48316 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:01:10 +0200 Subject: [PATCH 4/6] Fix diagnostic::on_const label span --- .../src/error_reporting/traits/fulfillment_errors.rs | 2 +- tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs | 1 + .../diagnostic_namespace/on_const/it_works_foreign.stderr | 7 +------ tests/ui/diagnostic_namespace/on_const/it_works_local.rs | 2 +- .../ui/diagnostic_namespace/on_const/it_works_local.stderr | 7 ++----- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 2ab907de7e2d8..a376ce2d30acb 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -905,7 +905,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { diag.primary_message(message); } if let Some(label) = label { - diag.span_label(impl_span, label); + diag.span_label(span, label); } for note in notes { diag.note(note); diff --git a/tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs b/tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs index a56487e71de08..0888ee096ff4f 100644 --- a/tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs +++ b/tests/ui/diagnostic_namespace/on_const/it_works_foreign.rs @@ -9,6 +9,7 @@ const _: () = { let x = X; x == x; //~^ ERROR: their message + //~| NOTE: their label //~| NOTE: trait `PartialEq` is implemented but not `const` //~| NOTE: their note //~| NOTE: their other note diff --git a/tests/ui/diagnostic_namespace/on_const/it_works_foreign.stderr b/tests/ui/diagnostic_namespace/on_const/it_works_foreign.stderr index 12d0472443d35..ad9b2364f21f3 100644 --- a/tests/ui/diagnostic_namespace/on_const/it_works_foreign.stderr +++ b/tests/ui/diagnostic_namespace/on_const/it_works_foreign.stderr @@ -2,12 +2,7 @@ error[E0277]: their message --> $DIR/it_works_foreign.rs:10:5 | LL | x == x; - | ^^^^^^ - | - ::: $DIR/auxiliary/non_const_impl.rs:11:1 - | -LL | impl PartialEq for X { - | -------------------- their label + | ^^^^^^ their label | note: trait `PartialEq` is implemented but not `const` --> $DIR/auxiliary/non_const_impl.rs:11:1 diff --git a/tests/ui/diagnostic_namespace/on_const/it_works_local.rs b/tests/ui/diagnostic_namespace/on_const/it_works_local.rs index 9e7feb7330d46..176fe8d01d353 100644 --- a/tests/ui/diagnostic_namespace/on_const/it_works_local.rs +++ b/tests/ui/diagnostic_namespace/on_const/it_works_local.rs @@ -10,7 +10,6 @@ pub struct X; note = "my other note" )] impl PartialEq for X { - //~^NOTE: my label fn eq(&self, _other: &X) -> bool { true } @@ -20,6 +19,7 @@ const _: () = { let x = X; x == x; //~^ ERROR: my message + //~| NOTE: my label //~| NOTE: my note //~| NOTE: my other note }; diff --git a/tests/ui/diagnostic_namespace/on_const/it_works_local.stderr b/tests/ui/diagnostic_namespace/on_const/it_works_local.stderr index 14a3d446e1925..0db90fac1639e 100644 --- a/tests/ui/diagnostic_namespace/on_const/it_works_local.stderr +++ b/tests/ui/diagnostic_namespace/on_const/it_works_local.stderr @@ -1,11 +1,8 @@ error[E0277]: my message - --> $DIR/it_works_local.rs:21:5 + --> $DIR/it_works_local.rs:20:5 | -LL | impl PartialEq for X { - | -------------------- my label -... LL | x == x; - | ^^^^^^ + | ^^^^^^ my label | = note: my note = note: my other note From 92ed4a21ffd70fd453c0882a31a3aee61df47da6 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Wed, 1 Apr 2026 15:06:06 +0200 Subject: [PATCH 5/6] Introduce #[diagnostic::on_move] on `Rc` This annotates the `Rc` type with the diagnostic attribute `#[diagnostic::on_move]`. Now when a moved `Rc` is borrowed, a suggestion to clone it is made, with a label explaining why. --- library/alloc/src/rc.rs | 6 ++++++ tests/ui/moves/move-fn-self-receiver.rs | 2 +- tests/ui/moves/move-fn-self-receiver.stderr | 5 +++-- tests/ui/moves/use_of_moved_value_clone_suggestions.rs | 2 +- tests/ui/moves/use_of_moved_value_clone_suggestions.stderr | 5 +++-- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index cbb801bd6d736..af76fddaced7c 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -311,6 +311,12 @@ fn rc_inner_layout_for_value_layout(layout: Layout) -> Layout { #[rustc_diagnostic_item = "Rc"] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_insignificant_dtor] +#[diagnostic::on_move( + message = "the type `{Self}` does not implement `Copy`", + label = "this move could be avoided by cloning the original `{Self}`, which is inexpensive", + note = "consider using `Rc::clone`" +)] + pub struct Rc< T: ?Sized, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, diff --git a/tests/ui/moves/move-fn-self-receiver.rs b/tests/ui/moves/move-fn-self-receiver.rs index 946642ef6f3ad..5079ae66ee14d 100644 --- a/tests/ui/moves/move-fn-self-receiver.rs +++ b/tests/ui/moves/move-fn-self-receiver.rs @@ -52,7 +52,7 @@ fn move_out(val: Container) { let rc_foo = Rc::new(Foo); rc_foo.use_rc_self(); - rc_foo; //~ ERROR use of moved + rc_foo; //~ ERROR the type `Rc` does not implement `Copy` let foo_add = Foo; foo_add + Foo; diff --git a/tests/ui/moves/move-fn-self-receiver.stderr b/tests/ui/moves/move-fn-self-receiver.stderr index de19a99d388ca..40a82523c840c 100644 --- a/tests/ui/moves/move-fn-self-receiver.stderr +++ b/tests/ui/moves/move-fn-self-receiver.stderr @@ -102,16 +102,17 @@ LL | mut_foo; LL | ret; | --- borrow later used here -error[E0382]: use of moved value: `rc_foo` +error[E0382]: the type `Rc` does not implement `Copy` --> $DIR/move-fn-self-receiver.rs:55:5 | LL | let rc_foo = Rc::new(Foo); - | ------ move occurs because `rc_foo` has type `Rc`, which does not implement the `Copy` trait + | ------ this move could be avoided by cloning the original `Rc`, which is inexpensive LL | rc_foo.use_rc_self(); | ------------- `rc_foo` moved due to this method call LL | rc_foo; | ^^^^^^ value used here after move | + = note: consider using `Rc::clone` note: `Foo::use_rc_self` takes ownership of the receiver `self`, which moves `rc_foo` --> $DIR/move-fn-self-receiver.rs:16:20 | diff --git a/tests/ui/moves/use_of_moved_value_clone_suggestions.rs b/tests/ui/moves/use_of_moved_value_clone_suggestions.rs index 9a0a397a73387..b31fe507b887a 100644 --- a/tests/ui/moves/use_of_moved_value_clone_suggestions.rs +++ b/tests/ui/moves/use_of_moved_value_clone_suggestions.rs @@ -1,6 +1,6 @@ // `Rc` is not ever `Copy`, we should not suggest adding `T: Copy` constraint fn duplicate_rc(t: std::rc::Rc) -> (std::rc::Rc, std::rc::Rc) { - (t, t) //~ ERROR use of moved value: `t` + (t, t) //~ ERROR the type `Rc` does not implement `Copy` } fn main() {} diff --git a/tests/ui/moves/use_of_moved_value_clone_suggestions.stderr b/tests/ui/moves/use_of_moved_value_clone_suggestions.stderr index 785329565eb9b..a9e554b6e1946 100644 --- a/tests/ui/moves/use_of_moved_value_clone_suggestions.stderr +++ b/tests/ui/moves/use_of_moved_value_clone_suggestions.stderr @@ -1,13 +1,14 @@ -error[E0382]: use of moved value: `t` +error[E0382]: the type `Rc` does not implement `Copy` --> $DIR/use_of_moved_value_clone_suggestions.rs:3:9 | LL | fn duplicate_rc(t: std::rc::Rc) -> (std::rc::Rc, std::rc::Rc) { - | - move occurs because `t` has type `Rc`, which does not implement the `Copy` trait + | - this move could be avoided by cloning the original `Rc`, which is inexpensive LL | (t, t) | - ^ value used here after move | | | value moved here | + = note: consider using `Rc::clone` help: clone the value to increment its reference count | LL | (t.clone(), t) From f8d3c27650f668f575349ed38a1341826e9f29ba Mon Sep 17 00:00:00 2001 From: Shivendra Sharma Date: Mon, 6 Apr 2026 21:48:47 +0530 Subject: [PATCH 6/6] rustdoc: Inherit inline attributes for declarative macros When explicitly re-exporting a declarative macro by name, rustdoc previously bypassed intermediate re-exports and dropped `#[doc(inline)]` attributes, causing the macro to be incorrectly stripped if the original definition was `#[doc(hidden)]`. This updates `generate_item_with_correct_attrs` to walk the `reexport_chain` specifically for declarative macros, allowing them to inherit inline attributes exactly as glob imports do, while preserving strict visibility rules for standard items. --- src/librustdoc/clean/mod.rs | 32 ++++- src/librustdoc/visit_ast.rs | 20 ++- tests/rustdoc-html/macro-reexport-inline.rs | 129 ++++++++++++++++++++ 3 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 tests/rustdoc-html/macro-reexport-inline.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index d6dae29c932e0..cda2ea532c377 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -176,6 +176,35 @@ fn is_glob_import(tcx: TyCtxt<'_>, import_id: LocalDefId) -> bool { } } +/// Returns true if `def_id` is a macro and should be inlined. +pub(crate) fn macro_reexport_is_inline( + tcx: TyCtxt<'_>, + import_id: LocalDefId, + def_id: DefId, +) -> bool { + if !matches!(tcx.def_kind(def_id), DefKind::Macro(MacroKinds::BANG)) { + return false; + } + + for reexport_def_id in reexport_chain(tcx, import_id, def_id).iter().flat_map(|r| r.id()) { + let is_hidden = tcx.is_doc_hidden(reexport_def_id); + let is_inline = find_attr!( + inline::load_attrs(tcx, reexport_def_id), + Doc(d) + if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) + ); + + // hidden takes absolute priority over inline on the same node + if is_hidden { + return false; + } + if is_inline { + return true; + } + } + false +} + fn generate_item_with_correct_attrs( cx: &mut DocContext<'_>, kind: ItemKind, @@ -201,7 +230,8 @@ fn generate_item_with_correct_attrs( Doc(d) if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) ) || (is_glob_import(tcx, import_id) - && (cx.document_hidden() || !tcx.is_doc_hidden(def_id))); + && (cx.document_hidden() || !tcx.is_doc_hidden(def_id))) + || macro_reexport_is_inline(tcx, import_id, def_id); attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline)); is_inline = is_inline || import_is_inline; } diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 906289ba755e3..fda03563c79f2 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -478,11 +478,21 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // If there was a private module in the current path then don't bother inlining // anything as it will probably be stripped anyway. if is_pub && self.inside_public_path { - let please_inline = find_attr!( - attrs, - Doc(d) - if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) - ); + let please_inline = if let Some(res_did) = res.opt_def_id() + && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG)) + { + crate::clean::macro_reexport_is_inline( + tcx, + item.owner_id.def_id, + res_did, + ) + } else { + find_attr!( + attrs, + Doc(d) + if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) + ) + }; let ident = match kind { hir::UseKind::Single(ident) => Some(ident.name), hir::UseKind::Glob => None, diff --git a/tests/rustdoc-html/macro-reexport-inline.rs b/tests/rustdoc-html/macro-reexport-inline.rs new file mode 100644 index 0000000000000..4707d958f8c97 --- /dev/null +++ b/tests/rustdoc-html/macro-reexport-inline.rs @@ -0,0 +1,129 @@ +// Regression test for . +// The goal is to ensure that declarative macros re-exported by name +// inherit the `#[doc(inline)]` attribute from intermediate re-exports, +// matching the behavior of glob re-exports. + +#![crate_name = "foo"] + +#[macro_use] +mod macros { + #[macro_export] + #[doc(hidden)] + macro_rules! explicit_macro { + () => {}; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! wild_macro { + () => {}; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! actually_hidden_macro { + () => {}; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! actually_hidden_wild_macro { + () => {}; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! actually_hidden_indirect_macro { + () => {}; + } +} + +// Standard items (like structs) are provided as control cases to ensure +// macro inlining behavior maintains parity. +#[doc(hidden)] +pub struct HiddenStruct; + +#[doc(hidden)] +pub struct IndirectlyHiddenStruct; + +pub mod bar { + mod hidden_explicit { + #[doc(inline)] + pub use crate::explicit_macro; + } + + mod hidden_wild { + #[doc(inline)] + pub use crate::wild_macro; + } + + mod actually_hidden { + // BUG: as demonstrated by the `actually_hidden_struct` module, when both + // `doc(hidden)` and `doc(inline)` are specified, `doc(hidden)` + // should take priority. + #[doc(hidden)] + #[doc(inline)] + pub use crate::actually_hidden_macro; + } + + mod actually_hidden_indirect_inner { + #[doc(inline)] + pub use crate::actually_hidden_indirect_macro; + } + + mod actually_hidden_indirect { + // BUG: when there is a chain of imports, we should stop looking as soon as soon as we hit + // something with `doc(hidden)`. + #[doc(hidden)] + pub use super::actually_hidden_indirect_inner::actually_hidden_indirect_macro; + } + + mod actually_hidden_indirect_struct_inner { + #[doc(inline)] + pub use crate::IndirectlyHiddenStruct; + } + + mod actually_hidden_indirect_struct { + #[doc(hidden)] + pub use super::actually_hidden_indirect_struct_inner::IndirectlyHiddenStruct; + } + + mod actually_hidden_wild { + #[doc(hidden)] + #[doc(inline)] + pub use crate::actually_hidden_wild_macro; + } + + mod actually_hidden_struct { + #[doc(inline)] + #[doc(hidden)] + pub use crate::HiddenStruct; + } + + // First, we check that the explicitly named macro inherits the inline attribute + // from `hidden_explicit` and is successfully rendered. + //@ has 'foo/bar/macro.explicit_macro.html' + //@ has 'foo/bar/index.html' '//a[@href="macro.explicit_macro.html"]' 'explicit_macro' + pub use self::hidden_explicit::explicit_macro; + + // Next, we ensure that the glob-imported macro continues to render correctly + // as a control case. + //@ has 'foo/bar/macro.wild_macro.html' + //@ has 'foo/bar/index.html' '//a[@href="macro.wild_macro.html"]' 'wild_macro' + pub use self::hidden_wild::*; + + //@ !has 'foo/bar/macro.actually_hidden_macro.html' + pub use self::actually_hidden::actually_hidden_macro; + + //@ !has 'foo/bar/macro.actually_hidden_wild_macro.html' + pub use self::actually_hidden_wild::*; + + //@ !has 'foo/bar/struct.HiddenStruct.html' + pub use self::actually_hidden_struct::HiddenStruct; + + //@ !has 'foo/bar/macro.actually_hidden_indirect_macro.html' + pub use self::actually_hidden_indirect::actually_hidden_indirect_macro; + + //@ !has 'foo/bar/struct.IndirectlyHiddenStruct.html' + pub use self::actually_hidden_indirect_struct::IndirectlyHiddenStruct; +}