Skip to content

Commit b85f57d

Browse files
committed
Auto merge of #106416 - Nilstrieb:better-failure, r=compiler-errors
Shrink `ParseResult` in the hot path. #105570 increased the size, which caused regressions. This uses the existing generic infrastructure to differentiate between the hot path and the diagnostics path.
2 parents 388538f + 5112f02 commit b85f57d

File tree

3 files changed

+81
-30
lines changed

3 files changed

+81
-30
lines changed

compiler/rustc_expand/src/mbe/diagnostics.rs

+23-2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ impl BestFailure {
114114
}
115115

116116
impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
117+
type Failure = (Token, usize, &'static str);
118+
119+
fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
120+
(tok, position, msg)
121+
}
122+
117123
fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
118124
if self.remaining_matcher.is_none()
119125
|| (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
@@ -122,7 +128,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
122128
}
123129
}
124130

125-
fn after_arm(&mut self, result: &NamedParseResult) {
131+
fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
126132
match result {
127133
Success(_) => {
128134
// Nonterminal parser recovery might turn failed matches into successful ones,
@@ -132,7 +138,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
132138
"should not collect detailed info for successful macro match",
133139
);
134140
}
135-
Failure(token, approx_position, msg) => {
141+
Failure((token, approx_position, msg)) => {
136142
debug!(?token, ?msg, "a new failure of an arm");
137143

138144
if self
@@ -175,6 +181,21 @@ impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
175181
}
176182
}
177183

184+
/// Currently used by macro_rules! compilation to extract a little information from the `Failure` case.
185+
pub struct FailureForwarder;
186+
187+
impl<'matcher> Tracker<'matcher> for FailureForwarder {
188+
type Failure = (Token, usize, &'static str);
189+
190+
fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
191+
(tok, position, msg)
192+
}
193+
194+
fn description() -> &'static str {
195+
"failure-forwarder"
196+
}
197+
}
198+
178199
pub(super) fn emit_frag_parse_err(
179200
mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
180201
parser: &Parser<'_>,

compiler/rustc_expand/src/mbe/macro_parser.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,13 @@ enum EofMatcherPositions {
305305
}
306306

307307
/// Represents the possible results of an attempted parse.
308-
pub(crate) enum ParseResult<T> {
308+
pub(crate) enum ParseResult<T, F> {
309309
/// Parsed successfully.
310310
Success(T),
311311
/// Arm failed to match. If the second parameter is `token::Eof`, it indicates an unexpected
312312
/// end of macro invocation. Otherwise, it indicates that no rules expected the given token.
313313
/// The usize is the approximate position of the token in the input token stream.
314-
Failure(Token, usize, &'static str),
314+
Failure(F),
315315
/// Fatal error (malformed macro?). Abort compilation.
316316
Error(rustc_span::Span, String),
317317
ErrorReported(ErrorGuaranteed),
@@ -320,7 +320,7 @@ pub(crate) enum ParseResult<T> {
320320
/// A `ParseResult` where the `Success` variant contains a mapping of
321321
/// `MacroRulesNormalizedIdent`s to `NamedMatch`es. This represents the mapping
322322
/// of metavars to the token trees they bind to.
323-
pub(crate) type NamedParseResult = ParseResult<NamedMatches>;
323+
pub(crate) type NamedParseResult<F> = ParseResult<NamedMatches, F>;
324324

325325
/// Contains a mapping of `MacroRulesNormalizedIdent`s to `NamedMatch`es.
326326
/// This represents the mapping of metavars to the token trees they bind to.
@@ -458,7 +458,7 @@ impl TtParser {
458458
token: &Token,
459459
approx_position: usize,
460460
track: &mut T,
461-
) -> Option<NamedParseResult> {
461+
) -> Option<NamedParseResult<T::Failure>> {
462462
// Matcher positions that would be valid if the macro invocation was over now. Only
463463
// modified if `token == Eof`.
464464
let mut eof_mps = EofMatcherPositions::None;
@@ -595,14 +595,14 @@ impl TtParser {
595595
EofMatcherPositions::Multiple => {
596596
Error(token.span, "ambiguity: multiple successful parses".to_string())
597597
}
598-
EofMatcherPositions::None => Failure(
598+
EofMatcherPositions::None => Failure(T::build_failure(
599599
Token::new(
600600
token::Eof,
601601
if token.span.is_dummy() { token.span } else { token.span.shrink_to_hi() },
602602
),
603603
approx_position,
604604
"missing tokens in macro arguments",
605-
),
605+
)),
606606
})
607607
} else {
608608
None
@@ -615,7 +615,7 @@ impl TtParser {
615615
parser: &mut Cow<'_, Parser<'_>>,
616616
matcher: &'matcher [MatcherLoc],
617617
track: &mut T,
618-
) -> NamedParseResult {
618+
) -> NamedParseResult<T::Failure> {
619619
// A queue of possible matcher positions. We initialize it with the matcher position in
620620
// which the "dot" is before the first token of the first token tree in `matcher`.
621621
// `parse_tt_inner` then processes all of these possible matcher positions and produces
@@ -648,11 +648,11 @@ impl TtParser {
648648
(0, 0) => {
649649
// There are no possible next positions AND we aren't waiting for the black-box
650650
// parser: syntax error.
651-
return Failure(
651+
return Failure(T::build_failure(
652652
parser.token.clone(),
653653
parser.approx_token_stream_pos(),
654654
"no rules expected this token in macro call",
655-
);
655+
));
656656
}
657657

658658
(_, 0) => {
@@ -711,11 +711,11 @@ impl TtParser {
711711
}
712712
}
713713

714-
fn ambiguity_error(
714+
fn ambiguity_error<F>(
715715
&self,
716716
matcher: &[MatcherLoc],
717717
token_span: rustc_span::Span,
718-
) -> NamedParseResult {
718+
) -> NamedParseResult<F> {
719719
let nts = self
720720
.bb_mps
721721
.iter()
@@ -741,11 +741,11 @@ impl TtParser {
741741
)
742742
}
743743

744-
fn nameize<I: Iterator<Item = NamedMatch>>(
744+
fn nameize<I: Iterator<Item = NamedMatch>, F>(
745745
&self,
746746
matcher: &[MatcherLoc],
747747
mut res: I,
748-
) -> NamedParseResult {
748+
) -> NamedParseResult<F> {
749749
// Make that each metavar has _exactly one_ binding. If so, insert the binding into the
750750
// `NamedParseResult`. Otherwise, it's an error.
751751
let mut ret_val = FxHashMap::default();

compiler/rustc_expand/src/mbe/macro_rules.rs

+45-15
Original file line numberDiff line numberDiff line change
@@ -141,31 +141,40 @@ fn trace_macros_note(cx_expansions: &mut FxIndexMap<Span, Vec<String>>, sp: Span
141141
}
142142

143143
pub(super) trait Tracker<'matcher> {
144+
/// The contents of `ParseResult::Failure`.
145+
type Failure;
146+
147+
/// Arm failed to match. If the token is `token::Eof`, it indicates an unexpected
148+
/// end of macro invocation. Otherwise, it indicates that no rules expected the given token.
149+
/// The usize is the approximate position of the token in the input token stream.
150+
fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure;
151+
144152
/// This is called before trying to match next MatcherLoc on the current token.
145-
fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc);
153+
fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {}
146154

147155
/// This is called after an arm has been parsed, either successfully or unsuccessfully. When this is called,
148156
/// `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
149-
fn after_arm(&mut self, result: &NamedParseResult);
157+
fn after_arm(&mut self, _result: &NamedParseResult<Self::Failure>) {}
150158

151159
/// For tracing.
152160
fn description() -> &'static str;
153161

154-
fn recovery() -> Recovery;
162+
fn recovery() -> Recovery {
163+
Recovery::Forbidden
164+
}
155165
}
156166

157167
/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization.
158168
pub(super) struct NoopTracker;
159169

160170
impl<'matcher> Tracker<'matcher> for NoopTracker {
161-
fn before_match_loc(&mut self, _: &TtParser, _: &'matcher MatcherLoc) {}
162-
fn after_arm(&mut self, _: &NamedParseResult) {}
171+
type Failure = ();
172+
173+
fn build_failure(_tok: Token, _position: usize, _msg: &'static str) -> Self::Failure {}
174+
163175
fn description() -> &'static str {
164176
"none"
165177
}
166-
fn recovery() -> Recovery {
167-
Recovery::Forbidden
168-
}
169178
}
170179

171180
/// Expands the rules based macro defined by `lhses` and `rhses` for a given
@@ -326,8 +335,8 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(
326335

327336
return Ok((i, named_matches));
328337
}
329-
Failure(_, reached_position, _) => {
330-
trace!(%reached_position, "Failed to match arm, trying the next one");
338+
Failure(_) => {
339+
trace!("Failed to match arm, trying the next one");
331340
// Try the next arm.
332341
}
333342
Error(_, _) => {
@@ -381,11 +390,13 @@ pub fn compile_declarative_macro(
381390
let rhs_nm = Ident::new(sym::rhs, def.span);
382391
let tt_spec = Some(NonterminalKind::TT);
383392

384-
// Parse the macro_rules! invocation
385-
let (macro_rules, body) = match &def.kind {
386-
ast::ItemKind::MacroDef(def) => (def.macro_rules, def.body.tokens.clone()),
393+
let macro_def = match &def.kind {
394+
ast::ItemKind::MacroDef(def) => def,
387395
_ => unreachable!(),
388396
};
397+
let macro_rules = macro_def.macro_rules;
398+
399+
// Parse the macro_rules! invocation
389400

390401
// The pattern that macro_rules matches.
391402
// The grammar for macro_rules! is:
@@ -426,13 +437,32 @@ pub fn compile_declarative_macro(
426437
// Convert it into `MatcherLoc` form.
427438
let argument_gram = mbe::macro_parser::compute_locs(&argument_gram);
428439

429-
let parser = Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS);
440+
let create_parser = || {
441+
let body = macro_def.body.tokens.clone();
442+
Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS)
443+
};
444+
445+
let parser = create_parser();
430446
let mut tt_parser =
431447
TtParser::new(Ident::with_dummy_span(if macro_rules { kw::MacroRules } else { kw::Macro }));
432448
let argument_map =
433449
match tt_parser.parse_tt(&mut Cow::Owned(parser), &argument_gram, &mut NoopTracker) {
434450
Success(m) => m,
435-
Failure(token, _, msg) => {
451+
Failure(()) => {
452+
// The fast `NoopTracker` doesn't have any info on failure, so we need to retry it with another one
453+
// that gives us the information we need.
454+
// For this we need to reclone the macro body as the previous parser consumed it.
455+
let retry_parser = create_parser();
456+
457+
let parse_result = tt_parser.parse_tt(
458+
&mut Cow::Owned(retry_parser),
459+
&argument_gram,
460+
&mut diagnostics::FailureForwarder,
461+
);
462+
let Failure((token, _, msg)) = parse_result else {
463+
unreachable!("matcher returned something other than Failure after retry");
464+
};
465+
436466
let s = parse_failure_msg(&token);
437467
let sp = token.span.substitute_dummy(def.span);
438468
let mut err = sess.parse_sess.span_diagnostic.struct_span_err(sp, &s);

0 commit comments

Comments
 (0)