Skip to content

Commit 2fed49b

Browse files
committed
add on_missing_args
1 parent 5a0d572 commit 2fed49b

42 files changed

Lines changed: 549 additions & 12 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItem
2222

2323
pub(crate) mod do_not_recommend;
2424
pub(crate) mod on_const;
25+
pub(crate) mod on_missing_args;
2526
pub(crate) mod on_move;
2627
pub(crate) mod on_unimplemented;
2728

@@ -33,6 +34,8 @@ pub(crate) enum Mode {
3334
DiagnosticOnUnimplemented,
3435
/// `#[diagnostic::on_const]`
3536
DiagnosticOnConst,
37+
/// `#[diagnostic::on_missing_args]`
38+
DiagnosticOnMissingArgs,
3639
/// `#[diagnostic::on_move]`
3740
DiagnosticOnMove,
3841
}
@@ -115,6 +118,13 @@ fn parse_directive_items<'p, S: Stage>(
115118
span,
116119
);
117120
}
121+
Mode::DiagnosticOnMissingArgs => {
122+
cx.emit_lint(
123+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
124+
AttributeLintKind::MalformedOnMissingArgsAttr { span },
125+
span,
126+
);
127+
}
118128
Mode::DiagnosticOnMove => {
119129
cx.emit_lint(
120130
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
@@ -140,7 +150,10 @@ 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
154+
| Mode::DiagnosticOnConst
155+
| Mode::DiagnosticOnMissingArgs
156+
| Mode::DiagnosticOnMove => {
144157
cx.emit_lint(
145158
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
146159
AttributeLintKind::IgnoredDiagnosticOption {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use rustc_hir::attrs::diagnostic::Directive;
2+
use rustc_hir::lints::AttributeLintKind;
3+
use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES;
4+
5+
use crate::attributes::diagnostic::*;
6+
use crate::attributes::prelude::*;
7+
8+
#[derive(Default)]
9+
pub(crate) struct OnMissingArgsParser {
10+
span: Option<Span>,
11+
directive: Option<(Span, Directive)>,
12+
}
13+
14+
impl<S: Stage> AttributeParser<S> for OnMissingArgsParser {
15+
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
16+
&[sym::diagnostic, sym::on_missing_args],
17+
template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]),
18+
|this, cx, args| {
19+
if !cx.features().diagnostic_on_missing_args() {
20+
return;
21+
}
22+
23+
let span = cx.attr_span;
24+
this.span = Some(span);
25+
26+
// Lint emitted in `check_attr.rs`.
27+
if !matches!(cx.target, Target::MacroDef) {
28+
return;
29+
}
30+
31+
let items = match args {
32+
ArgParser::List(items) if items.len() != 0 => items,
33+
ArgParser::NoArgs | ArgParser::List(_) => {
34+
cx.emit_lint(
35+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
36+
AttributeLintKind::MissingOptionsForOnMissingArgs,
37+
span,
38+
);
39+
return;
40+
}
41+
ArgParser::NameValue(_) => {
42+
cx.emit_lint(
43+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
44+
AttributeLintKind::MalformedOnMissingArgsAttr { span },
45+
span,
46+
);
47+
return;
48+
}
49+
};
50+
51+
let Some(directive) =
52+
parse_directive_items(cx, Mode::DiagnosticOnMissingArgs, items.mixed(), true)
53+
else {
54+
return;
55+
};
56+
merge_directives(cx, &mut this.directive, (span, directive));
57+
},
58+
)];
59+
60+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
61+
62+
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
63+
if let Some(span) = self.span {
64+
Some(AttributeKind::OnMissingArgs {
65+
span,
66+
directive: self.directive.map(|d| Box::new(d.1)),
67+
})
68+
} else {
69+
None
70+
}
71+
}
72+
}

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::debugger::*;
3030
use crate::attributes::deprecation::*;
3131
use crate::attributes::diagnostic::do_not_recommend::*;
3232
use crate::attributes::diagnostic::on_const::*;
33+
use crate::attributes::diagnostic::on_missing_args::*;
3334
use crate::attributes::diagnostic::on_move::*;
3435
use crate::attributes::diagnostic::on_unimplemented::*;
3536
use crate::attributes::doc::*;
@@ -154,6 +155,7 @@ attribute_parsers!(
154155
MacroUseParser,
155156
NakedParser,
156157
OnConstParser,
158+
OnMissingArgsParser,
157159
OnMoveParser,
158160
OnUnimplementedParser,
159161
RustcAlignParser,

compiler/rustc_expand/src/mbe/diagnostics.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::borrow::Cow;
33
use rustc_ast::token::{self, Token};
44
use rustc_ast::tokenstream::TokenStream;
55
use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage};
6+
use rustc_hir::attrs::diagnostic::{Directive, FormatArgs, OnUnimplementedNote};
67
use rustc_macros::Subdiagnostic;
78
use rustc_parse::parser::{Parser, Recovery, token_descr};
89
use rustc_session::parse::ParseSess;
@@ -32,6 +33,7 @@ pub(super) fn failed_to_match_macro(
3233
args: FailedMacro<'_>,
3334
body: &TokenStream,
3435
rules: &[MacroRule],
36+
on_missing_args: Option<&Directive>,
3537
) -> (Span, ErrorGuaranteed) {
3638
debug!("failed to match macro");
3739
let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) {
@@ -72,9 +74,20 @@ pub(super) fn failed_to_match_macro(
7274
};
7375

7476
let span = token.span.substitute_dummy(sp);
77+
let custom = if matches!(token.kind, token::Eof) {
78+
on_missing_args.map(on_missing_args_diagnostic)
79+
} else {
80+
None
81+
};
82+
let custom_message = custom.as_ref().and_then(|diag| diag.message.clone());
83+
let custom_label = custom.as_ref().and_then(|diag| diag.label.clone());
84+
let custom_notes = custom.map_or_else(Vec::new, |diag| diag.notes);
7585

76-
let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None));
77-
err.span_label(span, label);
86+
let mut err = match custom_message {
87+
Some(message) => psess.dcx().struct_span_err(span, message),
88+
None => psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)),
89+
};
90+
err.span_label(span, custom_label.unwrap_or_else(|| label.to_string()));
7891
if !def_head_span.is_dummy() {
7992
err.span_label(def_head_span, "when calling this macro");
8093
}
@@ -86,6 +99,9 @@ pub(super) fn failed_to_match_macro(
8699
} else {
87100
err.note(format!("while trying to match {remaining_matcher}"));
88101
}
102+
for note in custom_notes {
103+
err.note(note);
104+
}
89105

90106
if let MatcherLoc::Token { token: expected_token } = &remaining_matcher
91107
&& (matches!(expected_token.kind, token::OpenInvisible(_))
@@ -128,6 +144,22 @@ pub(super) fn failed_to_match_macro(
128144
(sp, guar)
129145
}
130146

147+
fn on_missing_args_diagnostic(directive: &Directive) -> OnUnimplementedNote {
148+
let args = FormatArgs {
149+
this: String::new(),
150+
trait_sugared: String::new(),
151+
item_context: "macro invocation",
152+
generic_args: Vec::new(),
153+
};
154+
155+
OnUnimplementedNote {
156+
message: directive.message.as_ref().map(|(_, message)| message.format(&args)),
157+
label: directive.label.as_ref().map(|(_, label)| label.format(&args)),
158+
notes: directive.notes.iter().map(|note| note.format(&args)).collect(),
159+
..Default::default()
160+
}
161+
}
162+
131163
/// The tracker used for the slow error path that collects useful info for diagnostics.
132164
struct CollectTrackerAndEmitter<'dcx, 'matcher> {
133165
dcx: DiagCtxtHandle<'dcx>,

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
1414
use rustc_errors::{Applicability, Diag, ErrorGuaranteed, MultiSpan};
1515
use rustc_feature::Features;
1616
use rustc_hir as hir;
17+
use rustc_hir::attrs::diagnostic::Directive;
1718
use rustc_hir::def::MacroKinds;
1819
use rustc_hir::find_attr;
1920
use rustc_lint_defs::builtin::{
@@ -164,6 +165,7 @@ pub struct MacroRulesMacroExpander {
164165
node_id: NodeId,
165166
name: Ident,
166167
span: Span,
168+
on_missing_args: Option<Directive>,
167169
transparency: Transparency,
168170
kinds: MacroKinds,
169171
rules: Vec<MacroRule>,
@@ -194,7 +196,8 @@ impl MacroRulesMacroExpander {
194196
) -> Result<TokenStream, ErrorGuaranteed> {
195197
// This is similar to `expand_macro`, but they have very different signatures, and will
196198
// diverge further once derives support arguments.
197-
let Self { name, ref rules, node_id, .. } = *self;
199+
let name = self.name;
200+
let rules = &self.rules;
198201
let psess = &cx.sess.psess;
199202

200203
if cx.trace_macros() {
@@ -220,8 +223,8 @@ impl MacroRulesMacroExpander {
220223
trace_macros_note(&mut cx.expansions, sp, msg);
221224
}
222225

223-
if is_defined_in_current_crate(node_id) {
224-
cx.resolver.record_macro_rule_usage(node_id, rule_index);
226+
if is_defined_in_current_crate(self.node_id) {
227+
cx.resolver.record_macro_rule_usage(self.node_id, rule_index);
225228
}
226229

227230
Ok(tts)
@@ -236,6 +239,7 @@ impl MacroRulesMacroExpander {
236239
FailedMacro::Derive,
237240
body,
238241
rules,
242+
self.on_missing_args.as_ref(),
239243
);
240244
cx.macro_error_and_trace_macros_diag();
241245
Err(guar)
@@ -260,6 +264,7 @@ impl TTMacroExpander for MacroRulesMacroExpander {
260264
self.transparency,
261265
input,
262266
&self.rules,
267+
self.on_missing_args.as_ref(),
263268
))
264269
}
265270
}
@@ -294,6 +299,7 @@ impl AttrProcMacro for MacroRulesMacroExpander {
294299
args,
295300
body,
296301
&self.rules,
302+
self.on_missing_args.as_ref(),
297303
)
298304
}
299305
}
@@ -355,7 +361,7 @@ impl<'matcher> Tracker<'matcher> for NoopTracker {
355361
}
356362

357363
/// Expands the rules based macro defined by `rules` for a given input `arg`.
358-
#[instrument(skip(cx, transparency, arg, rules))]
364+
#[instrument(skip(cx, transparency, arg, rules, on_missing_args))]
359365
fn expand_macro<'cx, 'a: 'cx>(
360366
cx: &'cx mut ExtCtxt<'_>,
361367
sp: Span,
@@ -365,6 +371,7 @@ fn expand_macro<'cx, 'a: 'cx>(
365371
transparency: Transparency,
366372
arg: TokenStream,
367373
rules: &'a [MacroRule],
374+
on_missing_args: Option<&Directive>,
368375
) -> Box<dyn MacResult + 'cx> {
369376
let psess = &cx.sess.psess;
370377

@@ -423,6 +430,7 @@ fn expand_macro<'cx, 'a: 'cx>(
423430
FailedMacro::Func,
424431
&arg,
425432
rules,
433+
on_missing_args,
426434
);
427435
cx.macro_error_and_trace_macros_diag();
428436
DummyResult::any(span, guar)
@@ -431,7 +439,7 @@ fn expand_macro<'cx, 'a: 'cx>(
431439
}
432440

433441
/// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`.
434-
#[instrument(skip(cx, transparency, args, body, rules))]
442+
#[instrument(skip(cx, transparency, args, body, rules, on_missing_args))]
435443
fn expand_macro_attr(
436444
cx: &mut ExtCtxt<'_>,
437445
sp: Span,
@@ -443,6 +451,7 @@ fn expand_macro_attr(
443451
args: TokenStream,
444452
body: TokenStream,
445453
rules: &[MacroRule],
454+
on_missing_args: Option<&Directive>,
446455
) -> Result<TokenStream, ErrorGuaranteed> {
447456
let psess = &cx.sess.psess;
448457
// Macros defined in the current crate have a real node id,
@@ -507,6 +516,7 @@ fn expand_macro_attr(
507516
FailedMacro::Attr(&args),
508517
&body,
509518
rules,
519+
on_missing_args,
510520
);
511521
cx.trace_macros_diag();
512522
Err(guar)
@@ -849,7 +859,22 @@ pub fn compile_declarative_macro(
849859
// Return the number of rules for unused rule linting, if this is a local macro.
850860
let nrules = if is_defined_in_current_crate(node_id) { rules.len() } else { 0 };
851861

852-
let exp = MacroRulesMacroExpander { name: ident, kinds, span, node_id, transparency, rules };
862+
let on_missing_args = find_attr!(
863+
attrs,
864+
OnMissingArgs { directive, .. } => directive.clone()
865+
)
866+
.flatten()
867+
.map(|directive| *directive);
868+
869+
let exp = MacroRulesMacroExpander {
870+
name: ident,
871+
kinds,
872+
span,
873+
node_id,
874+
on_missing_args,
875+
transparency,
876+
rules,
877+
};
853878
(mk_syn_ext(SyntaxExtensionKind::MacroRules(Arc::new(exp))), nrules)
854879
}
855880

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,7 @@ pub fn is_stable_diagnostic_attribute(sym: Symbol, features: &Features) -> bool
15871587
match sym {
15881588
sym::on_unimplemented | sym::do_not_recommend => true,
15891589
sym::on_const => features.diagnostic_on_const(),
1590+
sym::on_missing_args => features.diagnostic_on_missing_args(),
15901591
sym::on_move => features.diagnostic_on_move(),
15911592
_ => false,
15921593
}

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,8 @@ declare_features! (
472472
(unstable, derive_from, "1.91.0", Some(144889)),
473473
/// Allows giving non-const impls custom diagnostic messages if attempted to be used as const
474474
(unstable, diagnostic_on_const, "1.93.0", Some(143874)),
475+
/// Allows macros to customize incomplete-argument diagnostics.
476+
(unstable, diagnostic_on_missing_args, "CURRENT_RUSTC_VERSION", Some(152494)),
475477
/// Allows giving on-move borrowck custom diagnostic messages for a type
476478
(unstable, diagnostic_on_move, "CURRENT_RUSTC_VERSION", Some(154181)),
477479
/// Allows `#[doc(cfg(...))]`.

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,13 @@ pub enum AttributeKind {
13221322
directive: Option<Box<Directive>>,
13231323
},
13241324

1325+
/// Represents `#[diagnostic::on_missing_args]`.
1326+
OnMissingArgs {
1327+
span: Span,
1328+
/// None if the directive was malformed in some way.
1329+
directive: Option<Box<Directive>>,
1330+
},
1331+
13251332
/// Represents `#[diagnostic::on_move]`
13261333
OnMove {
13271334
span: Span,

compiler/rustc_hir/src/attrs/encode_cross_crate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl AttributeKind {
7878
NoStd(..) => No,
7979
NonExhaustive(..) => Yes, // Needed for rustdoc
8080
OnConst { .. } => Yes,
81+
OnMissingArgs { .. } => Yes,
8182
OnMove { .. } => Yes,
8283
OnUnimplemented { .. } => Yes,
8384
Optimize(..) => No,

compiler/rustc_lint/src/early/diagnostics.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> {
281281
&AttributeLintKind::MalformedOnConstAttr { span } => {
282282
lints::MalformedOnConstAttrLint { span }.into_diag(dcx, level)
283283
}
284+
&AttributeLintKind::MalformedOnMissingArgsAttr { span } => {
285+
lints::MalformedOnMissingArgsAttrLint { span }.into_diag(dcx, level)
286+
}
284287
AttributeLintKind::MalformedDiagnosticFormat { warning } => match warning {
285288
FormatWarning::PositionalArgument { .. } => {
286289
lints::DisallowedPositionalArgument.into_diag(dcx, level)
@@ -302,6 +305,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> {
302305
&AttributeLintKind::MissingOptionsForOnConst => {
303306
lints::MissingOptionsForOnConstAttr.into_diag(dcx, level)
304307
}
308+
&AttributeLintKind::MissingOptionsForOnMissingArgs => {
309+
lints::MissingOptionsForOnMissingArgsAttr.into_diag(dcx, level)
310+
}
305311
&AttributeLintKind::MalformedOnMoveAttr { span } => {
306312
lints::MalformedOnMoveAttrLint { span }.into_diag(dcx, level)
307313
}

0 commit comments

Comments
 (0)