From 8307dfd0e829f537918e1852664b138773e058e4 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Sun, 9 Feb 2025 18:29:32 +0100 Subject: [PATCH 1/2] Introduce a `#[diagnostic::on_unknown_item]` attribute This PR introduces a `#[diagnostic::on_unknown_item]` attribute that allows crate authors to customize the error messages emitted by unresolved imports. The main usecase for this is using this attribute as part of a proc macro that expects a certain external module structure to exist or certain dependencies to be there. For me personally the motivating use-case are several derives in diesel, that expect to refer to a `tabe` module. That is done either implicitly (via the name of the type with the derive) or explicitly by the user. This attribute would allow us to improve the error message in both cases: * For the implicit case we could explicity call out our assumptions (turning the name into lower case, adding an `s` in the end) + point to the explicit variant as alternative * For the explicit variant we would add additional notes to tell the user why this is happening and what they should look for to fix the problem (be more explicit about certain diesel specific assumptions of the module structure) I assume that similar use-cases exist for other proc-macros as well, therefore I decided to put in the work implementing this new attribute. I would also assume that this is likely not useful for std-lib internal usage. --- compiler/rustc_ast_passes/src/feature_gate.rs | 2 +- .../src/attributes/diagnostic/mod.rs | 15 ++- .../attributes/diagnostic/on_unknown_item.rs | 70 +++++++++++ compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_attr_parsing/src/interface.rs | 10 +- .../src/diagnostics/conflict_errors.rs | 6 +- .../src/deriving/generic/mod.rs | 2 +- .../src/proc_macro_harness.rs | 2 +- compiler/rustc_builtin_macros/src/test.rs | 2 +- .../rustc_builtin_macros/src/test_harness.rs | 2 +- compiler/rustc_expand/src/config.rs | 2 +- compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_feature/src/unstable.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 8 ++ compiler/rustc_hir/src/attrs/diagnostic.rs | 34 ++++-- .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_interface/src/passes.rs | 6 +- compiler/rustc_lint/src/builtin.rs | 2 +- compiler/rustc_lint/src/early/diagnostics.rs | 9 ++ compiler/rustc_lint/src/lints.rs | 18 +++ compiler/rustc_lint/src/nonstandard_style.rs | 2 +- compiler/rustc_lint_defs/src/lib.rs | 5 + compiler/rustc_passes/src/check_attr.rs | 23 +++- .../rustc_passes/src/debugger_visualizer.rs | 2 +- .../rustc_resolve/src/build_reduced_graph.rs | 8 +- compiler/rustc_resolve/src/imports.rs | 84 +++++++++++-- compiler/rustc_resolve/src/macros.rs | 22 +++- compiler/rustc_span/src/symbol.rs | 2 + .../on_unknown_item/incorrect-locations.rs | 51 ++++++++ .../incorrect-locations.stderr | 110 ++++++++++++++++++ .../on_unknown_item/malformed_attribute.rs | 19 +++ .../malformed_attribute.stderr | 44 +++++++ .../on_unknown_item/multiple_errors.rs | 48 ++++++++ .../on_unknown_item/multiple_errors.stderr | 43 +++++++ .../on_unknown_item/unknown_import.rs | 17 +++ .../on_unknown_item/unknown_import.stderr | 13 +++ ...feature-gate-diagnostic-on-unknown-item.rs | 8 ++ ...ure-gate-diagnostic-on-unknown-item.stderr | 22 ++++ 38 files changed, 669 insertions(+), 50 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 830eb3d6d8170..849e60153cead 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -662,7 +662,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate) AttributeParser::parse_limited( sess, &krate.attrs, - sym::feature, + &[sym::feature], DUMMY_SP, krate.id, Some(&features), diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 63780676bead9..fd5516ff25c4e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -24,6 +24,7 @@ pub(crate) mod do_not_recommend; pub(crate) mod on_const; pub(crate) mod on_move; pub(crate) mod on_unimplemented; +pub(crate) mod on_unknown_item; #[derive(Copy, Clone)] pub(crate) enum Mode { @@ -35,6 +36,8 @@ pub(crate) enum Mode { DiagnosticOnConst, /// `#[diagnostic::on_move]` DiagnosticOnMove, + /// `#[diagnostic::on_unknown_item]` + DiagnosticOnUnknownItem, } fn merge_directives( @@ -123,6 +126,13 @@ fn parse_directive_items<'p, S: Stage>( span, ); } + Mode::DiagnosticOnUnknownItem => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnknownItemdAttr { span }, + span, + ); + } } continue; }} @@ -142,7 +152,7 @@ fn parse_directive_items<'p, S: Stage>( Mode::RustcOnUnimplemented => { cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); } - Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove => { + Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnknownItem => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, AttributeLintKind::IgnoredDiagnosticOption { @@ -173,7 +183,8 @@ fn parse_directive_items<'p, S: Stage>( Ok((f, warnings)) => { for warning in warnings { let (FormatWarning::InvalidSpecifier { span, .. } - | FormatWarning::PositionalArgument { span, .. }) = warning; + | FormatWarning::PositionalArgument { span, .. } + | FormatWarning::DisallowedPlaceholder { span }) = warning; cx.emit_lint( MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, AttributeLintKind::MalformedDiagnosticFormat { warning }, diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs new file mode 100644 index 0000000000000..a7abecc671ec3 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs @@ -0,0 +1,70 @@ +use rustc_hir::attrs::diagnostic::Directive; +use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; + +use crate::attributes::diagnostic::*; +use crate::attributes::prelude::*; + +#[derive(Default)] +pub(crate) struct OnUnknownItemParser { + span: Option, + directive: Option<(Span, Directive)>, +} + +impl OnUnknownItemParser { + fn parse<'sess, S: Stage>( + &mut self, + cx: &mut AcceptContext<'_, 'sess, S>, + args: &ArgParser, + mode: Mode, + ) { + let span = cx.attr_span; + self.span = Some(span); + + let items = match args { + ArgParser::List(items) if !items.is_empty() => items, + ArgParser::NoArgs | ArgParser::List(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MissingOptionsForOnUnknownItem, + span, + ); + return; + } + ArgParser::NameValue(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnknownItemdAttr { span }, + span, + ); + return; + } + }; + + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut self.directive, (span, directive)); + }; + } +} + +impl AttributeParser for OnUnknownItemParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::diagnostic, sym::on_unknown_item], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { + this.parse(cx, args, Mode::DiagnosticOnUnknownItem); + }, + )]; + //FIXME attribute is not parsed for non-traits but diagnostics are issued in `check_attr.rs` + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + + fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { + if let Some(span) = self.span { + Some(AttributeKind::OnUnknownItem { + span, + directive: self.directive.map(|d| Box::new(d.1)), + }) + } else { + None + } + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 259a73de59853..c8dbc8a8ca39f 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -31,6 +31,7 @@ use crate::attributes::diagnostic::do_not_recommend::*; use crate::attributes::diagnostic::on_const::*; use crate::attributes::diagnostic::on_move::*; use crate::attributes::diagnostic::on_unimplemented::*; +use crate::attributes::diagnostic::on_unknown_item::*; use crate::attributes::doc::*; use crate::attributes::dummy::*; use crate::attributes::inline::*; @@ -152,6 +153,7 @@ attribute_parsers!( OnConstParser, OnMoveParser, OnUnimplementedParser, + OnUnknownItemParser, RustcAlignParser, RustcAlignStaticParser, RustcCguTestAttributeParser, diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index f75f63a0e811a..3449cc486676a 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -29,7 +29,7 @@ pub struct AttributeParser<'sess, S: Stage = Late> { /// *Only* parse attributes with this symbol. /// /// Used in cases where we want the lowering infrastructure for parse just a single attribute. - parse_only: Option, + parse_only: Option<&'static [Symbol]>, } impl<'sess> AttributeParser<'sess, Early> { @@ -50,7 +50,7 @@ impl<'sess> AttributeParser<'sess, Early> { pub fn parse_limited( sess: &'sess Session, attrs: &[ast::Attribute], - sym: Symbol, + sym: &'static [Symbol], target_span: Span, target_node_id: NodeId, features: Option<&'sess Features>, @@ -71,7 +71,7 @@ impl<'sess> AttributeParser<'sess, Early> { pub fn parse_limited_should_emit( sess: &'sess Session, attrs: &[ast::Attribute], - sym: Symbol, + sym: &'static [Symbol], target_span: Span, target_node_id: NodeId, features: Option<&'sess Features>, @@ -101,7 +101,7 @@ impl<'sess> AttributeParser<'sess, Early> { pub fn parse_limited_all( sess: &'sess Session, attrs: &[ast::Attribute], - parse_only: Option, + parse_only: Option<&'static [Symbol]>, target: Target, target_span: Span, target_node_id: NodeId, @@ -275,7 +275,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { for attr in attrs { // If we're only looking for a single attribute, skip all the ones we don't care about. if let Some(expected) = self.parse_only { - if !attr.has_name(expected) { + if !attr.path_matches(expected) { continue; } } diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 7bbce730c1658..f4817f93b1102 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -164,9 +164,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { generic_args, }; ( - directive.message.as_ref().map(|e| e.1.format(&args)), - directive.label.as_ref().map(|e| e.1.format(&args)), - directive.notes.iter().map(|e| e.format(&args)).collect(), + directive.message.as_ref().map(|e| e.1.format(Some(&args))), + directive.label.as_ref().map(|e| e.1.format(Some(&args))), + directive.notes.iter().map(|e| e.format(Some(&args))).collect(), ) } else { (None, None, ThinVec::new()) diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index b392a9623d050..18b1fafbe8754 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -493,7 +493,7 @@ impl<'a> TraitDef<'a> { match item { Annotatable::Item(item) => { let is_packed = matches!( - AttributeParser::parse_limited(cx.sess, &item.attrs, sym::repr, item.span, item.id, None), + AttributeParser::parse_limited(cx.sess, &item.attrs, &[sym::repr], item.span, item.id, None), Some(Attribute::Parsed(AttributeKind::Repr { reprs, .. })) if reprs.iter().any(|(x, _)| matches!(x, ReprPacked(..))) ); diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs index 24a5d79958c60..84f2a8e35b02c 100644 --- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -108,7 +108,7 @@ impl<'a> CollectProcMacros<'a> { })) = AttributeParser::parse_limited( self.session, slice::from_ref(attr), - sym::proc_macro_derive, + &[sym::proc_macro_derive], item.span, item.node_id(), None, diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index 5764dfc83927a..071b807109b71 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -483,7 +483,7 @@ fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic { AttributeParser::parse_limited( cx.sess, &i.attrs, - sym::should_panic, + &[sym::should_panic], i.span, i.node_id(), None, diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index 1bb6d8a6bfd05..1c947ea07d1a9 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -391,7 +391,7 @@ fn get_test_runner(sess: &Session, features: &Features, krate: &ast::Crate) -> O match AttributeParser::parse_limited( sess, &krate.attrs, - sym::test_runner, + &[sym::test_runner], krate.spans.inner_span, krate.id, Some(features), diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 8e4039b32d942..df058aa5dac70 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -53,7 +53,7 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) - AttributeParser::parse_limited( sess, krate_attrs, - sym::feature, + &[sym::feature], DUMMY_SP, DUMMY_NODE_ID, Some(&features), diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index d74450e67cf1d..f9f70bb7e455a 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -1588,6 +1588,7 @@ pub fn is_stable_diagnostic_attribute(sym: Symbol, features: &Features) -> bool sym::on_unimplemented | sym::do_not_recommend => true, sym::on_const => features.diagnostic_on_const(), sym::on_move => features.diagnostic_on_move(), + sym::on_unknown_item => features.diagnostic_on_unknown_item(), _ => false, } } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index e8ca20d7f5f44..054cf474a3701 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -474,6 +474,8 @@ declare_features! ( (unstable, diagnostic_on_const, "1.93.0", Some(143874)), /// Allows giving on-move borrowck custom diagnostic messages for a type (unstable, diagnostic_on_move, "CURRENT_RUSTC_VERSION", Some(150935)), + /// Allows giving unresolved imports a custom diagnostic message + (unstable, diagnostic_on_unknown_item, "CURRENT_RUSTC_VERSION", Some(152900)), /// Allows `#[doc(cfg(...))]`. (unstable, doc_cfg, "1.21.0", Some(43781)), /// Allows `#[doc(masked)]`. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index a18ddff947099..6fa3df2dfa7d1 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1192,6 +1192,14 @@ pub enum AttributeKind { /// None if the directive was malformed in some way. directive: Option>, }, + + /// Represents `#[diagnostic::on_unknown_item]` + OnUnknownItem { + span: Span, + /// None if the directive was malformed in some way. + directive: Option>, + }, + /// Represents `#[optimize(size|speed)]` Optimize(OptimizeAttr, Span), diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 7c66b3f844691..cd43f99434496 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -96,10 +96,10 @@ impl Directive { } OnUnimplementedNote { - label: label.map(|l| l.1.format(args)), - message: message.map(|m| m.1.format(args)), - notes: notes.into_iter().map(|n| n.format(args)).collect(), - parent_label: parent_label.map(|e_s| e_s.format(args)), + label: label.map(|l| l.1.format(Some(args))), + message: message.map(|m| m.1.format(Some(args))), + notes: notes.into_iter().map(|n| n.format(Some(args))).collect(), + parent_label: parent_label.map(|e_s| e_s.format(Some(args))), append_const_msg, } } @@ -133,7 +133,7 @@ pub struct FormatString { pub pieces: ThinVec, } impl FormatString { - pub fn format(&self, args: &FormatArgs) -> String { + pub fn format(&self, args: Option<&FormatArgs>) -> String { let mut ret = String::new(); for piece in &self.pieces { match piece { @@ -141,7 +141,9 @@ impl FormatString { // `A` if we have `trait Trait {}` and `note = "i'm the actual type of {A}"` Piece::Arg(FormatArg::GenericParam { generic_param, .. }) => { - match args.generic_args.iter().find(|(p, _)| p == generic_param) { + match args + .and_then(|args| args.generic_args.iter().find(|(p, _)| p == generic_param)) + { Some((_, val)) => ret.push_str(val.as_str()), None => { @@ -153,7 +155,9 @@ impl FormatString { } // `{Self}` Piece::Arg(FormatArg::SelfUpper) => { - let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) { + let slf = match args.and_then(|args| { + args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) + }) { Some((_, val)) => val.to_string(), None => "Self".to_string(), }; @@ -161,11 +165,21 @@ impl FormatString { } // It's only `rustc_onunimplemented` from here - Piece::Arg(FormatArg::This) => ret.push_str(&args.this), + Piece::Arg(FormatArg::This) => { + ret.push_str(&args.map(|args| args.this.as_str()).unwrap_or_default()) + } Piece::Arg(FormatArg::Trait) => { - let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared)); + let _ = fmt::write( + &mut ret, + format_args!( + "{}", + &args.map(|args| args.trait_sugared.as_str()).unwrap_or_default() + ), + ); + } + Piece::Arg(FormatArg::ItemContext) => { + ret.push_str(args.map(|args| args.item_context).unwrap_or_default()) } - Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context), } } ret diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index c19fc6976c6e6..699caa203ae4b 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -79,6 +79,7 @@ impl AttributeKind { OnConst { .. } => Yes, OnMove { .. } => Yes, OnUnimplemented { .. } => Yes, + OnUnknownItem { .. } => Yes, Optimize(..) => No, PanicRuntime => No, PatchableFunctionEntry { .. } => Yes, diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 6b7f31521d441..8b31b7370871a 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1318,7 +1318,7 @@ pub(crate) fn parse_crate_name( AttributeParser::parse_limited_should_emit( sess, attrs, - sym::crate_name, + &[sym::crate_name], DUMMY_SP, rustc_ast::node_id::CRATE_NODE_ID, None, @@ -1367,7 +1367,7 @@ pub fn collect_crate_types( AttributeParser::::parse_limited_should_emit( session, attrs, - sym::crate_type, + &[sym::crate_type], crate_span, CRATE_NODE_ID, None, @@ -1423,7 +1423,7 @@ fn get_recursion_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit let attr = AttributeParser::parse_limited_should_emit( sess, &krate_attrs, - sym::recursion_limit, + &[sym::recursion_limit], DUMMY_SP, rustc_ast::node_id::CRATE_NODE_ID, None, diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index af590d98c301c..66082d782e0bb 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -312,7 +312,7 @@ impl EarlyLintPass for UnsafeCode { AttributeParser::parse_limited( cx.builder.sess(), &it.attrs, - sym::allow_internal_unsafe, + &[sym::allow_internal_unsafe], it.span, DUMMY_NODE_ID, Some(cx.builder.features()), diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index c282eb34cf4d6..8501ae4cdba38 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -474,6 +474,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MalformedOnUnimplementedAttr { span } => { lints::MalformedOnUnimplementedAttrLint { span }.into_diag(dcx, level) } + &AttributeLintKind::MalformedOnUnknownItemdAttr { span } => { + lints::MalformedOnUnknownItemAttrLint { span }.into_diag(dcx, level) + } &AttributeLintKind::MalformedOnConstAttr { span } => { lints::MalformedOnConstAttrLint { span }.into_diag(dcx, level) } @@ -484,6 +487,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { FormatWarning::InvalidSpecifier { .. } => { lints::InvalidFormatSpecifier.into_diag(dcx, level) } + FormatWarning::DisallowedPlaceholder { .. } => { + lints::DisallowedPlaceholder.into_diag(dcx, level) + } }, AttributeLintKind::DiagnosticWrappedParserError { description, label, span } => { lints::WrappedParserError { description, label, span: *span }.into_diag(dcx, level) @@ -510,6 +516,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MissingOptionsForOnMove => { lints::MissingOptionsForOnMoveAttr.into_diag(dcx, level) } + &AttributeLintKind::MissingOptionsForOnUnknownItem => { + lints::MissingOptionsForOnUnknownItemAttr.into_diag(dcx, level) + } } } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 02448035216ea..7c07c1f8af48e 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3919,6 +3919,11 @@ pub(crate) struct UnreachableCfgSelectPredicateWildcard { )] pub(crate) struct DisallowedPositionalArgument; +#[derive(Diagnostic)] +#[diag("format arguments are not allowed here")] +#[help("consider removing this format argument")] +pub(crate) struct DisallowedPlaceholder; + #[derive(Diagnostic)] #[diag("invalid format specifier")] #[help("no format specifier are supported in this position")] @@ -3948,6 +3953,11 @@ pub(crate) struct IgnoredDiagnosticOption { #[help("at least one of the `message`, `note` and `label` options are expected")] pub(crate) struct MissingOptionsForOnUnimplementedAttr; +#[derive(Diagnostic)] +#[diag("missing options for `on_unknown_item` attribute")] +#[help("at least one of the `message`, `note` and `label` options are expected")] +pub(crate) struct MissingOptionsForOnUnknownItemAttr; + #[derive(Diagnostic)] #[diag("missing options for `on_const` attribute")] #[help("at least one of the `message`, `note` and `label` options are expected")] @@ -3966,6 +3976,14 @@ pub(crate) struct MalformedOnUnimplementedAttrLint { pub span: Span, } +#[derive(Diagnostic)] +#[diag("malformed `on_unknown_item` attribute")] +#[help("only `message`, `note` and `label` are allowed as options")] +pub(crate) struct MalformedOnUnknownItemAttrLint { + #[label("invalid option found here")] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("malformed `on_const` attribute")] #[help("only `message`, `note` and `label` are allowed as options")] diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs index 1fd6699e2506e..297dfac4a5f78 100644 --- a/compiler/rustc_lint/src/nonstandard_style.rs +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -145,7 +145,7 @@ impl NonCamelCaseTypes { impl EarlyLintPass for NonCamelCaseTypes { fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { let has_repr_c = matches!( - AttributeParser::parse_limited(cx.sess(), &it.attrs, sym::repr, it.span, it.id, None), + AttributeParser::parse_limited(cx.sess(), &it.attrs, &[sym::repr], it.span, it.id, None), Some(Attribute::Parsed(AttributeKind::Repr { reprs, ..})) if reprs.iter().any(|(r, _)| r == &ReprAttr::ReprC) ); diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index ef7067b25baa3..c04d023fd9163 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -837,6 +837,9 @@ pub enum AttributeLintKind { MalformedOnUnimplementedAttr { span: Span, }, + MalformedOnUnknownItemdAttr { + span: Span, + }, MalformedOnConstAttr { span: Span, }, @@ -858,6 +861,7 @@ pub enum AttributeLintKind { }, MissingOptionsForOnUnimplemented, MissingOptionsForOnConst, + MissingOptionsForOnUnknownItem, MissingOptionsForOnMove, OnMoveMalformedFormatLiterals { name: Symbol, @@ -869,6 +873,7 @@ pub enum AttributeLintKind { pub enum FormatWarning { PositionalArgument { span: Span, help: String }, InvalidSpecifier { name: String, span: Span }, + DisallowedPlaceholder { span: Span }, } pub type RegisteredTools = FxIndexSet; diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index e495088c55649..584ba64eae040 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -78,6 +78,13 @@ struct DiagnosticOnConstOnlyForNonConstTraitImpls { #[diag("`#[diagnostic::on_move]` can only be applied to enums, structs or unions")] struct DiagnosticOnMoveOnlyForAdt; +#[derive(Diagnostic)] +#[diag("`#[diagnostic::on_unknown_item]` can only be applied to `use` statements")] +struct DiagnosticOnUnknownItemOnlyForImports { + #[label("not an import")] + item_span: Span, +} + fn target_from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem<'_>) -> Target { match impl_item.kind { hir::ImplItemKind::Const(..) => Target::AssocConst, @@ -236,10 +243,11 @@ impl<'tcx> CheckAttrVisitor<'tcx> { }, Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)}, Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())}, - Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} Attribute::Parsed(AttributeKind::OnMove { span, directive }) => { self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref()) }, + Attribute::Parsed(AttributeKind::OnUnknownItem { span, .. }) => { self.check_diagnostic_on_unknown_item(*span, hir_id, target) }, + Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} Attribute::Parsed( // tidy-alphabetical-start AttributeKind::RustcAllowIncoherentImpl(..) @@ -741,6 +749,19 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + /// Checks if `#[diagnostic::on_unknown_item]` is applied to a trait impl + fn check_diagnostic_on_unknown_item(&self, attr_span: Span, hir_id: HirId, target: Target) { + if !matches!(target, Target::Use) { + let item_span = self.tcx.hir_span(hir_id); + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + attr_span, + DiagnosticOnUnknownItemOnlyForImports { item_span }, + ); + } + } + /// Checks if an `#[inline]` is applied to a function or a closure. fn check_inline(&self, hir_id: HirId, attr_span: Span, kind: &InlineAttr, target: Target) { match target { diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs index 7211f3cf85b31..828ba698e0f2f 100644 --- a/compiler/rustc_passes/src/debugger_visualizer.rs +++ b/compiler/rustc_passes/src/debugger_visualizer.rs @@ -25,7 +25,7 @@ impl DebuggerVisualizerCollector<'_> { AttributeParser::parse_limited( &self.sess, attrs, - sym::debugger_visualizer, + &[sym::debugger_visualizer], span, node_id, None, diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index d9d42cfefcd81..d706e227aa77e 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -32,7 +32,7 @@ use tracing::debug; use crate::Namespace::{MacroNS, TypeNS, ValueNS}; use crate::def_collector::collect_definitions; -use crate::imports::{ImportData, ImportKind}; +use crate::imports::{ImportData, ImportKind, OnUnknownItemData}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; use crate::{ @@ -544,6 +544,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { root_id, vis, vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), }); self.r.indeterminate_imports.push(import); @@ -1023,6 +1024,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis, vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), }); if used { self.r.import_use_map.insert(import, Used::Other); @@ -1118,7 +1120,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { AttributeParser::parse_limited( self.r.tcx.sess, &item.attrs, - sym::macro_use, + &[sym::macro_use], item.span, item.id, None, @@ -1155,6 +1157,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis: Visibility::Restricted(CRATE_DEF_ID), vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(this.r.tcx, item), }) }; @@ -1326,6 +1329,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis, vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), }); self.r.import_use_map.insert(import, Used::Other); let import_decl = self.r.new_import_decl(decl, import); diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 7696b4b220d6c..53b7be3787e01 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -2,16 +2,20 @@ use std::mem; -use rustc_ast::NodeId; +use rustc_ast::{Item, NodeId}; +use rustc_attr_parsing::AttributeParser; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_errors::codes::*; use rustc_errors::{Applicability, MultiSpan, pluralize, struct_span_code_err}; +use rustc_hir::Attribute; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::def::{self, DefKind, PartialRes}; use rustc_hir::def_id::{DefId, LocalDefIdMap}; use rustc_middle::metadata::{AmbigModChild, ModChild, Reexport}; use rustc_middle::span_bug; -use rustc_middle::ty::Visibility; +use rustc_middle::ty::{TyCtxt, Visibility}; use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::{ AMBIGUOUS_GLOB_REEXPORTS, EXPORTED_PRIVATE_DEPENDENCIES, HIDDEN_GLOB_REEXPORTS, @@ -140,6 +144,30 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { } } +#[derive(Debug, Clone, Default)] +pub(crate) struct OnUnknownItemData { + directive: Directive, +} + +impl OnUnknownItemData { + pub(crate) fn from_attrs<'tcx>(tcx: TyCtxt<'tcx>, item: &Item) -> Option { + if let Some(Attribute::Parsed(AttributeKind::OnUnknownItem { directive, .. })) = + AttributeParser::parse_limited( + tcx.sess, + &item.attrs, + &[sym::diagnostic, sym::on_unknown_item], + item.span, + item.id, + Some(tcx.features()), + ) + { + Some(Self { directive: *directive? }) + } else { + None + } + } +} + /// One import. #[derive(Debug, Clone)] pub(crate) struct ImportData<'ra> { @@ -186,6 +214,11 @@ pub(crate) struct ImportData<'ra> { /// Span of the visibility. pub vis_span: Span, + + /// A `#[diagnostic::on_unknown_item]` attribute applied + /// to the given import. This allows crates to specify + /// custom error messages for a specific import + pub on_unknown_item_attr: Option, } /// All imports are unique and allocated on a same arena, @@ -284,6 +317,7 @@ struct UnresolvedImportError { segment: Option, /// comes from `PathRes::Failed { module }` module: Option, + on_unknown_item_attr: Option, } // Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;` @@ -696,6 +730,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: None, module: None, + on_unknown_item_attr: import.on_unknown_item_attr.clone(), }; errors.push((*import, err)) } @@ -818,19 +853,47 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { format!("`{path}`") }) .collect::>(); - let msg = format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); - - let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{msg}"); + let default_message = + format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); + let mut diag = if self.tcx.features().diagnostic_on_unknown_item() + && let Some((_, message)) = + errors[0].1.on_unknown_item_attr.as_mut().and_then(|a| a.directive.message.take()) + { + let message = message.format(None); + let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{message}"); + diag.note(default_message); + diag + } else { + struct_span_code_err!(self.dcx(), span, E0432, "{default_message}") + }; - if let Some((_, UnresolvedImportError { note: Some(note), .. })) = errors.iter().last() { + if self.tcx.features().diagnostic_on_unknown_item() + && let Some(notes) = + errors[0].1.on_unknown_item_attr.as_ref().map(|a| &a.directive.notes) + { + for note in notes { + diag.note(note.format(None)); + } + } else if let Some((_, UnresolvedImportError { note: Some(note), .. })) = + errors.iter().last() + { diag.note(note.clone()); } /// Upper limit on the number of `span_label` messages. const MAX_LABEL_COUNT: usize = 10; - for (import, err) in errors.into_iter().take(MAX_LABEL_COUNT) { - if let Some(label) = err.label { + for (import, mut err) in errors.into_iter().take(MAX_LABEL_COUNT) { + if self.tcx.features().diagnostic_on_unknown_item() + && let Some(label) = err + .on_unknown_item_attr + .as_mut() + .and_then(|a| a.directive.label.take()) + .map(|label| label.1.format(None)) + .or(err.label.clone()) + { + diag.span_label(err.span, label); + } else if let Some(label) = err.label { diag.span_label(err.span, label); } @@ -1097,6 +1160,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: Some(segment_name), module, + on_unknown_item_attr: import.on_unknown_item_attr.clone(), }, None => UnresolvedImportError { span, @@ -1106,6 +1170,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: Some(segment_name), module, + on_unknown_item_attr: import.on_unknown_item_attr.clone(), }, }; return Some(err); @@ -1148,6 +1213,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: None, module: None, + on_unknown_item_attr: None, }); } if let Some(max_vis) = max_vis.get() @@ -1351,7 +1417,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let parent_suggestion = self.lookup_import_candidates(ident, TypeNS, &import.parent_scope, |_| true); - Some(UnresolvedImportError { span: import.span, label: Some(label), @@ -1370,6 +1435,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } }), segment: Some(ident.name), + on_unknown_item_attr: import.on_unknown_item_attr.clone(), }) } else { // `resolve_ident_in_module` reported a privacy error. diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 6ae9d3aaeb236..8491c940928ca 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -140,7 +140,7 @@ pub fn registered_tools_ast( AttributeParser::parse_limited( sess, pre_configured_attrs, - sym::register_tool, + &[sym::register_tool], DUMMY_SP, DUMMY_NODE_ID, Some(features), @@ -706,17 +706,27 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span, msg).emit(); } - const DIAG_ATTRS: &[Symbol] = - &[sym::on_unimplemented, sym::do_not_recommend, sym::on_const, sym::on_move]; + 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_item, self.tcx.features().diagnostic_on_unknown_item()), + ]; if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) && let [namespace, attribute, ..] = &*path.segments && namespace.ident.name == sym::diagnostic - && !DIAG_ATTRS.contains(&attribute.ident.name) + && !diagnostic_attributes + .iter() + .any(|(attr, stable)| *stable && attribute.ident.name == *attr) { let span = attribute.span(); - - let typo = find_best_match_for_name(DIAG_ATTRS, attribute.ident.name, Some(5)) + let candidates = diagnostic_attributes + .iter() + .filter_map(|(sym, stable)| stable.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 }); self.tcx.sess.psess.buffer_lint( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 738c9b975fd00..be0a360e6cda0 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -798,6 +798,7 @@ symbols! { diagnostic_namespace, diagnostic_on_const, diagnostic_on_move, + diagnostic_on_unknown_item, dialect, direct, discriminant_kind, @@ -1410,6 +1411,7 @@ symbols! { on_const, on_move, on_unimplemented, + on_unknown_item, opaque, opaque_generic_const_args, opaque_module_name_placeholder: "", diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs new file mode 100644 index 0000000000000..fabcd86deddc6 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs @@ -0,0 +1,51 @@ +#![feature(diagnostic_on_unknown_item)] + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +extern crate foo; +//~^ERROR can't find crate for `foo` + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +const CONST: () = (); + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +static STATIC: () = (); + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +type Type = (); + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +enum Enum {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +impl Enum {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +extern "C" {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +fn fun() {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +struct Struct {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +trait Trait {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +impl Trait for i32 {} + +#[diagnostic::on_unknown_item(message = "foo")] +use std::str::FromStr; + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr new file mode 100644 index 0000000000000..efa4805c318f8 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr @@ -0,0 +1,110 @@ +error[E0463]: can't find crate for `foo` + --> $DIR/incorrect-locations.rs:5:1 + | +LL | extern crate foo; + | ^^^^^^^^^^^^^^^^^ can't find crate + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:3:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | extern crate foo; + | ---------------- not an import + | + = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:8:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | const CONST: () = (); + | --------------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:12:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | static STATIC: () = (); + | ----------------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:16:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | type Type = (); + | --------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:20:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | enum Enum {} + | --------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:24:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | impl Enum {} + | --------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:28:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | extern "C" {} + | ------------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:32:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | fn fun() {} + | -------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:36:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | struct Struct {} + | ------------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:40:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | trait Trait {} + | ----------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:44:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | impl Trait for i32 {} + | ------------------ not an import + +error: aborting due to 1 previous error; 11 warnings emitted + +For more information about this error, try `rustc --explain E0463`. diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs new file mode 100644 index 0000000000000..4ffa9ffe37b5b --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs @@ -0,0 +1,19 @@ +#![feature(diagnostic_on_unknown_item)] +#[diagnostic::on_unknown_item] +//~^WARN missing options for `on_unknown_item` attribute +use std::str::FromStr; + +#[diagnostic::on_unknown_item(foo = "bar", message = "foo")] +//~^WARN malformed `on_unknown_item` attribute +use std::str::Bytes; + +#[diagnostic::on_unknown_item(label = "foo", label = "bar")] +//~^WARN `label` is ignored due to previous definition of `label` +use std::str::Chars; + +#[diagnostic::on_unknown_item(message = "Foo", message = "Bar")] +//~^WARN `message` is ignored due to previous definition of `message` +use std::str::NotExisting; +//~^ERROR Foo + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr new file mode 100644 index 0000000000000..42caaa9354afa --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr @@ -0,0 +1,44 @@ +error[E0432]: Foo + --> $DIR/malformed_attribute.rs:16:5 + | +LL | use std::str::NotExisting; + | ^^^^^^^^^^^^^^^^^^^^^ no `NotExisting` in `str` + | + = note: unresolved import `std::str::NotExisting` + +warning: missing options for `on_unknown_item` attribute + --> $DIR/malformed_attribute.rs:2:1 + | +LL | #[diagnostic::on_unknown_item] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: at least one of the `message`, `note` and `label` options are expected + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: malformed `on_unknown_item` attribute + --> $DIR/malformed_attribute.rs:6:31 + | +LL | #[diagnostic::on_unknown_item(foo = "bar", message = "foo")] + | ^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options + +warning: `label` is ignored due to previous definition of `label` + --> $DIR/malformed_attribute.rs:10:46 + | +LL | #[diagnostic::on_unknown_item(label = "foo", label = "bar")] + | ------------- ^^^^^^^^^^^^^ `label` is later redundantly declared here + | | + | `label` is first declared here + +warning: `message` is ignored due to previous definition of `message` + --> $DIR/malformed_attribute.rs:14:48 + | +LL | #[diagnostic::on_unknown_item(message = "Foo", message = "Bar")] + | --------------- ^^^^^^^^^^^^^^^ `message` is later redundantly declared here + | | + | `message` is first declared here + +error: aborting due to 1 previous error; 4 warnings emitted + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs b/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs new file mode 100644 index 0000000000000..431ab6cdd831a --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs @@ -0,0 +1,48 @@ +#![feature(diagnostic_on_unknown_item)] + +mod test1 { + #[diagnostic::on_unknown_item( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::vec::{NonExisting, Vec, Whatever}; + //~^ ERROR: custom message +} + +mod test2 { + #[diagnostic::on_unknown_item( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::{Whatever, vec::NonExisting, vec::Vec, *}; + //~^ ERROR: custom message +} + +mod test3 { + #[diagnostic::on_unknown_item( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::{ + string::String, + vec::{NonExisting, Vec}, + //~^ ERROR: custom message + }; +} + +mod test4 { + #[diagnostic::on_unknown_item( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::{ + string::String, + vec::{Vec, non_existing::*}, + //~^ ERROR: custom message + }; +} +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr new file mode 100644 index 0000000000000..fcce77f6aebbe --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr @@ -0,0 +1,43 @@ +error[E0432]: custom message + --> $DIR/multiple_errors.rs:9:20 + | +LL | use std::vec::{NonExisting, Vec, Whatever}; + | ^^^^^^^^^^^ ^^^^^^^^ custom label + | | + | custom label + | + = note: unresolved imports `std::vec::NonExisting`, `std::vec::Whatever` + = note: custom note + +error[E0432]: custom message + --> $DIR/multiple_errors.rs:19:15 + | +LL | use std::{Whatever, vec::NonExisting, vec::Vec, *}; + | ^^^^^^^^ ^^^^^^^^^^^^^^^^ custom label + | | + | custom label + | + = note: unresolved imports `std::Whatever`, `std::vec::NonExisting` + = note: custom note + +error[E0432]: custom message + --> $DIR/multiple_errors.rs:31:15 + | +LL | vec::{NonExisting, Vec}, + | ^^^^^^^^^^^ custom label + | + = note: unresolved import `std::vec::NonExisting` + = note: custom note + +error[E0432]: custom message + --> $DIR/multiple_errors.rs:44:20 + | +LL | vec::{Vec, non_existing::*}, + | ^^^^^^^^^^^^ custom label + | + = note: unresolved import `std::vec::non_existing` + = note: custom note + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs b/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs new file mode 100644 index 0000000000000..5af79af23c2cc --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs @@ -0,0 +1,17 @@ +#![feature(diagnostic_on_unknown_item)] +pub mod foo { + pub struct Bar; +} + +#[diagnostic::on_unknown_item( + message = "first message", + label = "first label", + note = "custom note", + note = "custom note 2" +)] +use foo::Foo; +//~^ERROR first message + +use foo::Bar; + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr new file mode 100644 index 0000000000000..a9867fd74bfb0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr @@ -0,0 +1,13 @@ +error[E0432]: first message + --> $DIR/unknown_import.rs:12:5 + | +LL | use foo::Foo; + | ^^^^^^^^ first label + | + = note: unresolved import `foo::Foo` + = note: custom note + = note: custom note 2 + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs new file mode 100644 index 0000000000000..fffb54636cfb7 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs @@ -0,0 +1,8 @@ +#![deny(warnings)] + +#[diagnostic::on_unknown_item(message = "Tada")] +//~^ ERROR: unknown diagnostic attribute +use std::vec::NotExisting; +//~^ ERROR: unresolved import `std::vec::NotExisting` + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr new file mode 100644 index 0000000000000..10662eb83b4a5 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr @@ -0,0 +1,22 @@ +error[E0432]: unresolved import `std::vec::NotExisting` + --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:5:5 + | +LL | use std::vec::NotExisting; + | ^^^^^^^^^^^^^^^^^^^^^ no `NotExisting` in `vec` + +error: unknown diagnostic attribute + --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:3:15 + | +LL | #[diagnostic::on_unknown_item(message = "Tada")] + | ^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:1:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(unknown_diagnostic_attributes)]` implied by `#[deny(warnings)]` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0432`. From b49a09717a4ca5f0d848321534e478bd1e52e098 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Mon, 16 Mar 2026 13:01:13 +0100 Subject: [PATCH 2/2] Address review comments --- .../attributes/diagnostic/on_unknown_item.rs | 5 +++- compiler/rustc_resolve/src/imports.rs | 23 ++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs index a7abecc671ec3..f2a243a8e0bba 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs @@ -17,6 +17,9 @@ impl OnUnknownItemParser { args: &ArgParser, mode: Mode, ) { + if !cx.features().diagnostic_on_unknown_item() { + return; + } let span = cx.attr_span; self.span = Some(span); @@ -54,7 +57,7 @@ impl AttributeParser for OnUnknownItemParser { this.parse(cx, args, Mode::DiagnosticOnUnknownItem); }, )]; - //FIXME attribute is not parsed for non-traits but diagnostics are issued in `check_attr.rs` + //FIXME attribute is not parsed for non-use statements but diagnostics are issued in `check_attr.rs` const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 53b7be3787e01..6b4df7572cb34 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -855,9 +855,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .collect::>(); let default_message = format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); - let mut diag = if self.tcx.features().diagnostic_on_unknown_item() - && let Some((_, message)) = - errors[0].1.on_unknown_item_attr.as_mut().and_then(|a| a.directive.message.take()) + let mut diag = if let Some((_, message)) = + errors[0].1.on_unknown_item_attr.as_mut().and_then(|a| a.directive.message.take()) { let message = message.format(None); let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{message}"); @@ -867,10 +866,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { struct_span_code_err!(self.dcx(), span, E0432, "{default_message}") }; - if self.tcx.features().diagnostic_on_unknown_item() - && let Some(notes) = - errors[0].1.on_unknown_item_attr.as_ref().map(|a| &a.directive.notes) - { + if let Some(notes) = errors[0].1.on_unknown_item_attr.as_ref().map(|a| &a.directive.notes) { for note in notes { diag.note(note.format(None)); } @@ -884,13 +880,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { const MAX_LABEL_COUNT: usize = 10; for (import, mut err) in errors.into_iter().take(MAX_LABEL_COUNT) { - if self.tcx.features().diagnostic_on_unknown_item() - && let Some(label) = err - .on_unknown_item_attr - .as_mut() - .and_then(|a| a.directive.label.take()) - .map(|label| label.1.format(None)) - .or(err.label.clone()) + if let Some(label) = err + .on_unknown_item_attr + .as_mut() + .and_then(|a| a.directive.label.take()) + .map(|label| label.1.format(None)) + .or(err.label.clone()) { diag.span_label(err.span, label); } else if let Some(label) = err.label {