Skip to content

Commit 6319918

Browse files
committed
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.
1 parent f824853 commit 6319918

37 files changed

Lines changed: 666 additions & 46 deletions

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate)
656656
AttributeParser::parse_limited(
657657
sess,
658658
&krate.attrs,
659-
sym::feature,
659+
&[sym::feature],
660660
DUMMY_SP,
661661
krate.id,
662662
Some(&features),

compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItem
2323
pub(crate) mod do_not_recommend;
2424
pub(crate) mod on_const;
2525
pub(crate) mod on_unimplemented;
26+
pub(crate) mod on_unknown_item;
2627

2728
#[derive(Copy, Clone)]
2829
pub(crate) enum Mode {
@@ -32,6 +33,8 @@ pub(crate) enum Mode {
3233
DiagnosticOnUnimplemented,
3334
/// `#[diagnostic::on_const]`
3435
DiagnosticOnConst,
36+
/// `#[diagnostic::on_unknown_item]`
37+
DiagnosticOnUnknownItem,
3538
}
3639

3740
fn merge_directives<S: Stage>(
@@ -113,6 +116,13 @@ fn parse_directive_items<'p, S: Stage>(
113116
span,
114117
);
115118
}
119+
Mode::DiagnosticOnUnknownItem => {
120+
cx.emit_lint(
121+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
122+
AttributeLintKind::MalformedOnUnknownItemdAttr { span },
123+
span,
124+
);
125+
}
116126
}
117127
continue;
118128
}}
@@ -132,7 +142,7 @@ fn parse_directive_items<'p, S: Stage>(
132142
Mode::RustcOnUnimplemented => {
133143
cx.emit_err(NoValueInOnUnimplemented { span: item.span() });
134144
}
135-
Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst => {
145+
Mode::DiagnosticOnUnimplemented | Mode::DiagnosticOnConst | Mode::DiagnosticOnUnknownItem => {
136146
cx.emit_lint(
137147
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
138148
AttributeLintKind::IgnoredDiagnosticOption {
@@ -163,7 +173,8 @@ fn parse_directive_items<'p, S: Stage>(
163173
Ok((f, warnings)) => {
164174
for warning in warnings {
165175
let (FormatWarning::InvalidSpecifier { span, .. }
166-
| FormatWarning::PositionalArgument { span, .. }) = warning;
176+
| FormatWarning::PositionalArgument { span, .. }
177+
| FormatWarning::DisallowedPlaceholder { span }) = warning;
167178
cx.emit_lint(
168179
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
169180
AttributeLintKind::MalformedDiagnosticFormat { warning },
@@ -304,6 +315,9 @@ pub(crate) fn parse_format_string(
304315
.map(|piece| match piece {
305316
RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)),
306317
RpfPiece::NextArgument(arg) => {
318+
if matches!(mode, Mode::DiagnosticOnUnknownItem) {
319+
warnings.push(FormatWarning::DisallowedPlaceholder { span });
320+
}
307321
warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal);
308322
let arg = parse_arg(&arg, mode, &mut warnings, span, parser.is_source_literal);
309323
Piece::Arg(arg)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use rustc_hir::attrs::diagnostic::Directive;
2+
use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES;
3+
4+
use crate::attributes::diagnostic::*;
5+
use crate::attributes::prelude::*;
6+
7+
#[derive(Default)]
8+
pub(crate) struct OnUnknownItemParser {
9+
span: Option<Span>,
10+
directive: Option<(Span, Directive)>,
11+
}
12+
13+
impl OnUnknownItemParser {
14+
fn parse<'sess, S: Stage>(
15+
&mut self,
16+
cx: &mut AcceptContext<'_, 'sess, S>,
17+
args: &ArgParser,
18+
mode: Mode,
19+
) {
20+
let span = cx.attr_span;
21+
self.span = Some(span);
22+
23+
let items = match args {
24+
ArgParser::List(items) if !items.is_empty() => items,
25+
ArgParser::NoArgs | ArgParser::List(_) => {
26+
cx.emit_lint(
27+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
28+
AttributeLintKind::MissingOptionsForOnUnknownItem,
29+
span,
30+
);
31+
return;
32+
}
33+
ArgParser::NameValue(_) => {
34+
cx.emit_lint(
35+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
36+
AttributeLintKind::MalformedOnUnknownItemdAttr { span },
37+
span,
38+
);
39+
return;
40+
}
41+
};
42+
43+
if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
44+
merge_directives(cx, &mut self.directive, (span, directive));
45+
};
46+
}
47+
}
48+
49+
impl<S: Stage> AttributeParser<S> for OnUnknownItemParser {
50+
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
51+
&[sym::diagnostic, sym::on_unknown_item],
52+
template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]),
53+
|this, cx, args| {
54+
this.parse(cx, args, Mode::DiagnosticOnUnknownItem);
55+
},
56+
)];
57+
//FIXME attribute is not parsed for non-traits but diagnostics are issued in `check_attr.rs`
58+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
59+
60+
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
61+
if let Some(span) = self.span {
62+
Some(AttributeKind::OnUnknownItem {
63+
span,
64+
directive: self.directive.map(|d| Box::new(d.1)),
65+
})
66+
} else {
67+
None
68+
}
69+
}
70+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::attributes::deprecation::*;
3030
use crate::attributes::diagnostic::do_not_recommend::*;
3131
use crate::attributes::diagnostic::on_const::*;
3232
use crate::attributes::diagnostic::on_unimplemented::*;
33+
use crate::attributes::diagnostic::on_unknown_item::*;
3334
use crate::attributes::doc::*;
3435
use crate::attributes::dummy::*;
3536
use crate::attributes::inline::*;
@@ -150,6 +151,7 @@ attribute_parsers!(
150151
NakedParser,
151152
OnConstParser,
152153
OnUnimplementedParser,
154+
OnUnknownItemParser,
153155
RustcAlignParser,
154156
RustcAlignStaticParser,
155157
RustcCguTestAttributeParser,

compiler/rustc_attr_parsing/src/interface.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub struct AttributeParser<'sess, S: Stage = Late> {
2929
/// *Only* parse attributes with this symbol.
3030
///
3131
/// Used in cases where we want the lowering infrastructure for parse just a single attribute.
32-
parse_only: Option<Symbol>,
32+
parse_only: Option<&'static [Symbol]>,
3333
}
3434

3535
impl<'sess> AttributeParser<'sess, Early> {
@@ -50,7 +50,7 @@ impl<'sess> AttributeParser<'sess, Early> {
5050
pub fn parse_limited(
5151
sess: &'sess Session,
5252
attrs: &[ast::Attribute],
53-
sym: Symbol,
53+
sym: &'static [Symbol],
5454
target_span: Span,
5555
target_node_id: NodeId,
5656
features: Option<&'sess Features>,
@@ -71,7 +71,7 @@ impl<'sess> AttributeParser<'sess, Early> {
7171
pub fn parse_limited_should_emit(
7272
sess: &'sess Session,
7373
attrs: &[ast::Attribute],
74-
sym: Symbol,
74+
sym: &'static [Symbol],
7575
target_span: Span,
7676
target_node_id: NodeId,
7777
features: Option<&'sess Features>,
@@ -101,7 +101,7 @@ impl<'sess> AttributeParser<'sess, Early> {
101101
pub fn parse_limited_all(
102102
sess: &'sess Session,
103103
attrs: &[ast::Attribute],
104-
parse_only: Option<Symbol>,
104+
parse_only: Option<&'static [Symbol]>,
105105
target: Target,
106106
target_span: Span,
107107
target_node_id: NodeId,
@@ -275,7 +275,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
275275
for attr in attrs {
276276
// If we're only looking for a single attribute, skip all the ones we don't care about.
277277
if let Some(expected) = self.parse_only {
278-
if !attr.has_name(expected) {
278+
if !attr.path_matches(expected) {
279279
continue;
280280
}
281281
}

compiler/rustc_builtin_macros/src/deriving/generic/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ impl<'a> TraitDef<'a> {
493493
match item {
494494
Annotatable::Item(item) => {
495495
let is_packed = matches!(
496-
AttributeParser::parse_limited(cx.sess, &item.attrs, sym::repr, item.span, item.id, None),
496+
AttributeParser::parse_limited(cx.sess, &item.attrs, &[sym::repr], item.span, item.id, None),
497497
Some(Attribute::Parsed(AttributeKind::Repr { reprs, .. })) if reprs.iter().any(|(x, _)| matches!(x, ReprPacked(..)))
498498
);
499499

compiler/rustc_builtin_macros/src/proc_macro_harness.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl<'a> CollectProcMacros<'a> {
108108
})) = AttributeParser::parse_limited(
109109
self.session,
110110
slice::from_ref(attr),
111-
sym::proc_macro_derive,
111+
&[sym::proc_macro_derive],
112112
item.span,
113113
item.node_id(),
114114
None,

compiler/rustc_builtin_macros/src/test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
484484
AttributeParser::parse_limited(
485485
cx.sess,
486486
&i.attrs,
487-
sym::should_panic,
487+
&[sym::should_panic],
488488
i.span,
489489
i.node_id(),
490490
None,

compiler/rustc_builtin_macros/src/test_harness.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ fn get_test_runner(sess: &Session, features: &Features, krate: &ast::Crate) -> O
391391
match AttributeParser::parse_limited(
392392
sess,
393393
&krate.attrs,
394-
sym::test_runner,
394+
&[sym::test_runner],
395395
krate.spans.inner_span,
396396
krate.id,
397397
Some(features),

compiler/rustc_expand/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) -
5353
AttributeParser::parse_limited(
5454
sess,
5555
krate_attrs,
56-
sym::feature,
56+
&[sym::feature],
5757
DUMMY_SP,
5858
DUMMY_NODE_ID,
5959
Some(&features),

0 commit comments

Comments
 (0)