diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 1b615b611258f..4e3310d3fb097 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -649,7 +649,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 e63baf77c0852..f68bed620f1b3 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; #[derive(Copy, Clone)] pub(crate) enum Mode { @@ -35,6 +36,8 @@ pub(crate) enum Mode { DiagnosticOnConst, /// `#[diagnostic::on_move]` DiagnosticOnMove, + /// `#[diagnostic::on_unknown]` + DiagnosticOnUnknown, } fn merge_directives( @@ -122,6 +125,13 @@ fn parse_directive_items<'p, S: Stage>( span, ); } + Mode::DiagnosticOnUnknown => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnknownAttr { span }, + span, + ); + } } continue; }} @@ -140,7 +150,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::DiagnosticOnUnknown => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, AttributeLintKind::IgnoredDiagnosticOption { @@ -176,7 +186,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 }, @@ -326,6 +337,10 @@ fn parse_arg( is_source_literal: bool, ) -> FormatArg { let span = slice_span(input_span, arg.position_span.clone(), is_source_literal); + if matches!(mode, Mode::DiagnosticOnUnknown) { + warnings.push(FormatWarning::DisallowedPlaceholder { span }); + return FormatArg::AsIs(sym::empty_braces); + } match arg.position { // Something like "hello {name}" diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs new file mode 100644 index 0000000000000..bd5eb4cbf82c7 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs @@ -0,0 +1,73 @@ +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 OnUnknownParser { + span: Option, + directive: Option<(Span, Directive)>, +} + +impl OnUnknownParser { + fn parse<'sess, S: Stage>( + &mut self, + cx: &mut AcceptContext<'_, 'sess, S>, + args: &ArgParser, + mode: Mode, + ) { + if !cx.features().diagnostic_on_unknown() { + return; + } + 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::MissingOptionsForOnUnknown, + span, + ); + return; + } + ArgParser::NameValue(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnknownAttr { 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 OnUnknownParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::diagnostic, sym::on_unknown], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { + this.parse(cx, args, Mode::DiagnosticOnUnknown); + }, + )]; + //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 { + if let Some(span) = self.span { + Some(AttributeKind::OnUnknown { + 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 6ab3f98e2015c..3fde8d79f5143 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -32,6 +32,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::*; use crate::attributes::doc::*; use crate::attributes::dummy::*; use crate::attributes::inline::*; @@ -154,6 +155,7 @@ attribute_parsers!( OnConstParser, OnMoveParser, OnUnimplementedParser, + OnUnknownParser, RustcAlignParser, RustcAlignStaticParser, RustcCguTestAttributeParser, diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index f66d6dd3f4c9c..a2a2967f9ad6c 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>, @@ -72,7 +72,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, target: Target, @@ -103,7 +103,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, @@ -272,7 +272,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_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 63c06e672727d..ae0078523adbc 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 ec5951e50e3a8..87e157babe8dc 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -54,7 +54,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 acbcba90fbcc0..3edc19e4314ca 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 => features.diagnostic_on_unknown(), _ => false, } } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 859a1ad391cb9..c2fe6e1360201 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(154181)), + /// Allows giving unresolved imports a custom diagnostic message + (unstable, diagnostic_on_unknown, "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..c94444893f1e3 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]` + OnUnknown { + 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/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index c19fc6976c6e6..861d74766ccb9 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, + OnUnknown { .. } => Yes, Optimize(..) => No, PanicRuntime => No, PatchableFunctionEntry { .. } => Yes, diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index eadb099a3e1a2..f450ddfc698e6 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1369,7 +1369,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, Target::Crate, @@ -1419,7 +1419,7 @@ pub fn collect_crate_types( AttributeParser::::parse_limited_should_emit( session, attrs, - sym::crate_type, + &[sym::crate_type], crate_span, CRATE_NODE_ID, Target::Crate, @@ -1476,7 +1476,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, Target::Crate, 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 776313a7e8040..1b31639c40785 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -179,6 +179,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MalformedOnUnimplementedAttr { span } => { lints::MalformedOnUnimplementedAttrLint { span }.into_diag(dcx, level) } + &AttributeLintKind::MalformedOnUnknownAttr { span } => { + lints::MalformedOnUnknownAttrLint { span }.into_diag(dcx, level) + } &AttributeLintKind::MalformedOnConstAttr { span } => { lints::MalformedOnConstAttrLint { span }.into_diag(dcx, level) } @@ -189,6 +192,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) @@ -215,6 +221,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MissingOptionsForOnMove => { lints::MissingOptionsForOnMoveAttr.into_diag(dcx, level) } + &AttributeLintKind::MissingOptionsForOnUnknown => { + lints::MissingOptionsForOnUnknownAttr.into_diag(dcx, level) + } } } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 4279ab230df55..19ec3f4ca45d1 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3542,6 +3542,11 @@ pub(crate) struct UnknownCrateTypesSuggestion { )] 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")] @@ -3571,6 +3576,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` attribute")] +#[help("at least one of the `message`, `note` and `label` options are expected")] +pub(crate) struct MissingOptionsForOnUnknownAttr; + #[derive(Diagnostic)] #[diag("missing options for `on_const` attribute")] #[help("at least one of the `message`, `note` and `label` options are expected")] @@ -3589,6 +3599,14 @@ pub(crate) struct MalformedOnUnimplementedAttrLint { pub span: Span, } +#[derive(Diagnostic)] +#[diag("malformed `on_unknown` attribute")] +#[help("only `message`, `note` and `label` are allowed as options")] +pub(crate) struct MalformedOnUnknownAttrLint { + #[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 2cec2ed06a502..a77b7bc7d9483 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -736,6 +736,9 @@ pub enum AttributeLintKind { MalformedOnUnimplementedAttr { span: Span, }, + MalformedOnUnknownAttr { + span: Span, + }, MalformedOnConstAttr { span: Span, }, @@ -757,6 +760,7 @@ pub enum AttributeLintKind { }, MissingOptionsForOnUnimplemented, MissingOptionsForOnConst, + MissingOptionsForOnUnknown, MissingOptionsForOnMove, OnMoveMalformedFormatLiterals { name: Symbol, @@ -768,6 +772,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 6aeb0ae57e752..c498728028786 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -74,6 +74,13 @@ struct DiagnosticOnConstOnlyForNonConstTraitImpls { #[diag("`#[diagnostic::on_move]` can only be applied to enums, structs or unions")] struct DiagnosticOnMoveOnlyForAdt; +#[derive(Diagnostic)] +#[diag("`#[diagnostic::on_unknown]` can only be applied to `use` statements")] +struct DiagnosticOnUnknownOnlyForImports { + #[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, @@ -219,6 +226,7 @@ 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::OnUnknown { span, .. }) => { self.check_diagnostic_on_unknown(*span, hir_id, target) }, 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()) @@ -727,6 +735,19 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + /// Checks if `#[diagnostic::on_unknown]` is applied to a trait impl + fn check_diagnostic_on_unknown(&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, + DiagnosticOnUnknownOnlyForImports { 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 d00c306329b7e..983a7a201b107 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, OnUnknownData}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; use crate::{ @@ -545,6 +545,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { root_id, vis, vis_span: item.vis.span, + on_unknown_attr: OnUnknownData::from_attrs(self.r.tcx, item), }); self.r.indeterminate_imports.push(import); @@ -1026,6 +1027,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis, vis_span: item.vis.span, + on_unknown_attr: OnUnknownData::from_attrs(self.r.tcx, item), }); if used { self.r.import_use_map.insert(import, Used::Other); @@ -1121,7 +1123,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, @@ -1158,6 +1160,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_attr: OnUnknownData::from_attrs(this.r.tcx, item), }) }; @@ -1329,6 +1332,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis, vis_span: item.vis.span, + on_unknown_attr: OnUnknownData::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 d51ce9fb7946b..18db60167c27c 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, Diagnostic, MultiSpan, pluralize, struct_span_code_err}; +use rustc_hir::Attribute; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; 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::builtin::{ AMBIGUOUS_GLOB_REEXPORTS, EXPORTED_PRIVATE_DEPENDENCIES, HIDDEN_GLOB_REEXPORTS, PUB_USE_OF_PRIVATE_EXTERN_CRATE, REDUNDANT_IMPORTS, UNUSED_IMPORTS, @@ -140,6 +144,30 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { } } +#[derive(Debug, Clone, Default)] +pub(crate) struct OnUnknownData { + directive: Directive, +} + +impl OnUnknownData { + pub(crate) fn from_attrs<'tcx>(tcx: TyCtxt<'tcx>, item: &Item) -> Option { + if let Some(Attribute::Parsed(AttributeKind::OnUnknown { directive, .. })) = + AttributeParser::parse_limited( + tcx.sess, + &item.attrs, + &[sym::diagnostic, sym::on_unknown], + 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]` attribute applied + /// to the given import. This allows crates to specify + /// custom error messages for a specific import + pub on_unknown_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_attr: Option, } // Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;` @@ -700,6 +734,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: None, module: None, + on_unknown_attr: import.on_unknown_attr.clone(), }; errors.push((*import, err)) } @@ -822,11 +857,41 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { format!("`{path}`") }) .collect::>(); - let msg = format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); + let default_message = + format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); + let (message, label, notes) = if self.tcx.features().diagnostic_on_unknown() + && let Some(directive) = errors[0].1.on_unknown_attr.as_ref().map(|a| &a.directive) + { + let args = FormatArgs { + this: paths.join(", "), + // Unused + this_sugared: String::new(), + // Unused + item_context: "", + // Unused + generic_args: Vec::new(), + }; + let CustomDiagnostic { message, label, notes, .. } = directive.eval(None, &args); - let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{msg}"); + (message, label, notes) + } else { + (None, None, Vec::new()) + }; + let has_custom_message = message.is_some(); + let message = message.as_deref().unwrap_or(default_message.as_str()); - if let Some((_, UnresolvedImportError { note: Some(note), .. })) = errors.iter().last() { + let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{message}"); + if has_custom_message { + diag.note(default_message); + } + + if !notes.is_empty() { + for note in notes { + diag.note(note); + } + } else if let Some((_, UnresolvedImportError { note: Some(note), .. })) = + errors.iter().last() + { diag.note(note.clone()); } @@ -834,8 +899,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { const MAX_LABEL_COUNT: usize = 10; for (import, err) in errors.into_iter().take(MAX_LABEL_COUNT) { - if let Some(label) = err.label { - diag.span_label(err.span, label); + if let Some(label) = &label { + diag.span_label(err.span, label.clone()); + } else if let Some(label) = &err.label { + diag.span_label(err.span, label.clone()); } if let Some((suggestions, msg, applicability)) = err.suggestion { @@ -1101,6 +1168,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: Some(segment_name), module, + on_unknown_attr: import.on_unknown_attr.clone(), }, None => UnresolvedImportError { span, @@ -1110,6 +1178,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: Some(segment_name), module, + on_unknown_attr: import.on_unknown_attr.clone(), }, }; return Some(err); @@ -1152,6 +1221,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: None, module: None, + on_unknown_attr: None, }); } if let Some(max_vis) = max_vis.get() @@ -1374,6 +1444,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } }), segment: Some(ident.name), + on_unknown_attr: import.on_unknown_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 619e61211984d..67a896bdd7557 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), @@ -712,17 +712,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, self.tcx.features().diagnostic_on_unknown()), + ]; 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 33bc5a578e8b6..f128427cc675d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -799,6 +799,7 @@ symbols! { diagnostic_namespace, diagnostic_on_const, diagnostic_on_move, + diagnostic_on_unknown, dialect, direct, discriminant_kind, @@ -1418,6 +1419,7 @@ symbols! { on_const, on_move, on_unimplemented, + on_unknown, opaque, opaque_module_name_placeholder: "", ops, diff --git a/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.rs b/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.rs new file mode 100644 index 0000000000000..b8852e7dd216c --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.rs @@ -0,0 +1,52 @@ +//@ run-pass +#![allow(dead_code, unused_imports)] +#![feature(diagnostic_on_unknown)] + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +extern crate std as other_std; + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +const CONST: () = (); + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +static STATIC: () = (); + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +type Type = (); + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +enum Enum {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +impl Enum {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +extern "C" {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +fn fun() {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +struct Struct {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +trait Trait {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +impl Trait for i32 {} + +#[diagnostic::on_unknown(message = "foo")] +use std::str::FromStr; + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.stderr b/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.stderr new file mode 100644 index 0000000000000..33636e1fcfc3f --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.stderr @@ -0,0 +1,103 @@ +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:5:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | extern crate std as other_std; + | ----------------------------- not an import + | + = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:9:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | const CONST: () = (); + | --------------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:13:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | static STATIC: () = (); + | ----------------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:17:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | type Type = (); + | --------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:21:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | enum Enum {} + | --------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:25:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | impl Enum {} + | --------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:29:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | extern "C" {} + | ------------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:33:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | fn fun() {} + | -------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:37:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | struct Struct {} + | ------------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:41:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | trait Trait {} + | ----------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:45:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | impl Trait for i32 {} + | ------------------ not an import + +warning: 11 warnings emitted + diff --git a/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.rs b/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.rs new file mode 100644 index 0000000000000..cdf0f1e89efc1 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.rs @@ -0,0 +1,33 @@ +#![feature(diagnostic_on_unknown)] + +#[diagnostic::on_unknown(message = "foo {}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist; +//~^ ERROR: foo {} + +#[diagnostic::on_unknown(message = "foo {A}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist2; +//~^ ERROR: foo {} + +#[diagnostic::on_unknown(label = "foo {}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist3; +//~^ ERROR: unresolved import `std::does_not_exist3` + +#[diagnostic::on_unknown(label = "foo {A}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist4; +//~^ ERROR: unresolved import `std::does_not_exist4` + +#[diagnostic::on_unknown(note = "foo {}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist5; +//~^ ERROR: unresolved import `std::does_not_exist5` + +#[diagnostic::on_unknown(note = "foo {A}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist6; +//~^ ERROR: unresolved import `std::does_not_exist6` + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.stderr b/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.stderr new file mode 100644 index 0000000000000..2b942338ffb4e --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.stderr @@ -0,0 +1,96 @@ +error[E0432]: foo {} + --> $DIR/incorrect_format_string.rs:5:5 + | +LL | use std::does_not_exist; + | ^^^^^^^^^^^^^^^^^^^ no `does_not_exist` in the root + | + = note: unresolved import `std::does_not_exist` + +error[E0432]: foo {} + --> $DIR/incorrect_format_string.rs:10:5 + | +LL | use std::does_not_exist2; + | ^^^^^^^^^^^^^^^^^^^^ no `does_not_exist2` in the root + | + = note: unresolved import `std::does_not_exist2` + +error[E0432]: unresolved import `std::does_not_exist3` + --> $DIR/incorrect_format_string.rs:15:5 + | +LL | use std::does_not_exist3; + | ^^^^^^^^^^^^^^^^^^^^ foo {} + +error[E0432]: unresolved import `std::does_not_exist4` + --> $DIR/incorrect_format_string.rs:20:5 + | +LL | use std::does_not_exist4; + | ^^^^^^^^^^^^^^^^^^^^ foo {} + +error[E0432]: unresolved import `std::does_not_exist5` + --> $DIR/incorrect_format_string.rs:25:5 + | +LL | use std::does_not_exist5; + | ^^^^^^^^^^^^^^^^^^^^ no `does_not_exist5` in the root + | + = note: foo {} + +error[E0432]: unresolved import `std::does_not_exist6` + --> $DIR/incorrect_format_string.rs:30:5 + | +LL | use std::does_not_exist6; + | ^^^^^^^^^^^^^^^^^^^^ no `does_not_exist6` in the root + | + = note: foo {} + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:3:42 + | +LL | #[diagnostic::on_unknown(message = "foo {}")] + | ^ + | + = help: consider removing this format argument + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:8:42 + | +LL | #[diagnostic::on_unknown(message = "foo {A}")] + | ^ + | + = help: consider removing this format argument + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:13:40 + | +LL | #[diagnostic::on_unknown(label = "foo {}")] + | ^ + | + = help: consider removing this format argument + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:18:40 + | +LL | #[diagnostic::on_unknown(label = "foo {A}")] + | ^ + | + = help: consider removing this format argument + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:23:39 + | +LL | #[diagnostic::on_unknown(note = "foo {}")] + | ^ + | + = help: consider removing this format argument + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:28:39 + | +LL | #[diagnostic::on_unknown(note = "foo {A}")] + | ^ + | + = help: consider removing this format argument + +error: aborting due to 6 previous errors; 6 warnings emitted + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.rs b/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.rs new file mode 100644 index 0000000000000..d8fcd1336bce1 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.rs @@ -0,0 +1,19 @@ +#![feature(diagnostic_on_unknown)] +#[diagnostic::on_unknown] +//~^WARN missing options for `on_unknown` attribute +use std::str::FromStr; + +#[diagnostic::on_unknown(foo = "bar", message = "foo")] +//~^WARN malformed `on_unknown` attribute +use std::str::Bytes; + +#[diagnostic::on_unknown(label = "foo", label = "bar")] +//~^WARN `label` is ignored due to previous definition of `label` +use std::str::Chars; + +#[diagnostic::on_unknown(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/malformed_attribute.stderr b/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.stderr new file mode 100644 index 0000000000000..319d45c88c429 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/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` attribute + --> $DIR/malformed_attribute.rs:2:1 + | +LL | #[diagnostic::on_unknown] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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` attribute + --> $DIR/malformed_attribute.rs:6:26 + | +LL | #[diagnostic::on_unknown(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:41 + | +LL | #[diagnostic::on_unknown(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:43 + | +LL | #[diagnostic::on_unknown(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/multiple_errors.rs b/tests/ui/diagnostic_namespace/on_unknown/multiple_errors.rs new file mode 100644 index 0000000000000..3ccf2fc5f6cab --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/multiple_errors.rs @@ -0,0 +1,48 @@ +#![feature(diagnostic_on_unknown)] + +mod test1 { + #[diagnostic::on_unknown( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::vec::{NonExisting, Vec, Whatever}; + //~^ ERROR: custom message +} + +mod test2 { + #[diagnostic::on_unknown( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::{Whatever, vec::NonExisting, vec::Vec, *}; + //~^ ERROR: custom message +} + +mod test3 { + #[diagnostic::on_unknown( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::{ + string::String, + vec::{NonExisting, Vec}, + //~^ ERROR: custom message + }; +} + +mod test4 { + #[diagnostic::on_unknown( + 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/multiple_errors.stderr b/tests/ui/diagnostic_namespace/on_unknown/multiple_errors.stderr new file mode 100644 index 0000000000000..fcce77f6aebbe --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/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/unknown_import.rs b/tests/ui/diagnostic_namespace/on_unknown/unknown_import.rs new file mode 100644 index 0000000000000..f2b0f059bb0a8 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/unknown_import.rs @@ -0,0 +1,17 @@ +#![feature(diagnostic_on_unknown)] +pub mod foo { + pub struct Bar; +} + +#[diagnostic::on_unknown( + 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/unknown_import.stderr b/tests/ui/diagnostic_namespace/on_unknown/unknown_import.stderr new file mode 100644 index 0000000000000..a9867fd74bfb0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/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.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.rs new file mode 100644 index 0000000000000..11cc0d50e0c94 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.rs @@ -0,0 +1,8 @@ +#![deny(warnings)] + +#[diagnostic::on_unknown(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.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr new file mode 100644 index 0000000000000..f6d7ffadaceae --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr @@ -0,0 +1,22 @@ +error[E0432]: unresolved import `std::vec::NotExisting` + --> $DIR/feature-gate-diagnostic-on-unknown.rs:5:5 + | +LL | use std::vec::NotExisting; + | ^^^^^^^^^^^^^^^^^^^^^ no `NotExisting` in `vec` + +error: unknown diagnostic attribute + --> $DIR/feature-gate-diagnostic-on-unknown.rs:3:15 + | +LL | #[diagnostic::on_unknown(message = "Tada")] + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/feature-gate-diagnostic-on-unknown.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`.