@@ -4,7 +4,7 @@ use crate::symbol::{sym, Symbol};
44use crate :: parse:: unescape;
55use crate :: parse:: unescape_error_reporting:: { emit_unescape_error, push_escaped_char} ;
66
7- use errors:: { FatalError , Diagnostic , DiagnosticBuilder } ;
7+ use errors:: { Applicability , FatalError , Diagnostic , DiagnosticBuilder } ;
88use syntax_pos:: { BytePos , Pos , Span , NO_EXPANSION } ;
99use core:: unicode:: property:: Pattern_White_Space ;
1010
@@ -145,19 +145,64 @@ impl<'a> StringReader<'a> {
145145 self . ch . is_none ( )
146146 }
147147
148- fn fail_unterminated_raw_string ( & self , pos : BytePos , hash_count : u16 ) -> ! {
149- let mut err = self . struct_span_fatal ( pos, pos, "unterminated raw string" ) ;
150- err. span_label ( self . mk_sp ( pos, pos) , "unterminated raw string" ) ;
148+ fn fail_unterminated_raw_string ( & self , start : Span , hash_count : u16 , spans : Vec < Span > ) -> ! {
149+ const SPAN_THRESHOLD : usize = 3 ;
150+ const MSG_STR : & str = "you might have meant to end the raw string here" ;
151+ let hash_str = format ! ( "\" {}" , "#" . repeat( hash_count as usize ) ) ;
152+ let spans_len = spans. len ( ) ;
151153
152- if hash_count > 0 {
153- err. note ( & format ! ( "this raw string should be terminated with `\" {}`" ,
154- "#" . repeat( hash_count as usize ) ) ) ;
154+ let mut err = self . sess . span_diagnostic . struct_span_fatal ( start, "unterminated raw string" ) ;
155+ err. span_label ( start, "unterminated raw string" ) ;
156+
157+ for s in spans {
158+ if spans_len < SPAN_THRESHOLD {
159+ err. span_suggestion (
160+ s,
161+ MSG_STR ,
162+ hash_str. clone ( ) ,
163+ Applicability :: MaybeIncorrect
164+ ) ;
165+ } else {
166+ err. tool_only_span_suggestion (
167+ s,
168+ MSG_STR ,
169+ hash_str. clone ( ) ,
170+ Applicability :: MaybeIncorrect
171+ ) ;
172+ }
173+ }
174+
175+ if hash_count > 0 && spans_len >= SPAN_THRESHOLD {
176+ err. note ( & format ! ( "this raw string should be terminated with `\" {}`" , hash_str) ) ;
155177 }
156178
157179 err. emit ( ) ;
158180 FatalError . raise ( ) ;
159181 }
160182
183+ fn fail_incorrect_raw_string_delimiter ( & mut self , start : BytePos ) -> ! {
184+ loop {
185+ match self . ch {
186+ Some ( '#' ) | Some ( '"' ) => break ,
187+ _ => self . bump ( ) ,
188+ }
189+ }
190+ let end = self . pos ;
191+ let span = self . mk_sp ( start, end) ;
192+ let mut err = self . sess . span_diagnostic . struct_span_fatal (
193+ span,
194+ "found invalid character; only `#` is allowed in raw string delimitation" ,
195+ ) ;
196+ err. span_suggestion_hidden (
197+ span,
198+ "replace with `#`" ,
199+ format ! ( "{}" , "#" . repeat( ( end. 0 - start. 0 ) as usize ) ) ,
200+ Applicability :: MachineApplicable ,
201+ ) ;
202+ err. emit ( ) ;
203+ FatalError . raise ( ) ;
204+ }
205+
161206 crate fn emit_fatal_errors ( & mut self ) {
162207 for err in & mut self . fatal_errs {
163208 err. emit ( ) ;
@@ -202,16 +247,6 @@ impl<'a> StringReader<'a> {
202247 self . err_span ( self . mk_sp ( from_pos, to_pos) , m)
203248 }
204249
205- /// Report a lexical error spanning [`from_pos`, `to_pos`), appending an
206- /// escaped character to the error message
207- fn fatal_span_char ( & self , from_pos : BytePos , to_pos : BytePos , m : & str , c : char ) -> FatalError {
208- let mut m = m. to_string ( ) ;
209- m. push_str ( ": " ) ;
210- push_escaped_char ( & mut m, c) ;
211-
212- self . fatal_span_ ( from_pos, to_pos, & m[ ..] )
213- }
214-
215250 fn struct_span_fatal ( & self , from_pos : BytePos , to_pos : BytePos , m : & str )
216251 -> DiagnosticBuilder < ' a >
217252 {
@@ -945,6 +980,7 @@ impl<'a> StringReader<'a> {
945980 Ok ( TokenKind :: lit ( token:: Char , symbol, suffix) )
946981 }
947982 'b' => {
983+ let start_bpos = self . pos ;
948984 self . bump ( ) ;
949985 let ( kind, symbol) = match self . ch {
950986 Some ( '\'' ) => {
@@ -963,7 +999,7 @@ impl<'a> StringReader<'a> {
963999 ( token:: ByteStr , symbol)
9641000 } ,
9651001 Some ( 'r' ) => {
966- let ( start, end, hash_count) = self . scan_raw_string ( ) ;
1002+ let ( start, end, hash_count) = self . scan_raw_string ( start_bpos ) ;
9671003 let symbol = self . symbol_from_to ( start, end) ;
9681004 self . validate_raw_byte_str_escape ( start, end) ;
9691005
@@ -984,7 +1020,7 @@ impl<'a> StringReader<'a> {
9841020 Ok ( TokenKind :: lit ( token:: Str , symbol, suffix) )
9851021 }
9861022 'r' => {
987- let ( start, end, hash_count) = self . scan_raw_string ( ) ;
1023+ let ( start, end, hash_count) = self . scan_raw_string ( self . pos ) ;
9881024 let symbol = self . symbol_from_to ( start, end) ;
9891025 self . validate_raw_str_escape ( start, end) ;
9901026 let suffix = self . scan_optional_raw_name ( ) ;
@@ -1145,8 +1181,7 @@ impl<'a> StringReader<'a> {
11451181
11461182 /// Scans a raw (byte) string, returning byte position range for `"<literal>"`
11471183 /// (including quotes) along with `#` character count in `(b)r##..."<literal>"##...`;
1148- fn scan_raw_string ( & mut self ) -> ( BytePos , BytePos , u16 ) {
1149- let start_bpos = self . pos ;
1184+ fn scan_raw_string ( & mut self , start_bpos : BytePos ) -> ( BytePos , BytePos , u16 ) {
11501185 self . bump ( ) ;
11511186 let mut hash_count: u16 = 0 ;
11521187 while self . ch_is ( '#' ) {
@@ -1161,30 +1196,32 @@ impl<'a> StringReader<'a> {
11611196 hash_count += 1 ;
11621197 }
11631198
1164- if self . is_eof ( ) {
1165- self . fail_unterminated_raw_string ( start_bpos , hash_count ) ;
1166- } else if ! self . ch_is ( '"' ) {
1167- let last_bpos = self . pos ;
1168- let curr_char = self . ch . unwrap ( ) ;
1169- self . fatal_span_char ( start_bpos ,
1170- last_bpos ,
1171- "found invalid character; only `#` is allowed \
1172- in raw string delimitation" ,
1173- curr_char ) . raise ( ) ;
1199+ let bpos_span = self . mk_sp ( start_bpos , self . pos ) ;
1200+
1201+ match self . ch {
1202+ None => self . fail_unterminated_raw_string (
1203+ bpos_span ,
1204+ hash_count ,
1205+ vec ! [ self . mk_sp ( self . pos , self . pos ) ]
1206+ ) ,
1207+ Some ( '"' ) => ( ) ,
1208+ Some ( _ ) => self . fail_incorrect_raw_string_delimiter ( self . pos ) ,
11741209 }
1210+
11751211 self . bump ( ) ;
11761212 let content_start_bpos = self . pos ;
11771213 let mut content_end_bpos;
1214+ let mut spans = vec ! [ ] ;
1215+
11781216 ' outer: loop {
11791217 match self . ch {
1180- None => {
1181- self . fail_unterminated_raw_string ( start_bpos, hash_count) ;
1182- }
1218+ None => self . fail_unterminated_raw_string ( bpos_span, hash_count, spans) ,
11831219 Some ( '"' ) => {
11841220 content_end_bpos = self . pos ;
11851221 for _ in 0 ..hash_count {
11861222 self . bump ( ) ;
11871223 if !self . ch_is ( '#' ) {
1224+ spans. push ( self . mk_sp ( content_end_bpos, self . pos ) ) ;
11881225 continue ' outer;
11891226 }
11901227 }
0 commit comments