@@ -63,6 +63,7 @@ pub(crate) fn parse_token_trees<'psess, 'src>(
6363 cursor,
6464 override_span,
6565 nbsp_is_whitespace : false ,
66+ last_lifetime : None ,
6667 } ;
6768 let ( stream, res, unmatched_delims) =
6869 tokentrees:: TokenTreesReader :: parse_all_token_trees ( string_reader) ;
@@ -105,6 +106,10 @@ struct StringReader<'psess, 'src> {
105106 /// in this file, it's safe to treat further occurrences of the non-breaking
106107 /// space character as whitespace.
107108 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 > ,
108113}
109114
110115impl < ' psess , ' src > StringReader < ' psess , ' src > {
@@ -130,6 +135,18 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
130135
131136 debug ! ( "next_token: {:?}({:?})" , token. kind, self . str_from( start) ) ;
132137
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+
133150 // Now "cook" the token, converting the simple `rustc_lexer::TokenKind` enum into a
134151 // rich `rustc_ast::TokenKind`. This turns strings into interned symbols and runs
135152 // additional validation.
@@ -247,6 +264,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
247264 // expansion purposes. See #12512 for the gory details of why
248265 // this is necessary.
249266 let lifetime_name = self . str_from ( start) ;
267+ self . last_lifetime = Some ( self . mk_sp ( start, start + BytePos ( 1 ) ) ) ;
250268 if starts_with_number {
251269 let span = self . mk_sp ( start, self . pos ) ;
252270 self . dcx ( ) . struct_err ( "lifetimes cannot start with a number" )
@@ -395,10 +413,21 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
395413 match kind {
396414 rustc_lexer:: LiteralKind :: Char { terminated } => {
397415 if !terminated {
398- self . dcx ( )
416+ let mut err = self
417+ . dcx ( )
399418 . 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 ( )
402431 }
403432 self . cook_unicode ( token:: Char , Mode :: Char , start, end, 1 , 1 ) // ' '
404433 }
@@ -669,15 +698,33 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
669698 let expn_data = prefix_span. ctxt ( ) . outer_expn_data ( ) ;
670699
671700 if expn_data. edition >= Edition :: Edition2021 {
701+ let mut silence = false ;
672702 // In Rust 2021, this is a hard error.
673703 let sugg = if prefix == "rb" {
674704 Some ( errors:: UnknownPrefixSugg :: UseBr ( prefix_span) )
675705 } 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+ }
677719 } else {
678720 None
679721 } ;
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+ }
681728 } else {
682729 // Before Rust 2021, only emit a lint for migration.
683730 self . psess . buffer_lint_with_diagnostic (
0 commit comments