From c87fc9eed84ab132e1912fed14c51727aedbe8b1 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Sun, 9 Feb 2025 18:29:32 +0100 Subject: [PATCH] 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_feature/src/builtin_attrs.rs | 1 + compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_passes/src/check_attr.rs | 111 +++++++++++++++++- .../rustc_resolve/src/build_reduced_graph.rs | 6 +- compiler/rustc_resolve/src/imports.rs | 103 ++++++++++++++-- compiler/rustc_resolve/src/macros.rs | 6 +- compiler/rustc_span/src/symbol.rs | 2 + .../on_unknown_item/incorrect-locations.rs | 47 ++++++++ .../incorrect-locations.stderr | 64 ++++++++++ .../on_unknown_item/malformed_attribute.rs | 19 +++ .../malformed_attribute.stderr | 48 ++++++++ .../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 ++++ 17 files changed, 547 insertions(+), 13 deletions(-) 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_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 2c2dae0fef52e..4ce88310a67bf 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -1585,6 +1585,7 @@ pub fn is_stable_diagnostic_attribute(sym: Symbol, features: &Features) -> bool match sym { sym::on_unimplemented | sym::do_not_recommend => true, sym::on_const => features.diagnostic_on_const(), + 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 36be4a0378060..3c9e621e78bf2 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -464,6 +464,8 @@ declare_features! ( (unstable, derive_from, "1.91.0", Some(144889)), /// Allows giving non-const impls custom diagnostic messages if attempted to be used as const (unstable, diagnostic_on_const, "1.93.0", Some(143874)), + /// 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_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index ec0306371205b..98adbd5e1e385 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -43,8 +43,8 @@ use rustc_middle::{bug, span_bug}; use rustc_session::config::CrateType; use rustc_session::lint; use rustc_session::lint::builtin::{ - CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MISPLACED_DIAGNOSTIC_ATTRIBUTES, - UNUSED_ATTRIBUTES, + CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_ATTRIBUTES, + MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES, }; use rustc_session::parse::feature_err; use rustc_span::edition::Edition; @@ -74,6 +74,38 @@ struct DiagnosticOnConstOnlyForNonConstTraitImpls { item_span: Span, } +#[derive(LintDiagnostic)] +#[diag("`#[diagnostic::on_unknown_item]` can only be applied to `use` statements")] +struct DiagnosticOnUnknownItemOnlyForImports; + +#[derive(LintDiagnostic)] +#[diag("malformed `#[diagnostic::on_unknown_item]` attribute")] +#[help("at least one of the following options is required: `message`, `label` or `note`")] +pub(crate) struct MalformedOnUnknownItemAttr { + #[label("the `#[diagnostic::on_unknown_item]` attribute expects at least one option")] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[diag("`{$option_name}` is ignored due to previous definition of `{$option_name}`")] +#[help("consider removing the second `{$option_name}` as it is ignored anyway")] +pub(crate) struct IgnoredDiagnosticOption { + pub option_name: &'static str, + #[label("`{$option_name}` is already declared here")] + pub span: Span, + #[label("`{$option_name}` is first declared here")] + pub prev_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag("unknown option `{$option_name}` for the `#[diagnostic::on_unknown_item]` attribute")] +#[help("only `message`, `note` and `label` are allowed as options")] +pub(crate) struct UnknownOptionForOnUnknownItemAttr { + pub option_name: String, + #[label("`{$option_name}` is an invalid option")] + pub 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, @@ -388,6 +420,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { [sym::diagnostic, sym::on_const, ..] => { self.check_diagnostic_on_const(attr.span(), hir_id, target, item) } + [sym::diagnostic, sym::on_unknown_item, ..] => { + self.check_diagnostic_on_unknown_item(attr.span(), hir_id, target, attr) + } [sym::autodiff_forward, ..] | [sym::autodiff_reverse, ..] => { self.check_autodiff(hir_id, attr, span, target) } @@ -619,6 +654,78 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + /// Checks if `#[diagnostic::on_unknown_item]` is applied to an import definition + fn check_diagnostic_on_unknown_item( + &self, + attr_span: Span, + hir_id: HirId, + target: Target, + attr: &Attribute, + ) { + if !matches!(target, Target::Use) { + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + attr_span, + DiagnosticOnUnknownItemOnlyForImports, + ); + } + if let Some(meta) = attr.meta_item_list() { + let mut message = None; + let mut label = None; + for item in meta { + if item.has_name(sym::message) { + if let Some(message_span) = message { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + item.span(), + IgnoredDiagnosticOption { + option_name: "message", + span: item.span(), + prev_span: message_span, + }, + ); + } + message = Some(item.span()); + } else if item.has_name(sym::label) { + if let Some(label_span) = label { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + item.span(), + IgnoredDiagnosticOption { + option_name: "label", + span: item.span(), + prev_span: label_span, + }, + ); + } + label = Some(item.span()); + } else if item.has_name(sym::note) { + // accept any number of notes + } else { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + item.span(), + UnknownOptionForOnUnknownItemAttr { + option_name: item.name().map(|s| s.to_string()).unwrap_or_default(), + span: item.span(), + }, + ); + } + } + } else { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + attr_span, + MalformedOnUnknownItemAttr { span: attr_span }, + ); + } + } + /// Checks if `#[diagnostic::on_const]` is applied to a trait impl fn check_diagnostic_on_const( &self, diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index f0dffd8829da3..b24227222c03e 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::{ @@ -538,6 +538,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { root_id, vis, vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(&item.attrs), }); self.r.indeterminate_imports.push(import); @@ -1035,6 +1036,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(&item.attrs), }); if used { self.r.import_use_map.insert(import, Used::Other); @@ -1167,6 +1169,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(&item.attrs), }) }; @@ -1338,6 +1341,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(&item.attrs), }); 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 4e7622d08462e..c206acb90a7e2 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -2,7 +2,7 @@ use std::mem; -use rustc_ast::NodeId; +use rustc_ast::{NodeId, ast}; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_errors::codes::*; @@ -140,6 +140,57 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { } } +#[derive(Debug, Clone, Default)] +pub(crate) struct OnUnknownItemData { + pub(crate) message: Option, + pub(crate) label: Option, + pub(crate) notes: Option>, +} + +impl OnUnknownItemData { + pub(crate) fn from_attrs(attrs: &[ast::Attribute]) -> Option { + // the attribute syntax is checked in the check_attr + // ast pass, so we just consume any valid + // options here and ignore everything else + let mut out = OnUnknownItemData::default(); + for attr in + attrs.iter().filter(|a| a.path_matches(&[sym::diagnostic, sym::on_unknown_item])) + { + if let Some(meta) = attr.meta_item_list() { + for item in meta { + if item.has_name(sym::message) { + if out.message.is_none() + && let Some(message) = item.value_str() + { + out.message = Some(message.as_str().to_owned()); + } + } else if item.has_name(sym::label) { + if out.label.is_none() + && let Some(label) = item.value_str() + { + out.label = Some(label.as_str().to_owned()); + } + } else if item.has_name(sym::note) { + if let Some(note) = item.value_str() { + out.notes = Some(out.notes.unwrap_or_default()); + out.notes + .as_mut() + .expect("We initialized it above") + .push(note.as_str().to_owned()); + } + } + } + } + } + + if out.message.is_none() && out.label.is_none() && out.notes.is_none() { + None + } else { + Some(out) + } + } +} + /// One import. #[derive(Debug, Clone)] pub(crate) struct ImportData<'ra> { @@ -186,6 +237,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 +340,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;` @@ -693,6 +750,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)) } @@ -815,19 +873,45 @@ 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.message.take()) + { + 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_mut().and_then(|a| a.notes.take()) + { + for note in notes { + diag.note(note); + } + } 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.label.take()) + .or(err.label.clone()) + { + diag.span_label(err.span, label); + } else if let Some(label) = err.label { diag.span_label(err.span, label); } @@ -1088,6 +1172,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, @@ -1097,6 +1182,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); @@ -1139,6 +1225,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() @@ -1339,7 +1426,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), @@ -1358,6 +1444,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 e0973271da52d..9c0e9edbfc990 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -701,12 +701,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } const DIAG_ATTRS: &[Symbol] = - &[sym::on_unimplemented, sym::do_not_recommend, sym::on_const]; + &[sym::on_unimplemented, sym::do_not_recommend, sym::on_const, sym::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) + && (!DIAG_ATTRS.contains(&attribute.ident.name) + || (attribute.ident.name == sym::on_unknown_item + && !self.tcx.features().diagnostic_on_unknown_item())) { let span = attribute.span(); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ade60d17f2b52..e7bb84240a08a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -907,6 +907,7 @@ symbols! { diagnostic, diagnostic_namespace, diagnostic_on_const, + diagnostic_on_unknown_item, dialect, direct, discriminant_kind, @@ -1639,6 +1640,7 @@ symbols! { on, on_const, 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..cdb5d01532a05 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs @@ -0,0 +1,47 @@ +#![feature(diagnostic_on_unknown_item)] +//@check-pass + +#[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..afa92abeeb736 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr @@ -0,0 +1,64 @@ +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:4:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: 10 warnings emitted + 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..5316893db2d1f --- /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 malformed `#[diagnostic::on_unknown_item]` attribute +use std::str::FromStr; + +#[diagnostic::on_unknown_item(foo = "bar", message = "foo")] +//~^WARN unknown option `foo` for the `#[diagnostic::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..3ec59720c23a0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr @@ -0,0 +1,48 @@ +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: malformed `#[diagnostic::on_unknown_item]` attribute + --> $DIR/malformed_attribute.rs:2:1 + | +LL | #[diagnostic::on_unknown_item] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `#[diagnostic::on_unknown_item]` attribute expects at least one option + | + = help: at least one of the following options is required: `message`, `label` or `note` + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: unknown option `foo` for the `#[diagnostic::on_unknown_item]` attribute + --> $DIR/malformed_attribute.rs:6:31 + | +LL | #[diagnostic::on_unknown_item(foo = "bar", message = "foo")] + | ^^^^^^^^^^^ `foo` is an invalid option + | + = 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 already declared here + | | + | `label` is first declared here + | + = help: consider removing the second `label` as it is ignored anyway + +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 already declared here + | | + | `message` is first declared here + | + = help: consider removing the second `message` as it is ignored anyway + +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`.