@@ -63,6 +63,7 @@ pub(crate) fn parse_token_trees<'psess, 'src>(
63
63
cursor,
64
64
override_span,
65
65
nbsp_is_whitespace : false ,
66
+ last_lifetime : None ,
66
67
} ;
67
68
let ( stream, res, unmatched_delims) =
68
69
tokentrees:: TokenTreesReader :: parse_all_token_trees ( string_reader) ;
@@ -105,6 +106,10 @@ struct StringReader<'psess, 'src> {
105
106
/// in this file, it's safe to treat further occurrences of the non-breaking
106
107
/// space character as whitespace.
107
108
nbsp_is_whitespace : bool ,
109
+
110
+ /// Track the `Span` for the leading `'` of the last lifetime. Used for
111
+ /// diagnostics to detect possible typo where `"` was meant.
112
+ last_lifetime : Option < Span > ,
108
113
}
109
114
110
115
impl < ' psess , ' src > StringReader < ' psess , ' src > {
@@ -130,6 +135,18 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
130
135
131
136
debug ! ( "next_token: {:?}({:?})" , token. kind, self . str_from( start) ) ;
132
137
138
+ if let rustc_lexer:: TokenKind :: Semi
139
+ | rustc_lexer:: TokenKind :: LineComment { .. }
140
+ | rustc_lexer:: TokenKind :: BlockComment { .. }
141
+ | rustc_lexer:: TokenKind :: CloseParen
142
+ | rustc_lexer:: TokenKind :: CloseBrace
143
+ | rustc_lexer:: TokenKind :: CloseBracket = token. kind
144
+ {
145
+ // Heuristic: we assume that it is unlikely we're dealing with an unterminated
146
+ // string surrounded by single quotes.
147
+ self . last_lifetime = None ;
148
+ }
149
+
133
150
// Now "cook" the token, converting the simple `rustc_lexer::TokenKind` enum into a
134
151
// rich `rustc_ast::TokenKind`. This turns strings into interned symbols and runs
135
152
// additional validation.
@@ -247,6 +264,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
247
264
// expansion purposes. See #12512 for the gory details of why
248
265
// this is necessary.
249
266
let lifetime_name = self . str_from ( start) ;
267
+ self . last_lifetime = Some ( self . mk_sp ( start, start + BytePos ( 1 ) ) ) ;
250
268
if starts_with_number {
251
269
let span = self . mk_sp ( start, self . pos ) ;
252
270
self . dcx ( ) . struct_err ( "lifetimes cannot start with a number" )
@@ -395,10 +413,21 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
395
413
match kind {
396
414
rustc_lexer:: LiteralKind :: Char { terminated } => {
397
415
if !terminated {
398
- self . dcx ( )
416
+ let mut err = self
417
+ . dcx ( )
399
418
. struct_span_fatal ( self . mk_sp ( start, end) , "unterminated character literal" )
400
- . with_code ( E0762 )
401
- . emit ( )
419
+ . with_code ( E0762 ) ;
420
+ if let Some ( lt_sp) = self . last_lifetime {
421
+ err. multipart_suggestion (
422
+ "if you meant to write a string literal, use double quotes" ,
423
+ vec ! [
424
+ ( lt_sp, "\" " . to_string( ) ) ,
425
+ ( self . mk_sp( start, start + BytePos ( 1 ) ) , "\" " . to_string( ) ) ,
426
+ ] ,
427
+ Applicability :: MaybeIncorrect ,
428
+ ) ;
429
+ }
430
+ err. emit ( )
402
431
}
403
432
self . cook_unicode ( token:: Char , Mode :: Char , start, end, 1 , 1 ) // ' '
404
433
}
@@ -669,15 +698,33 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
669
698
let expn_data = prefix_span. ctxt ( ) . outer_expn_data ( ) ;
670
699
671
700
if expn_data. edition >= Edition :: Edition2021 {
701
+ let mut silence = false ;
672
702
// In Rust 2021, this is a hard error.
673
703
let sugg = if prefix == "rb" {
674
704
Some ( errors:: UnknownPrefixSugg :: UseBr ( prefix_span) )
675
705
} else if expn_data. is_root ( ) {
676
- Some ( errors:: UnknownPrefixSugg :: Whitespace ( prefix_span. shrink_to_hi ( ) ) )
706
+ if self . cursor . first ( ) == '\''
707
+ && let Some ( start) = self . last_lifetime
708
+ && self . cursor . third ( ) != '\''
709
+ {
710
+ // An "unclosed `char`" error will be emitted already, silence redundant error.
711
+ silence = true ;
712
+ Some ( errors:: UnknownPrefixSugg :: MeantStr {
713
+ start,
714
+ end : self . mk_sp ( self . pos , self . pos + BytePos ( 1 ) ) ,
715
+ } )
716
+ } else {
717
+ Some ( errors:: UnknownPrefixSugg :: Whitespace ( prefix_span. shrink_to_hi ( ) ) )
718
+ }
677
719
} else {
678
720
None
679
721
} ;
680
- self . dcx ( ) . emit_err ( errors:: UnknownPrefix { span : prefix_span, prefix, sugg } ) ;
722
+ let err = errors:: UnknownPrefix { span : prefix_span, prefix, sugg } ;
723
+ if silence {
724
+ self . dcx ( ) . create_err ( err) . delay_as_bug ( ) ;
725
+ } else {
726
+ self . dcx ( ) . emit_err ( err) ;
727
+ }
681
728
} else {
682
729
// Before Rust 2021, only emit a lint for migration.
683
730
self . psess . buffer_lint_with_diagnostic (
0 commit comments