@@ -20,6 +20,7 @@ pub use Flag::*;
2020pub use Piece :: * ;
2121pub use Position :: * ;
2222
23+ use rustc_lexer:: unescape;
2324use std:: iter;
2425use std:: str;
2526use std:: string;
@@ -56,6 +57,13 @@ impl InnerWidthMapping {
5657 }
5758}
5859
60+ /// Whether the input string is a literal. If yes, it contains the inner width mappings.
61+ #[ derive( Clone , PartialEq , Eq ) ]
62+ enum InputStringKind {
63+ NotALiteral ,
64+ Literal { width_mappings : Vec < InnerWidthMapping > } ,
65+ }
66+
5967/// The type of format string that we are parsing.
6068#[ derive( Copy , Clone , Debug , Eq , PartialEq ) ]
6169pub enum ParseMode {
@@ -306,7 +314,11 @@ impl<'a> Parser<'a> {
306314 append_newline : bool ,
307315 mode : ParseMode ,
308316 ) -> Parser < ' a > {
309- let ( width_map, is_literal) = find_width_map_from_snippet ( snippet, style) ;
317+ let input_string_kind = find_width_map_from_snippet ( s, snippet, style) ;
318+ let ( width_map, is_literal) = match input_string_kind {
319+ InputStringKind :: Literal { width_mappings } => ( width_mappings, true ) ,
320+ InputStringKind :: NotALiteral => ( Vec :: new ( ) , false ) ,
321+ } ;
310322 Parser {
311323 mode,
312324 input : s,
@@ -844,20 +856,40 @@ impl<'a> Parser<'a> {
844856/// written code (code snippet) and the `InternedString` that gets processed in the `Parser`
845857/// in order to properly synthesise the intra-string `Span`s for error diagnostics.
846858fn find_width_map_from_snippet (
859+ input : & str ,
847860 snippet : Option < string:: String > ,
848861 str_style : Option < usize > ,
849- ) -> ( Vec < InnerWidthMapping > , bool ) {
862+ ) -> InputStringKind {
850863 let snippet = match snippet {
851864 Some ( ref s) if s. starts_with ( '"' ) || s. starts_with ( "r\" " ) || s. starts_with ( "r#" ) => s,
852- _ => return ( vec ! [ ] , false ) ,
865+ _ => return InputStringKind :: NotALiteral ,
853866 } ;
854867
855868 if str_style. is_some ( ) {
856- return ( vec ! [ ] , true ) ;
869+ return InputStringKind :: Literal { width_mappings : Vec :: new ( ) } ;
857870 }
858871
872+ // Strip quotes.
859873 let snippet = & snippet[ 1 ..snippet. len ( ) - 1 ] ;
860874
875+ // Macros like `println` add a newline at the end. That technically doens't make them "literals" anymore, but it's fine
876+ // since we will never need to point our spans there, so we lie about it here by ignoring it.
877+ // Since there might actually be newlines in the source code, we need to normalize away all trailing newlines.
878+ // If we only trimmed it off the input, `format!("\n")` would cause a mismatch as here we they actually match up.
879+ // Alternatively, we could just count the trailing newlines and only trim one from the input if they don't match up.
880+ let input_no_nl = input. trim_end_matches ( '\n' ) ;
881+ let Ok ( unescaped) = unescape_string ( snippet) else {
882+ return InputStringKind :: NotALiteral ;
883+ } ;
884+
885+ let unescaped_no_nl = unescaped. trim_end_matches ( '\n' ) ;
886+
887+ if unescaped_no_nl != input_no_nl {
888+ // The source string that we're pointing at isn't our input, so spans pointing at it will be incorrect.
889+ // This can for example happen with proc macros that respan generated literals.
890+ return InputStringKind :: NotALiteral ;
891+ }
892+
861893 let mut s = snippet. char_indices ( ) ;
862894 let mut width_mappings = vec ! [ ] ;
863895 while let Some ( ( pos, c) ) = s. next ( ) {
@@ -936,7 +968,21 @@ fn find_width_map_from_snippet(
936968 _ => { }
937969 }
938970 }
939- ( width_mappings, true )
971+
972+ InputStringKind :: Literal { width_mappings }
973+ }
974+
975+ fn unescape_string ( string : & str ) -> Result < string:: String , unescape:: EscapeError > {
976+ let mut buf = string:: String :: new ( ) ;
977+ let mut error = Ok ( ( ) ) ;
978+ unescape:: unescape_literal ( string, unescape:: Mode :: Str , & mut |_, unescaped_char| {
979+ match unescaped_char {
980+ Ok ( c) => buf. push ( c) ,
981+ Err ( err) => error = Err ( err) ,
982+ }
983+ } ) ;
984+
985+ error. map ( |_| buf)
940986}
941987
942988// Assert a reasonable size for `Piece`
0 commit comments