Skip to content

Commit 5f73eac

Browse files
committed
Retry matching with tracking for diagnostics
For now, we only collect the small info for the `best_failure`, but using this tracker, we can easily extend it in the future to track things with more performance overhead. We cannot retry cases where the macro failed with a parser error that was emitted already, as that would cause us to emit the same error to the user twice.
1 parent 39584b1 commit 5f73eac

File tree

1 file changed

+109
-9
lines changed

1 file changed

+109
-9
lines changed

compiler/rustc_expand/src/mbe/macro_rules.rs

+109-9
Original file line numberDiff line numberDiff line change
@@ -316,16 +316,116 @@ fn expand_macro<'cx>(
316316
is_local,
317317
});
318318
}
319-
Err(()) => {
320-
todo!("Retry macro invocation while tracking diagnostics info and emit error");
321-
319+
Err(CanRetry::No(_)) => {
320+
debug!("Will not retry matching as an error was emitted already");
322321
return DummyResult::any(sp);
323322
}
323+
Err(CanRetry::Yes) => {
324+
// Retry and emit a better error below.
325+
}
326+
}
327+
328+
// An error occured, try the expansion again, tracking the expansion closely for better diagnostics
329+
let mut tracker = CollectTrackerAndEmitter::new(cx, sp);
330+
331+
let try_success_result = try_match_macro(sess, name, &arg, lhses, &mut tracker);
332+
assert!(try_success_result.is_err(), "Macro matching returned a success on the second try");
333+
334+
if let Some(result) = tracker.result {
335+
// An irrecoverable error occured and has been emitted.
336+
return result;
337+
}
338+
339+
let Some((token, label)) = tracker.best_failure else {
340+
return tracker.result.expect("must have encountered Error or ErrorReported");
341+
};
342+
343+
let span = token.span.substitute_dummy(sp);
344+
345+
let mut err = cx.struct_span_err(span, &parse_failure_msg(&token));
346+
err.span_label(span, label);
347+
if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
348+
err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro");
324349
}
325350

351+
annotate_doc_comment(&mut err, sess.source_map(), span);
352+
353+
// Check whether there's a missing comma in this macro call, like `println!("{}" a);`
354+
if let Some((arg, comma_span)) = arg.add_comma() {
355+
for lhs in lhses {
356+
let parser = parser_from_cx(sess, arg.clone());
357+
let mut tt_parser = TtParser::new(name);
358+
359+
if let Success(_) =
360+
tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker)
361+
{
362+
if comma_span.is_dummy() {
363+
err.note("you might be missing a comma");
364+
} else {
365+
err.span_suggestion_short(
366+
comma_span,
367+
"missing comma here",
368+
", ",
369+
Applicability::MachineApplicable,
370+
);
371+
}
372+
}
373+
}
374+
}
375+
err.emit();
376+
cx.trace_macros_diag();
326377
DummyResult::any(sp)
327378
}
328379

380+
/// The tracker used for the slow error path that collects useful info for diagnostics
381+
struct CollectTrackerAndEmitter<'a, 'cx> {
382+
cx: &'a mut ExtCtxt<'cx>,
383+
/// Which arm's failure should we report? (the one furthest along)
384+
best_failure: Option<(Token, &'static str)>,
385+
root_span: Span,
386+
result: Option<Box<dyn MacResult + 'cx>>,
387+
}
388+
389+
impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx> {
390+
fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {
391+
// Empty for now.
392+
}
393+
394+
fn after_arm(&mut self, result: &NamedParseResult) {
395+
match result {
396+
Success(_) => {
397+
unreachable!("should not collect detailed info for successful macro match");
398+
}
399+
Failure(token, msg) => match self.best_failure {
400+
Some((ref best_token, _)) if best_token.span.lo() >= token.span.lo() => {}
401+
_ => self.best_failure = Some((token.clone(), msg)),
402+
},
403+
Error(err_sp, msg) => {
404+
let span = err_sp.substitute_dummy(self.root_span);
405+
self.cx.struct_span_err(span, msg).emit();
406+
self.result = Some(DummyResult::any(span));
407+
}
408+
ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),
409+
}
410+
}
411+
412+
fn description() -> &'static str {
413+
"detailed"
414+
}
415+
}
416+
417+
impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx> {
418+
fn new(cx: &'a mut ExtCtxt<'cx>, root_span: Span) -> Self {
419+
Self { cx, best_failure: None, root_span, result: None }
420+
}
421+
}
422+
423+
enum CanRetry {
424+
Yes,
425+
/// We are not allowed to retry macro expansion as a fatal error has been emitted already.
426+
No(ErrorGuaranteed),
427+
}
428+
329429
/// Try expanding the macro. Returns the index of the sucessful arm and its named_matches if it was successful,
330430
/// and nothing if it failed. On failure, it's the callers job to use `track` accordingly to record all errors
331431
/// correctly.
@@ -335,7 +435,7 @@ fn try_match_macro<'matcher, T: Tracker<'matcher>>(
335435
arg: &TokenStream,
336436
lhses: &'matcher [Vec<MatcherLoc>],
337437
track: &mut T,
338-
) -> Result<(usize, NamedMatches), ()> {
438+
) -> Result<(usize, NamedMatches), CanRetry> {
339439
// We create a base parser that can be used for the "black box" parts.
340440
// Every iteration needs a fresh copy of that parser. However, the parser
341441
// is not mutated on many of the iterations, particularly when dealing with
@@ -383,10 +483,10 @@ fn try_match_macro<'matcher, T: Tracker<'matcher>>(
383483
}
384484
Error(_, _) => {
385485
// We haven't emitted an error yet
386-
return Err(());
486+
return Err(CanRetry::Yes);
387487
}
388-
ErrorReported(_) => {
389-
return Err(());
488+
ErrorReported(guarantee) => {
489+
return Err(CanRetry::No(guarantee));
390490
}
391491
}
392492

@@ -395,7 +495,7 @@ fn try_match_macro<'matcher, T: Tracker<'matcher>>(
395495
mem::swap(&mut gated_spans_snapshot, &mut sess.gated_spans.spans.borrow_mut());
396496
}
397497

398-
Err(())
498+
Err(CanRetry::Yes)
399499
}
400500

401501
// Note that macro-by-example's input is also matched against a token tree:
@@ -478,7 +578,7 @@ pub fn compile_declarative_macro(
478578
let mut tt_parser =
479579
TtParser::new(Ident::with_dummy_span(if macro_rules { kw::MacroRules } else { kw::Macro }));
480580
let argument_map =
481-
match tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &argument_gram, &mut NoopTracker) {
581+
match tt_parser.parse_tt(&mut Cow::Owned(parser), &argument_gram, &mut NoopTracker) {
482582
Success(m) => m,
483583
Failure(token, msg) => {
484584
let s = parse_failure_msg(&token);

0 commit comments

Comments
 (0)