From 21925e9e2353a24e9a2fc9dd42a1e4c681eacdf9 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 12 Apr 2026 01:24:04 +0200 Subject: [PATCH 1/4] Lazily check diagnostic namespace features --- .../src/attributes/diagnostic/on_const.rs | 1 + .../src/attributes/diagnostic/on_unknown.rs | 1 + compiler/rustc_resolve/src/macros.rs | 25 +++++++++++-------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs index def4069f6b477..23db854252a37 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs @@ -17,6 +17,7 @@ impl AttributeParser for OnConstParser { template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), |this, cx, args| { if !cx.features().diagnostic_on_const() { + // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs return; } diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs index bd5eb4cbf82c7..dcfba68a4cf8b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs @@ -18,6 +18,7 @@ impl OnUnknownParser { mode: Mode, ) { if !cx.features().diagnostic_on_unknown() { + // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs return; } let span = cx.attr_span; diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 67a896bdd7557..15244b7afb69f 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -712,25 +712,28 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span, msg).emit(); } - let diagnostic_attributes: &[(Symbol, bool)] = &[ - (sym::on_unimplemented, true), - (sym::do_not_recommend, true), - (sym::on_move, true), - (sym::on_const, self.tcx.features().diagnostic_on_const()), - (sym::on_unknown, self.tcx.features().diagnostic_on_unknown()), + const DIAGNOSTIC_ATTRIBUTES: &[(Symbol, Option)] = &[ + (sym::on_unimplemented, None), + (sym::do_not_recommend, None), + (sym::on_move, None), + (sym::on_const, Some(sym::diagnostic_on_const)), + (sym::on_unknown, Some(sym::diagnostic_on_unknown)), ]; if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) && let [namespace, attribute, ..] = &*path.segments && namespace.ident.name == sym::diagnostic - && !diagnostic_attributes - .iter() - .any(|(attr, stable)| *stable && attribute.ident.name == *attr) + && !DIAGNOSTIC_ATTRIBUTES.iter().any(|(attr, stable)| { + attribute.ident.name == *attr + && stable.is_none_or(|f| self.tcx.features().enabled(f)) + }) { let span = attribute.span(); - let candidates = diagnostic_attributes + let candidates = DIAGNOSTIC_ATTRIBUTES .iter() - .filter_map(|(sym, stable)| stable.then_some(*sym)) + .filter_map(|(sym, stable)| { + stable.is_none_or(|f| self.tcx.features().enabled(f)).then_some(*sym) + }) .collect::>(); let typo = find_best_match_for_name(&candidates, attribute.ident.name, Some(5)) .map(|typo_name| errors::UnknownDiagnosticAttributeTypoSugg { span, typo_name }); From d69403abfc978657586e9ff85b71b988248b915e Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 12 Apr 2026 01:42:04 +0200 Subject: [PATCH 2/4] Properly emit diagnostic for diagnostic::on_move being used without feature gate --- .../src/attributes/diagnostic/on_move.rs | 1 + compiler/rustc_resolve/src/macros.rs | 2 +- .../feature-gate-diagnostic-on-move.rs | 5 ++--- .../feature-gate-diagnostic-on-move.stderr | 16 ++++++++++++---- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs index 006b3b66658e0..a79b7d6afbcdc 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs @@ -24,6 +24,7 @@ impl OnMoveParser { mode: Mode, ) { if !cx.features().diagnostic_on_move() { + // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs return; } diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 15244b7afb69f..7769b6d815454 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -715,7 +715,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { const DIAGNOSTIC_ATTRIBUTES: &[(Symbol, Option)] = &[ (sym::on_unimplemented, None), (sym::do_not_recommend, None), - (sym::on_move, None), + (sym::on_move, Some(sym::diagnostic_on_move)), (sym::on_const, Some(sym::diagnostic_on_const)), (sym::on_unknown, Some(sym::diagnostic_on_unknown)), ]; diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.rs index a1f3b1fbbc860..a55a6260d6512 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.rs +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.rs @@ -2,9 +2,8 @@ //! gate, but the fact that not adding the feature gate will cause the //! diagnostic to not emit the custom diagnostic message //! -#[diagnostic::on_move( - message = "Foo" -)] +#[diagnostic::on_move(message = "Foo")] +//~^ WARN unknown diagnostic attribute #[derive(Debug)] struct Foo; diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr index 9ba6f272cf92b..83d6448ed5704 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr @@ -1,5 +1,13 @@ +warning: unknown diagnostic attribute + --> $DIR/feature-gate-diagnostic-on-move.rs:5:15 + | +LL | #[diagnostic::on_move(message = "Foo")] + | ^^^^^^^ + | + = note: `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + error[E0382]: use of moved value: `foo` - --> $DIR/feature-gate-diagnostic-on-move.rs:16:15 + --> $DIR/feature-gate-diagnostic-on-move.rs:15:15 | LL | let foo = Foo; | --- move occurs because `foo` has type `Foo`, which does not implement the `Copy` trait @@ -9,14 +17,14 @@ LL | let bar = foo; | ^^^ value used here after move | note: consider changing this parameter type in function `takes_foo` to borrow instead if owning the value isn't necessary - --> $DIR/feature-gate-diagnostic-on-move.rs:11:17 + --> $DIR/feature-gate-diagnostic-on-move.rs:10:17 | LL | fn takes_foo(_: Foo) {} | --------- ^^^ this parameter takes ownership of the value | | | in this function note: if `Foo` implemented `Clone`, you could clone the value - --> $DIR/feature-gate-diagnostic-on-move.rs:9:1 + --> $DIR/feature-gate-diagnostic-on-move.rs:8:1 | LL | struct Foo; | ^^^^^^^^^^ consider implementing `Clone` for this type @@ -24,6 +32,6 @@ LL | struct Foo; LL | takes_foo(foo); | --- you could clone this value -error: aborting due to 1 previous error +error: aborting due to 1 previous error; 1 warning emitted For more information about this error, try `rustc --explain E0382`. From 55cd47658c8ae2e09011ccbfee773bd03b704263 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 12 Apr 2026 01:47:07 +0200 Subject: [PATCH 3/4] Change edit distance to not suggest `on_move` and `on_const` against each other --- compiler/rustc_resolve/src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 7769b6d815454..f106d88a83206 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -735,7 +735,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { stable.is_none_or(|f| self.tcx.features().enabled(f)).then_some(*sym) }) .collect::>(); - let typo = find_best_match_for_name(&candidates, attribute.ident.name, Some(5)) + let typo = find_best_match_for_name(&candidates, attribute.ident.name, None) .map(|typo_name| errors::UnknownDiagnosticAttributeTypoSugg { span, typo_name }); self.tcx.sess.psess.buffer_lint( From 08a12dc1432edd57898f3cb6b154a72977937fde Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 12 Apr 2026 18:38:17 +0200 Subject: [PATCH 4/4] Add feature hint for unstable diagnostic attributes --- compiler/rustc_resolve/src/errors.rs | 26 +++++++------ compiler/rustc_resolve/src/macros.rs | 38 ++++++++++++++----- .../feature-gate-diagnostic-on-move.stderr | 1 + .../feature-gate-diagnostic-on-unknown.stderr | 1 + 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index bcc754fd984da..8c7bf61949a29 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1521,20 +1521,24 @@ pub(crate) struct RedundantImportVisibility { #[diag("unknown diagnostic attribute")] pub(crate) struct UnknownDiagnosticAttribute { #[subdiagnostic] - pub typo: Option, + pub help: Option, } #[derive(Subdiagnostic)] -#[suggestion( - "an attribute with a similar name exists", - style = "verbose", - code = "{typo_name}", - applicability = "machine-applicable" -)] -pub(crate) struct UnknownDiagnosticAttributeTypoSugg { - #[primary_span] - pub span: Span, - pub typo_name: Symbol, +pub(crate) enum UnknownDiagnosticAttributeHelp { + #[suggestion( + "an attribute with a similar name exists", + style = "verbose", + code = "{typo_name}", + applicability = "machine-applicable" + )] + Typo { + #[primary_span] + span: Span, + typo_name: Symbol, + }, + #[help("add `#![feature({$feature})]` to the crate attributes to enable")] + UseFeature { feature: Symbol }, } // FIXME: Make this properly translatable. diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index f106d88a83206..13bda9c98c0a7 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -723,26 +723,44 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) && let [namespace, attribute, ..] = &*path.segments && namespace.ident.name == sym::diagnostic - && !DIAGNOSTIC_ATTRIBUTES.iter().any(|(attr, stable)| { + && !DIAGNOSTIC_ATTRIBUTES.iter().any(|(attr, feature)| { attribute.ident.name == *attr - && stable.is_none_or(|f| self.tcx.features().enabled(f)) + && feature.is_none_or(|f| self.tcx.features().enabled(f)) }) { + let name = attribute.ident.name; let span = attribute.span(); - let candidates = DIAGNOSTIC_ATTRIBUTES - .iter() - .filter_map(|(sym, stable)| { - stable.is_none_or(|f| self.tcx.features().enabled(f)).then_some(*sym) + + let help = 'help: { + if self.tcx.sess.is_nightly_build() { + for (attr, feature) in DIAGNOSTIC_ATTRIBUTES { + if let Some(feature) = *feature + && *attr == name + { + break 'help Some(errors::UnknownDiagnosticAttributeHelp::UseFeature { + feature, + }); + } + } + } + + let candidates = DIAGNOSTIC_ATTRIBUTES + .iter() + .filter_map(|(attr, feature)| { + feature.is_none_or(|f| self.tcx.features().enabled(f)).then_some(*attr) + }) + .collect::>(); + + find_best_match_for_name(&candidates, name, None).map(|typo_name| { + errors::UnknownDiagnosticAttributeHelp::Typo { span, typo_name } }) - .collect::>(); - let typo = find_best_match_for_name(&candidates, attribute.ident.name, None) - .map(|typo_name| errors::UnknownDiagnosticAttributeTypoSugg { span, typo_name }); + }; self.tcx.sess.psess.buffer_lint( UNKNOWN_DIAGNOSTIC_ATTRIBUTES, span, node_id, - errors::UnknownDiagnosticAttribute { typo }, + errors::UnknownDiagnosticAttribute { help }, ); } diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr index 83d6448ed5704..593120edd1700 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr @@ -4,6 +4,7 @@ warning: unknown diagnostic attribute LL | #[diagnostic::on_move(message = "Foo")] | ^^^^^^^ | + = help: add `#![feature(diagnostic_on_move)]` to the crate attributes to enable = note: `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default error[E0382]: use of moved value: `foo` diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr index f6d7ffadaceae..d9c8071339b75 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr @@ -10,6 +10,7 @@ error: unknown diagnostic attribute LL | #[diagnostic::on_unknown(message = "Tada")] | ^^^^^^^^^^ | + = help: add `#![feature(diagnostic_on_unknown)]` to the crate attributes to enable note: the lint level is defined here --> $DIR/feature-gate-diagnostic-on-unknown.rs:1:9 |