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.
This commit is contained in:
Georg Semmler
2025-02-09 18:29:32 +01:00
parent dd82fd2034
commit 6e5fc9075c
38 changed files with 771 additions and 34 deletions
@@ -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),
@@ -24,6 +24,7 @@
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<S: Stage>(
@@ -122,6 +125,13 @@ fn parse_directive_items<'p, S: Stage>(
span,
);
}
Mode::DiagnosticOnUnknownItem => {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::MalformedOnUnknownItemdAttr { 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::DiagnosticOnUnknownItem => {
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::DiagnosticOnUnknownItem) {
warnings.push(FormatWarning::DisallowedPlaceholder { span });
return FormatArg::AsIs(sym::empty_braces);
}
match arg.position {
// Something like "hello {name}"
@@ -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<Span>,
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<S: Stage> AttributeParser<S> for OnUnknownItemParser {
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
&[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<AttributeKind> {
if let Some(span) = self.span {
Some(AttributeKind::OnUnknownItem {
span,
directive: self.directive.map(|d| Box::new(d.1)),
})
} else {
None
}
}
}
@@ -32,6 +32,7 @@
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::*;
@@ -154,6 +155,7 @@ mod late {
OnConstParser,
OnMoveParser,
OnUnimplementedParser,
OnUnknownItemParser,
RustcAlignParser,
RustcAlignStaticParser,
RustcCguTestAttributeParser,
+5 -5
View File
@@ -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<Symbol>,
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 @@ pub fn parse_limited(
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 @@ pub fn parse_limited_should_emit(
pub fn parse_limited_all(
sess: &'sess Session,
attrs: &[ast::Attribute],
parse_only: Option<Symbol>,
parse_only: Option<&'static [Symbol]>,
target: Target,
target_span: Span,
target_node_id: NodeId,
@@ -272,7 +272,7 @@ pub fn parse_attribute_list(
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;
}
}
@@ -493,7 +493,7 @@ pub(crate) fn expand_ext(
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(..)))
);
@@ -108,7 +108,7 @@ fn collect_custom_derive(
})) = AttributeParser::parse_limited(
self.session,
slice::from_ref(attr),
sym::proc_macro_derive,
&[sym::proc_macro_derive],
item.span,
item.node_id(),
None,
+1 -1
View File
@@ -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,
@@ -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),
+1 -1
View File
@@ -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),
@@ -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,
}
}
+2
View File
@@ -474,6 +474,8 @@ pub fn internal(&self, feature: Symbol) -> bool {
(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_item, "CURRENT_RUSTC_VERSION", Some(152900)),
/// Allows `#[doc(cfg(...))]`.
(unstable, doc_cfg, "1.21.0", Some(43781)),
/// Allows `#[doc(masked)]`.
@@ -1192,6 +1192,14 @@ pub enum AttributeKind {
/// None if the directive was malformed in some way.
directive: Option<Box<Directive>>,
},
/// Represents `#[diagnostic::on_unknown_item]`
OnUnknownItem {
span: Span,
/// None if the directive was malformed in some way.
directive: Option<Box<Directive>>,
},
/// Represents `#[optimize(size|speed)]`
Optimize(OptimizeAttr, Span),
@@ -79,6 +79,7 @@ pub fn encode_cross_crate(&self) -> EncodeCrossCrate {
OnConst { .. } => Yes,
OnMove { .. } => Yes,
OnUnimplemented { .. } => Yes,
OnUnknownItem { .. } => Yes,
Optimize(..) => No,
PanicRuntime => No,
PatchableFunctionEntry { .. } => Yes,
+3 -3
View File
@@ -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::<Early>::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,
+1 -1
View File
@@ -312,7 +312,7 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
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()),
@@ -179,6 +179,9 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
&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)
}
@@ -189,6 +192,9 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
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 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
&AttributeLintKind::MissingOptionsForOnMove => {
lints::MissingOptionsForOnMoveAttr.into_diag(dcx, level)
}
&AttributeLintKind::MissingOptionsForOnUnknownItem => {
lints::MissingOptionsForOnUnknownItemAttr.into_diag(dcx, level)
}
}
}
}
+18
View File
@@ -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_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")]
@@ -3589,6 +3599,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")]
+1 -1
View File
@@ -145,7 +145,7 @@ fn check_case(&self, cx: &EarlyContext<'_>, sort: &str, ident: &Ident) {
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)
);
+5
View File
@@ -736,6 +736,9 @@ pub enum AttributeLintKind {
MalformedOnUnimplementedAttr {
span: Span,
},
MalformedOnUnknownItemdAttr {
span: Span,
},
MalformedOnConstAttr {
span: Span,
},
@@ -757,6 +760,7 @@ pub enum AttributeLintKind {
},
MissingOptionsForOnUnimplemented,
MissingOptionsForOnConst,
MissingOptionsForOnUnknownItem,
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<Ident>;
+21
View File
@@ -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_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,
@@ -219,6 +226,7 @@ fn check_attributes(
},
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::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(AttributeKind::OnMove { span, directive }) => {
self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref())
@@ -727,6 +735,19 @@ fn check_diagnostic_on_move(
}
}
/// 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 {
@@ -25,7 +25,7 @@ fn check_for_debugger_visualizer(
AttributeParser::parse_limited(
&self.sess,
attrs,
sym::debugger_visualizer,
&[sym::debugger_visualizer],
span,
node_id,
None,
@@ -32,7 +32,7 @@
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::{
@@ -545,6 +545,7 @@ fn add_import(
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);
@@ -1026,6 +1027,7 @@ fn build_reduced_graph_for_extern_crate(
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);
@@ -1121,7 +1123,7 @@ fn process_macro_use_imports(&mut self, item: &Item, module: Module<'ra>) -> boo
AttributeParser::parse_limited(
self.r.tcx.sess,
&item.attrs,
sym::macro_use,
&[sym::macro_use],
item.span,
item.id,
None,
@@ -1158,6 +1160,7 @@ fn process_macro_use_imports(&mut self, item: &Item, module: Module<'ra>) -> boo
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),
})
};
@@ -1329,6 +1332,7 @@ fn define_macro(&mut self, item: &ast::Item) -> MacroRulesScopeRef<'ra> {
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);
+78 -7
View File
@@ -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 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct OnUnknownItemData {
directive: Directive,
}
impl OnUnknownItemData {
pub(crate) fn from_attrs<'tcx>(tcx: TyCtxt<'tcx>, item: &Item) -> Option<OnUnknownItemData> {
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<OnUnknownItemData>,
}
/// All imports are unique and allocated on a same arena,
@@ -284,6 +317,7 @@ struct UnresolvedImportError {
segment: Option<Symbol>,
/// comes from `PathRes::Failed { module }`
module: Option<DefId>,
on_unknown_item_attr: Option<OnUnknownItemData>,
}
// Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;`
@@ -700,6 +734,7 @@ pub(crate) fn finalize_imports(&mut self) {
candidates: None,
segment: None,
module: None,
on_unknown_item_attr: import.on_unknown_item_attr.clone(),
};
errors.push((*import, err))
}
@@ -822,11 +857,41 @@ fn throw_unresolved_import_error(
format!("`{path}`")
})
.collect::<Vec<_>>();
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_item()
&& let Some(directive) = errors[0].1.on_unknown_item_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 @@ fn throw_unresolved_import_error(
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 @@ fn finalize_import(&mut self, import: Import<'ra>) -> Option<UnresolvedImportErr
candidates: None,
segment: Some(segment_name),
module,
on_unknown_item_attr: import.on_unknown_item_attr.clone(),
},
None => UnresolvedImportError {
span,
@@ -1110,6 +1178,7 @@ fn finalize_import(&mut self, import: Import<'ra>) -> Option<UnresolvedImportErr
candidates: None,
segment: Some(segment_name),
module,
on_unknown_item_attr: import.on_unknown_item_attr.clone(),
},
};
return Some(err);
@@ -1152,6 +1221,7 @@ fn finalize_import(&mut self, import: Import<'ra>) -> Option<UnresolvedImportErr
candidates: None,
segment: None,
module: None,
on_unknown_item_attr: None,
});
}
if let Some(max_vis) = max_vis.get()
@@ -1374,6 +1444,7 @@ fn finalize_import(&mut self, import: Import<'ra>) -> Option<UnresolvedImportErr
}
}),
segment: Some(ident.name),
on_unknown_item_attr: import.on_unknown_item_attr.clone(),
})
} else {
// `resolve_ident_in_module` reported a privacy error.
+16 -6
View File
@@ -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 @@ fn smart_resolve_macro_path(
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::<Vec<_>>();
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(
+2
View File
@@ -799,6 +799,7 @@
diagnostic_namespace,
diagnostic_on_const,
diagnostic_on_move,
diagnostic_on_unknown_item,
dialect,
direct,
discriminant_kind,
@@ -1418,6 +1419,7 @@
on_const,
on_move,
on_unimplemented,
on_unknown_item,
opaque,
opaque_module_name_placeholder: "<opaque>",
ops,
@@ -0,0 +1,52 @@
//@ run-pass
#![allow(dead_code, unused_imports)]
#![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 std as other_std;
#[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() {}
@@ -0,0 +1,103 @@
warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements
--> $DIR/incorrect-locations.rs:5:1
|
LL | #[diagnostic::on_unknown_item(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_item]` can only be applied to `use` statements
--> $DIR/incorrect-locations.rs:9: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:13: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:17: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:21: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:25: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:29: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:33: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:37: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:41: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:45:1
|
LL | #[diagnostic::on_unknown_item(message = "foo")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL |
LL | impl Trait for i32 {}
| ------------------ not an import
warning: 11 warnings emitted
@@ -0,0 +1,33 @@
#![feature(diagnostic_on_unknown_item)]
#[diagnostic::on_unknown_item(message = "foo {}")]
//~^ WARN: format arguments are not allowed here
use std::does_not_exist;
//~^ ERROR: foo {}
#[diagnostic::on_unknown_item(message = "foo {A}")]
//~^ WARN: format arguments are not allowed here
use std::does_not_exist2;
//~^ ERROR: foo {}
#[diagnostic::on_unknown_item(label = "foo {}")]
//~^ WARN: format arguments are not allowed here
use std::does_not_exist3;
//~^ ERROR: unresolved import `std::does_not_exist3`
#[diagnostic::on_unknown_item(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_item(note = "foo {}")]
//~^ WARN: format arguments are not allowed here
use std::does_not_exist5;
//~^ ERROR: unresolved import `std::does_not_exist5`
#[diagnostic::on_unknown_item(note = "foo {A}")]
//~^ WARN: format arguments are not allowed here
use std::does_not_exist6;
//~^ ERROR: unresolved import `std::does_not_exist6`
fn main() {}
@@ -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:47
|
LL | #[diagnostic::on_unknown_item(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:47
|
LL | #[diagnostic::on_unknown_item(message = "foo {A}")]
| ^
|
= help: consider removing this format argument
warning: format arguments are not allowed here
--> $DIR/incorrect_format_string.rs:13:45
|
LL | #[diagnostic::on_unknown_item(label = "foo {}")]
| ^
|
= help: consider removing this format argument
warning: format arguments are not allowed here
--> $DIR/incorrect_format_string.rs:18:45
|
LL | #[diagnostic::on_unknown_item(label = "foo {A}")]
| ^
|
= help: consider removing this format argument
warning: format arguments are not allowed here
--> $DIR/incorrect_format_string.rs:23:44
|
LL | #[diagnostic::on_unknown_item(note = "foo {}")]
| ^
|
= help: consider removing this format argument
warning: format arguments are not allowed here
--> $DIR/incorrect_format_string.rs:28:44
|
LL | #[diagnostic::on_unknown_item(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`.
@@ -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() {}
@@ -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`.
@@ -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() {}
@@ -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`.
@@ -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() {}
@@ -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`.
@@ -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() {}
@@ -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`.