@@ -210,7 +210,17 @@ pub struct ParseError {
210210 pub label : string:: String ,
211211 pub span : InnerSpan ,
212212 pub secondary_label : Option < ( string:: String , InnerSpan ) > ,
213- pub should_be_replaced_with_positional_argument : bool ,
213+ pub suggestion : Suggestion ,
214+ }
215+
216+ pub enum Suggestion {
217+ None ,
218+ /// Replace inline argument with positional argument:
219+ /// `format!("{foo.bar}")` -> `format!("{}", foo.bar)`
220+ UsePositional ,
221+ /// Remove `r#` from identifier:
222+ /// `format!("{r#foo}")` -> `format!("{foo}")`
223+ RemoveRawIdent ( InnerSpan ) ,
214224}
215225
216226/// The parser structure for interpreting the input format string. This is
@@ -365,7 +375,7 @@ impl<'a> Parser<'a> {
365375 label : label. into ( ) ,
366376 span,
367377 secondary_label : None ,
368- should_be_replaced_with_positional_argument : false ,
378+ suggestion : Suggestion :: None ,
369379 } ) ;
370380 }
371381
@@ -389,7 +399,7 @@ impl<'a> Parser<'a> {
389399 label : label. into ( ) ,
390400 span,
391401 secondary_label : None ,
392- should_be_replaced_with_positional_argument : false ,
402+ suggestion : Suggestion :: None ,
393403 } ) ;
394404 }
395405
@@ -493,7 +503,7 @@ impl<'a> Parser<'a> {
493503 label,
494504 span : pos. to ( pos) ,
495505 secondary_label,
496- should_be_replaced_with_positional_argument : false ,
506+ suggestion : Suggestion :: None ,
497507 } ) ;
498508
499509 None
@@ -573,7 +583,37 @@ impl<'a> Parser<'a> {
573583 Some ( ArgumentIs ( i) )
574584 } else {
575585 match self . cur . peek ( ) {
576- Some ( & ( _, c) ) if rustc_lexer:: is_id_start ( c) => Some ( ArgumentNamed ( self . word ( ) ) ) ,
586+ Some ( & ( lo, c) ) if rustc_lexer:: is_id_start ( c) => {
587+ let word = self . word ( ) ;
588+
589+ // Recover from `r#ident` in format strings.
590+ // FIXME: use a let chain
591+ if word == "r" {
592+ if let Some ( ( pos, '#' ) ) = self . cur . peek ( ) {
593+ if self . input [ pos + 1 ..]
594+ . chars ( )
595+ . next ( )
596+ . is_some_and ( rustc_lexer:: is_id_start)
597+ {
598+ self . cur . next ( ) ;
599+ let word = self . word ( ) ;
600+ let prefix_span = self . span ( lo, lo + 2 ) ;
601+ let full_span = self . span ( lo, lo + 2 + word. len ( ) ) ;
602+ self . errors . insert ( 0 , ParseError {
603+ description : "raw identifiers are not supported" . to_owned ( ) ,
604+ note : Some ( "identifiers in format strings can be keywords and don't need to be prefixed with `r#`" . to_string ( ) ) ,
605+ label : "raw identifier used here" . to_owned ( ) ,
606+ span : full_span,
607+ secondary_label : None ,
608+ suggestion : Suggestion :: RemoveRawIdent ( prefix_span) ,
609+ } ) ;
610+ return Some ( ArgumentNamed ( word) ) ;
611+ }
612+ }
613+ }
614+
615+ Some ( ArgumentNamed ( word) )
616+ }
577617
578618 // This is an `ArgumentNext`.
579619 // Record the fact and do the resolution after parsing the
@@ -841,7 +881,7 @@ impl<'a> Parser<'a> {
841881 label : "expected `?` to occur after `:`" . to_owned ( ) ,
842882 span : pos. to ( pos) ,
843883 secondary_label : None ,
844- should_be_replaced_with_positional_argument : false ,
884+ suggestion : Suggestion :: None ,
845885 } ,
846886 ) ;
847887 }
@@ -867,7 +907,7 @@ impl<'a> Parser<'a> {
867907 label : "not supported" . to_string ( ) ,
868908 span : InnerSpan :: new ( arg. position_span . start , field. position_span . end ) ,
869909 secondary_label : None ,
870- should_be_replaced_with_positional_argument : true ,
910+ suggestion : Suggestion :: UsePositional ,
871911 } ,
872912 ) ;
873913 }
0 commit comments