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