Skip to content

Commit 63cfb3a

Browse files
committed
mbe: Defer checks for compile_error! until reporting an unused macro rule
The MBE parser checks rules at initial parse time to see if their RHS has `compile_error!` in it, and returns a list of rule indexes and LHS spans that don't map to `compile_error!`, for use in unused macro rule checking. Instead, have the unused macro rule reporting ask the macro for the rule to report, and let the macro check at that time. That avoids checking rules unless they're unused. In the process, refactor the data structure used to store macro rules, to group the LHS and RHS (and LHS span) of each rule together, and refactor the unused rule tracking to only track rule indexes. This ends up being a net simplification, and reduction in code size.
1 parent 0d5ab3e commit 63cfb3a

File tree

6 files changed

+80
-92
lines changed

6 files changed

+80
-92
lines changed

compiler/rustc_expand/src/base.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,10 @@ pub trait TTMacroExpander {
348348
span: Span,
349349
input: TokenStream,
350350
) -> MacroExpanderResult<'cx>;
351+
352+
fn get_unused_rule(&self, _rule_i: usize) -> Option<(&Ident, Span)> {
353+
None
354+
}
351355
}
352356

353357
pub type MacroExpanderResult<'cx> = ExpandResult<Box<dyn MacResult + 'cx>, ()>;

compiler/rustc_expand/src/mbe/diagnostics.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rustc_span::source_map::SourceMap;
1010
use rustc_span::{ErrorGuaranteed, Ident, Span};
1111
use tracing::debug;
1212

13-
use super::macro_rules::{NoopTracker, parser_from_cx};
13+
use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx};
1414
use crate::expand::{AstFragmentKind, parse_ast_fragment};
1515
use crate::mbe::macro_parser::ParseResult::*;
1616
use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser};
@@ -22,14 +22,14 @@ pub(super) fn failed_to_match_macro(
2222
def_span: Span,
2323
name: Ident,
2424
arg: TokenStream,
25-
lhses: &[Vec<MatcherLoc>],
25+
rules: &[MacroRule],
2626
) -> (Span, ErrorGuaranteed) {
2727
debug!("failed to match macro");
2828
// An error occurred, try the expansion again, tracking the expansion closely for better
2929
// diagnostics.
3030
let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp);
3131

32-
let try_success_result = try_match_macro(psess, name, &arg, lhses, &mut tracker);
32+
let try_success_result = try_match_macro(psess, name, &arg, rules, &mut tracker);
3333

3434
if try_success_result.is_ok() {
3535
// Nonterminal parser recovery might turn failed matches into successful ones,
@@ -80,12 +80,12 @@ pub(super) fn failed_to_match_macro(
8080

8181
// Check whether there's a missing comma in this macro call, like `println!("{}" a);`
8282
if let Some((arg, comma_span)) = arg.add_comma() {
83-
for lhs in lhses {
83+
for rule in rules {
8484
let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed);
8585
let mut tt_parser = TtParser::new(name);
8686

8787
if let Success(_) =
88-
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
88+
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, &mut NoopTracker)
8989
{
9090
if comma_span.is_dummy() {
9191
err.note("you might be missing a comma");

compiler/rustc_expand/src/mbe/macro_rules.rs

Lines changed: 46 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,18 @@ impl<'a> ParserAnyMacro<'a> {
9898
}
9999
}
100100

101+
pub(super) struct MacroRule {
102+
pub(super) lhs: Vec<MatcherLoc>,
103+
lhs_span: Span,
104+
rhs: mbe::TokenTree,
105+
}
106+
101107
struct MacroRulesMacroExpander {
102108
node_id: NodeId,
103109
name: Ident,
104110
span: Span,
105111
transparency: Transparency,
106-
lhses: Vec<Vec<MatcherLoc>>,
107-
rhses: Vec<mbe::TokenTree>,
112+
rules: Vec<MacroRule>,
108113
}
109114

110115
impl TTMacroExpander for MacroRulesMacroExpander {
@@ -122,10 +127,15 @@ impl TTMacroExpander for MacroRulesMacroExpander {
122127
self.name,
123128
self.transparency,
124129
input,
125-
&self.lhses,
126-
&self.rhses,
130+
&self.rules,
127131
))
128132
}
133+
134+
fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, Span)> {
135+
// If the rhs contains an invocation like `compile_error!`, don't report it as unused.
136+
let rule = &self.rules[rule_i];
137+
if has_compile_error_macro(&rule.rhs) { None } else { Some((&self.name, rule.lhs_span)) }
138+
}
129139
}
130140

131141
struct DummyExpander(ErrorGuaranteed);
@@ -184,9 +194,8 @@ impl<'matcher> Tracker<'matcher> for NoopTracker {
184194
}
185195
}
186196

187-
/// Expands the rules based macro defined by `lhses` and `rhses` for a given
188-
/// input `arg`.
189-
#[instrument(skip(cx, transparency, arg, lhses, rhses))]
197+
/// Expands the rules based macro defined by `rules` for a given input `arg`.
198+
#[instrument(skip(cx, transparency, arg, rules))]
190199
fn expand_macro<'cx>(
191200
cx: &'cx mut ExtCtxt<'_>,
192201
sp: Span,
@@ -195,8 +204,7 @@ fn expand_macro<'cx>(
195204
name: Ident,
196205
transparency: Transparency,
197206
arg: TokenStream,
198-
lhses: &[Vec<MatcherLoc>],
199-
rhses: &[mbe::TokenTree],
207+
rules: &[MacroRule],
200208
) -> Box<dyn MacResult + 'cx> {
201209
let psess = &cx.sess.psess;
202210
// Macros defined in the current crate have a real node id,
@@ -209,14 +217,14 @@ fn expand_macro<'cx>(
209217
}
210218

211219
// Track nothing for the best performance.
212-
let try_success_result = try_match_macro(psess, name, &arg, lhses, &mut NoopTracker);
220+
let try_success_result = try_match_macro(psess, name, &arg, rules, &mut NoopTracker);
213221

214222
match try_success_result {
215-
Ok((i, named_matches)) => {
216-
let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rhses[i] else {
223+
Ok((i, rule, named_matches)) => {
224+
let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rule.rhs else {
217225
cx.dcx().span_bug(sp, "malformed macro rhs");
218226
};
219-
let arm_span = rhses[i].span();
227+
let arm_span = rule.rhs.span();
220228

221229
// rhs has holes ( `$id` and `$(...)` that need filled)
222230
let id = cx.current_expansion.id;
@@ -262,7 +270,7 @@ fn expand_macro<'cx>(
262270
Err(CanRetry::Yes) => {
263271
// Retry and emit a better error.
264272
let (span, guar) =
265-
diagnostics::failed_to_match_macro(cx.psess(), sp, def_span, name, arg, lhses);
273+
diagnostics::failed_to_match_macro(cx.psess(), sp, def_span, name, arg, rules);
266274
cx.trace_macros_diag();
267275
DummyResult::any(span, guar)
268276
}
@@ -278,14 +286,14 @@ pub(super) enum CanRetry {
278286
/// Try expanding the macro. Returns the index of the successful arm and its named_matches if it was successful,
279287
/// and nothing if it failed. On failure, it's the callers job to use `track` accordingly to record all errors
280288
/// correctly.
281-
#[instrument(level = "debug", skip(psess, arg, lhses, track), fields(tracking = %T::description()))]
289+
#[instrument(level = "debug", skip(psess, arg, rules, track), fields(tracking = %T::description()))]
282290
pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
283291
psess: &ParseSess,
284292
name: Ident,
285293
arg: &TokenStream,
286-
lhses: &'matcher [Vec<MatcherLoc>],
294+
rules: &'matcher [MacroRule],
287295
track: &mut T,
288-
) -> Result<(usize, NamedMatches), CanRetry> {
296+
) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> {
289297
// We create a base parser that can be used for the "black box" parts.
290298
// Every iteration needs a fresh copy of that parser. However, the parser
291299
// is not mutated on many of the iterations, particularly when dealing with
@@ -308,7 +316,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
308316
let parser = parser_from_cx(psess, arg.clone(), T::recovery());
309317
// Try each arm's matchers.
310318
let mut tt_parser = TtParser::new(name);
311-
for (i, lhs) in lhses.iter().enumerate() {
319+
for (i, rule) in rules.iter().enumerate() {
312320
let _tracing_span = trace_span!("Matching arm", %i);
313321

314322
// Take a snapshot of the state of pre-expansion gating at this point.
@@ -317,7 +325,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
317325
// are not recorded. On the first `Success(..)`ful matcher, the spans are merged.
318326
let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut());
319327

320-
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track);
328+
let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, track);
321329

322330
track.after_arm(&result);
323331

@@ -328,7 +336,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
328336
// Merge the gated spans from parsing the matcher with the preexisting ones.
329337
psess.gated_spans.merge(gated_spans_snapshot);
330338

331-
return Ok((i, named_matches));
339+
return Ok((i, rule, named_matches));
332340
}
333341
Failure(_) => {
334342
trace!("Failed to match arm, trying the next one");
@@ -364,7 +372,7 @@ pub fn compile_declarative_macro(
364372
span: Span,
365373
node_id: NodeId,
366374
edition: Edition,
367-
) -> (SyntaxExtension, Vec<(usize, Span)>) {
375+
) -> (SyntaxExtension, usize) {
368376
let mk_syn_ext = |expander| {
369377
SyntaxExtension::new(
370378
sess,
@@ -377,7 +385,7 @@ pub fn compile_declarative_macro(
377385
node_id != DUMMY_NODE_ID,
378386
)
379387
};
380-
let dummy_syn_ext = |guar| (mk_syn_ext(Arc::new(DummyExpander(guar))), Vec::new());
388+
let dummy_syn_ext = |guar| (mk_syn_ext(Arc::new(DummyExpander(guar))), 0);
381389

382390
let macro_rules = macro_def.macro_rules;
383391
let exp_sep = if macro_rules { exp!(Semi) } else { exp!(Comma) };
@@ -389,8 +397,7 @@ pub fn compile_declarative_macro(
389397
let mut guar = None;
390398
let mut check_emission = |ret: Result<(), ErrorGuaranteed>| guar = guar.or(ret.err());
391399

392-
let mut lhses = Vec::new();
393-
let mut rhses = Vec::new();
400+
let mut rules = Vec::new();
394401

395402
while p.token != token::Eof {
396403
let lhs_tt = p.parse_token_tree();
@@ -415,8 +422,15 @@ pub fn compile_declarative_macro(
415422
let rhs_tt = parse_one_tt(rhs_tt, RulePart::Body, sess, node_id, features, edition);
416423
check_emission(check_rhs(sess, &rhs_tt));
417424
check_emission(macro_check::check_meta_variables(&sess.psess, node_id, &lhs_tt, &rhs_tt));
418-
lhses.push(lhs_tt);
419-
rhses.push(rhs_tt);
425+
let lhs_span = lhs_tt.span();
426+
// Convert the lhs into `MatcherLoc` form, which is better for doing the
427+
// actual matching.
428+
let lhs = if let mbe::TokenTree::Delimited(.., delimited) = lhs_tt {
429+
mbe::macro_parser::compute_locs(&delimited.tts)
430+
} else {
431+
return dummy_syn_ext(guar.unwrap());
432+
};
433+
rules.push(MacroRule { lhs, lhs_span, rhs: rhs_tt });
420434
if p.token == token::Eof {
421435
break;
422436
}
@@ -425,7 +439,7 @@ pub fn compile_declarative_macro(
425439
}
426440
}
427441

428-
if lhses.is_empty() {
442+
if rules.is_empty() {
429443
let guar = sess.dcx().span_err(span, "macros must contain at least one rule");
430444
return dummy_syn_ext(guar);
431445
}
@@ -439,48 +453,12 @@ pub fn compile_declarative_macro(
439453
return dummy_syn_ext(guar);
440454
}
441455

442-
// Compute the spans of the macro rules for unused rule linting.
443-
// Also, we are only interested in non-foreign macros.
444-
let rule_spans = if node_id != DUMMY_NODE_ID {
445-
lhses
446-
.iter()
447-
.zip(rhses.iter())
448-
.enumerate()
449-
// If the rhs contains an invocation like compile_error!,
450-
// don't consider the rule for the unused rule lint.
451-
.filter(|(_idx, (_lhs, rhs))| !has_compile_error_macro(rhs))
452-
// We only take the span of the lhs here,
453-
// so that the spans of created warnings are smaller.
454-
.map(|(idx, (lhs, _rhs))| (idx, lhs.span()))
455-
.collect::<Vec<_>>()
456-
} else {
457-
Vec::new()
458-
};
456+
// Return the number of rules for unused rule linting, if this is a local macro.
457+
let nrules = if node_id != DUMMY_NODE_ID { rules.len() } else { 0 };
459458

460-
// Convert the lhses into `MatcherLoc` form, which is better for doing the
461-
// actual matching.
462-
let lhses = lhses
463-
.iter()
464-
.map(|lhs| {
465-
// Ignore the delimiters around the matcher.
466-
match lhs {
467-
mbe::TokenTree::Delimited(.., delimited) => {
468-
mbe::macro_parser::compute_locs(&delimited.tts)
469-
}
470-
_ => sess.dcx().span_bug(span, "malformed macro lhs"),
471-
}
472-
})
473-
.collect();
474-
475-
let expander = Arc::new(MacroRulesMacroExpander {
476-
name: ident,
477-
span,
478-
node_id,
479-
transparency,
480-
lhses,
481-
rhses,
482-
});
483-
(mk_syn_ext(expander), rule_spans)
459+
let expander =
460+
Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules });
461+
(mk_syn_ext(expander), nrules)
484462
}
485463

486464
fn check_lhs_nt_follows(

compiler/rustc_resolve/src/build_reduced_graph.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,12 +1202,8 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
12021202
fn insert_unused_macro(&mut self, ident: Ident, def_id: LocalDefId, node_id: NodeId) {
12031203
if !ident.as_str().starts_with('_') {
12041204
self.r.unused_macros.insert(def_id, (node_id, ident));
1205-
for (rule_i, rule_span) in &self.r.macro_map[&def_id.to_def_id()].rule_spans {
1206-
self.r
1207-
.unused_macro_rules
1208-
.entry(node_id)
1209-
.or_default()
1210-
.insert(*rule_i, (ident, *rule_span));
1205+
for rule_i in 0..self.r.macro_map[&def_id.to_def_id()].nrules {
1206+
self.r.unused_macro_rules.entry(node_id).or_default().insert(rule_i);
12111207
}
12121208
}
12131209
}

compiler/rustc_resolve/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,13 +1014,13 @@ struct DeriveData {
10141014

10151015
struct MacroData {
10161016
ext: Arc<SyntaxExtension>,
1017-
rule_spans: Vec<(usize, Span)>,
1017+
nrules: usize,
10181018
macro_rules: bool,
10191019
}
10201020

10211021
impl MacroData {
10221022
fn new(ext: Arc<SyntaxExtension>) -> MacroData {
1023-
MacroData { ext, rule_spans: Vec::new(), macro_rules: false }
1023+
MacroData { ext, nrules: 0, macro_rules: false }
10241024
}
10251025
}
10261026

@@ -1135,7 +1135,7 @@ pub struct Resolver<'ra, 'tcx> {
11351135
ast_transform_scopes: FxHashMap<LocalExpnId, Module<'ra>>,
11361136
unused_macros: FxIndexMap<LocalDefId, (NodeId, Ident)>,
11371137
/// A map from the macro to all its potentially unused arms.
1138-
unused_macro_rules: FxIndexMap<NodeId, UnordMap<usize, (Ident, Span)>>,
1138+
unused_macro_rules: FxIndexMap<NodeId, UnordSet<usize>>,
11391139
proc_macro_stubs: FxHashSet<LocalDefId>,
11401140
/// Traces collected during macro resolution and validated when it's complete.
11411141
single_segment_macro_resolutions:

compiler/rustc_resolve/src/macros.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,23 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
351351
}
352352

353353
for (&node_id, unused_arms) in self.unused_macro_rules.iter() {
354-
for (&arm_i, &(ident, rule_span)) in unused_arms.to_sorted_stable_ord() {
355-
self.lint_buffer.buffer_lint(
356-
UNUSED_MACRO_RULES,
357-
node_id,
358-
rule_span,
359-
BuiltinLintDiag::MacroRuleNeverUsed(arm_i, ident.name),
360-
);
354+
if unused_arms.is_empty() {
355+
continue;
356+
}
357+
let def_id = self.local_def_id(node_id).to_def_id();
358+
let m = &self.macro_map[&def_id];
359+
let SyntaxExtensionKind::LegacyBang(ref ext) = m.ext.kind else {
360+
continue;
361+
};
362+
for &arm_i in unused_arms.to_sorted_stable_ord() {
363+
if let Some((ident, rule_span)) = ext.get_unused_rule(arm_i) {
364+
self.lint_buffer.buffer_lint(
365+
UNUSED_MACRO_RULES,
366+
node_id,
367+
rule_span,
368+
BuiltinLintDiag::MacroRuleNeverUsed(arm_i, ident.name),
369+
);
370+
}
361371
}
362372
}
363373
}
@@ -1146,7 +1156,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
11461156
node_id: NodeId,
11471157
edition: Edition,
11481158
) -> MacroData {
1149-
let (mut ext, mut rule_spans) = compile_declarative_macro(
1159+
let (mut ext, mut nrules) = compile_declarative_macro(
11501160
self.tcx.sess,
11511161
self.tcx.features(),
11521162
macro_def,
@@ -1163,13 +1173,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
11631173
// The macro is a built-in, replace its expander function
11641174
// while still taking everything else from the source code.
11651175
ext.kind = builtin_ext_kind.clone();
1166-
rule_spans = Vec::new();
1176+
nrules = 0;
11671177
} else {
11681178
self.dcx().emit_err(errors::CannotFindBuiltinMacroWithName { span, ident });
11691179
}
11701180
}
11711181

1172-
MacroData { ext: Arc::new(ext), rule_spans, macro_rules: macro_def.macro_rules }
1182+
MacroData { ext: Arc::new(ext), nrules, macro_rules: macro_def.macro_rules }
11731183
}
11741184

11751185
fn path_accessible(

0 commit comments

Comments
 (0)