@@ -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