Skip to content

Commit dbf9492

Browse files
Rollup merge of #152901 - weiznich:feature/on_unknown_item, r=jdonszelmann
Introduce a `#[diagnostic::on_unknown]` attribute This PR introduces a `#[diagnostic::on_unknown]` 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. related #152900 and #128674
2 parents 8317fef + 97da819 commit dbf9492

38 files changed

Lines changed: 774 additions & 34 deletions

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate)
649649
AttributeParser::parse_limited(
650650
sess,
651651
&krate.attrs,
652-
sym::feature,
652+
&[sym::feature],
653653
DUMMY_SP,
654654
krate.id,
655655
Some(&features),

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub(crate) mod do_not_recommend;
2424
pub(crate) mod on_const;
2525
pub(crate) mod on_move;
2626
pub(crate) mod on_unimplemented;
27+
pub(crate) mod on_unknown;
2728

2829
#[derive(Copy, Clone)]
2930
pub(crate) enum Mode {
@@ -35,6 +36,8 @@ pub(crate) enum Mode {
3536
DiagnosticOnConst,
3637
/// `#[diagnostic::on_move]`
3738
DiagnosticOnMove,
39+
/// `#[diagnostic::on_unknown]`
40+
DiagnosticOnUnknown,
3841
}
3942

4043
fn merge_directives<S: Stage>(
@@ -122,6 +125,13 @@ fn parse_directive_items<'p, S: Stage>(
122125
span,
123126
);
124127
}
128+
Mode::DiagnosticOnUnknown => {
129+
cx.emit_lint(
130+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
131+
AttributeLintKind::MalformedOnUnknownAttr { span },
132+
span,
133+
);
134+
}
125135
}
126136
continue;
127137
}}
@@ -140,7 +150,7 @@ fn parse_directive_items<'p, S: Stage>(
140150
Mode::RustcOnUnimplemented => {
141151
cx.emit_err(NoValueInOnUnimplemented { span: item.span() });
142152
}
143-
Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove => {
153+
Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnknown => {
144154
cx.emit_lint(
145155
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
146156
AttributeLintKind::IgnoredDiagnosticOption {
@@ -176,7 +186,8 @@ fn parse_directive_items<'p, S: Stage>(
176186
Ok((f, warnings)) => {
177187
for warning in warnings {
178188
let (FormatWarning::InvalidSpecifier { span, .. }
179-
| FormatWarning::PositionalArgument { span, .. }) = warning;
189+
| FormatWarning::PositionalArgument { span, .. }
190+
| FormatWarning::DisallowedPlaceholder { span }) = warning;
180191
cx.emit_lint(
181192
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
182193
AttributeLintKind::MalformedDiagnosticFormat { warning },
@@ -326,6 +337,10 @@ fn parse_arg(
326337
is_source_literal: bool,
327338
) -> FormatArg {
328339
let span = slice_span(input_span, arg.position_span.clone(), is_source_literal);
340+
if matches!(mode, Mode::DiagnosticOnUnknown) {
341+
warnings.push(FormatWarning::DisallowedPlaceholder { span });
342+
return FormatArg::AsIs(sym::empty_braces);
343+
}
329344

330345
match arg.position {
331346
// Something like "hello {name}"
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 OnUnknownParser {
9+
span: Option<Span>,
10+
directive: Option<(Span, Directive)>,
11+
}
12+
13+
impl OnUnknownParser {
14+
fn parse<'sess, S: Stage>(
15+
&mut self,
16+
cx: &mut AcceptContext<'_, 'sess, S>,
17+
args: &ArgParser,
18+
mode: Mode,
19+
) {
20+
if !cx.features().diagnostic_on_unknown() {
21+
return;
22+
}
23+
let span = cx.attr_span;
24+
self.span = Some(span);
25+
26+
let items = match args {
27+
ArgParser::List(items) if !items.is_empty() => items,
28+
ArgParser::NoArgs | ArgParser::List(_) => {
29+
cx.emit_lint(
30+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
31+
AttributeLintKind::MissingOptionsForOnUnknown,
32+
span,
33+
);
34+
return;
35+
}
36+
ArgParser::NameValue(_) => {
37+
cx.emit_lint(
38+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
39+
AttributeLintKind::MalformedOnUnknownAttr { span },
40+
span,
41+
);
42+
return;
43+
}
44+
};
45+
46+
if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
47+
merge_directives(cx, &mut self.directive, (span, directive));
48+
};
49+
}
50+
}
51+
52+
impl<S: Stage> AttributeParser<S> for OnUnknownParser {
53+
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
54+
&[sym::diagnostic, sym::on_unknown],
55+
template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]),
56+
|this, cx, args| {
57+
this.parse(cx, args, Mode::DiagnosticOnUnknown);
58+
},
59+
)];
60+
//FIXME attribute is not parsed for non-use statements but diagnostics are issued in `check_attr.rs`
61+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
62+
63+
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
64+
if let Some(span) = self.span {
65+
Some(AttributeKind::OnUnknown {
66+
span,
67+
directive: self.directive.map(|d| Box::new(d.1)),
68+
})
69+
} else {
70+
None
71+
}
72+
}
73+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::attributes::diagnostic::do_not_recommend::*;
3232
use crate::attributes::diagnostic::on_const::*;
3333
use crate::attributes::diagnostic::on_move::*;
3434
use crate::attributes::diagnostic::on_unimplemented::*;
35+
use crate::attributes::diagnostic::on_unknown::*;
3536
use crate::attributes::doc::*;
3637
use crate::attributes::dummy::*;
3738
use crate::attributes::inline::*;
@@ -154,6 +155,7 @@ attribute_parsers!(
154155
OnConstParser,
155156
OnMoveParser,
156157
OnUnimplementedParser,
158+
OnUnknownParser,
157159
RustcAlignParser,
158160
RustcAlignStaticParser,
159161
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>,
@@ -72,7 +72,7 @@ impl<'sess> AttributeParser<'sess, Early> {
7272
pub fn parse_limited_should_emit(
7373
sess: &'sess Session,
7474
attrs: &[ast::Attribute],
75-
sym: Symbol,
75+
sym: &'static [Symbol],
7676
target_span: Span,
7777
target_node_id: NodeId,
7878
target: Target,
@@ -103,7 +103,7 @@ impl<'sess> AttributeParser<'sess, Early> {
103103
pub fn parse_limited_all(
104104
sess: &'sess Session,
105105
attrs: &[ast::Attribute],
106-
parse_only: Option<Symbol>,
106+
parse_only: Option<&'static [Symbol]>,
107107
target: Target,
108108
target_span: Span,
109109
target_node_id: NodeId,
@@ -272,7 +272,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
272272
for attr in attrs {
273273
// If we're only looking for a single attribute, skip all the ones we don't care about.
274274
if let Some(expected) = self.parse_only {
275-
if !attr.has_name(expected) {
275+
if !attr.path_matches(expected) {
276276
continue;
277277
}
278278
}

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
@@ -483,7 +483,7 @@ fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
483483
AttributeParser::parse_limited(
484484
cx.sess,
485485
&i.attrs,
486-
sym::should_panic,
486+
&[sym::should_panic],
487487
i.span,
488488
i.node_id(),
489489
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
@@ -54,7 +54,7 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) -
5454
AttributeParser::parse_limited(
5555
sess,
5656
krate_attrs,
57-
sym::feature,
57+
&[sym::feature],
5858
DUMMY_SP,
5959
DUMMY_NODE_ID,
6060
Some(&features),

0 commit comments

Comments
 (0)