From 982918f4935fd4677af06d42e0d0b298bfb1c243 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Sat, 9 Mar 2024 00:35:57 +0000
Subject: [PATCH 1/6] Handle str literals written with `'` lexed as lifetime

Given `'hello world'` and `'1 str', provide a structured suggestion for a valid string literal:

```
error[E0762]: unterminated character literal
  --> $DIR/lex-bad-str-literal-as-char-3.rs:2:26
   |
LL |     println!('hello world');
   |                          ^^^^
   |
help: if you meant to write a `str` literal, use double quotes
   |
LL |     println!("hello world");
   |              ~           ~
```
```
error[E0762]: unterminated character literal
  --> $DIR/lex-bad-str-literal-as-char-1.rs:2:20
   |
LL |     println!('1 + 1');
   |                    ^^^^
   |
help: if you meant to write a `str` literal, use double quotes
   |
LL |     println!("1 + 1");
   |              ~     ~
```

Fix #119685.
---
 compiler/rustc_lexer/src/cursor.rs            |  2 +-
 compiler/rustc_parse/messages.ftl             |  1 +
 compiler/rustc_parse/src/errors.rs            | 11 +++++
 compiler/rustc_parse/src/lexer/mod.rs         | 46 +++++++++++++++++--
 .../lexer/lex-bad-str-literal-as-char-1.fixed |  6 +++
 .../ui/lexer/lex-bad-str-literal-as-char-1.rs |  6 +++
 .../lex-bad-str-literal-as-char-1.stderr      | 20 ++++++++
 .../lexer/lex-bad-str-literal-as-char-2.fixed |  4 ++
 .../ui/lexer/lex-bad-str-literal-as-char-2.rs |  4 ++
 .../lex-bad-str-literal-as-char-2.stderr      | 13 ++++++
 .../lexer/lex-bad-str-literal-as-char-3.fixed |  4 ++
 .../ui/lexer/lex-bad-str-literal-as-char-3.rs |  4 ++
 .../lex-bad-str-literal-as-char-3.stderr      | 14 ++++++
 13 files changed, 130 insertions(+), 5 deletions(-)
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-1.fixed
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-1.rs
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-2.fixed
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-2.rs
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-3.fixed
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr

diff --git a/compiler/rustc_lexer/src/cursor.rs b/compiler/rustc_lexer/src/cursor.rs
index aba7f95487e9d..8d8cc9ecc3e9f 100644
--- a/compiler/rustc_lexer/src/cursor.rs
+++ b/compiler/rustc_lexer/src/cursor.rs
@@ -46,7 +46,7 @@ impl<'a> Cursor<'a> {
     /// If requested position doesn't exist, `EOF_CHAR` is returned.
     /// However, getting `EOF_CHAR` doesn't always mean actual end of file,
     /// it should be checked with `is_eof` method.
-    pub(crate) fn first(&self) -> char {
+    pub fn first(&self) -> char {
         // `.next()` optimizes better than `.nth(0)`
         self.chars.clone().next().unwrap_or(EOF_CHAR)
     }
diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl
index a100e2d47bbbb..888c621f02afd 100644
--- a/compiler/rustc_parse/messages.ftl
+++ b/compiler/rustc_parse/messages.ftl
@@ -835,6 +835,7 @@ parse_unknown_prefix = prefix `{$prefix}` is unknown
     .label = unknown prefix
     .note =  prefixed identifiers and literals are reserved since Rust 2021
     .suggestion_br = use `br` for a raw byte string
+    .suggestion_str = if you meant to write a `str` literal, use double quotes
     .suggestion_whitespace = consider inserting whitespace here
 
 parse_unknown_start_of_token = unknown start of token: {$escaped}
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index 32b56bb7e877e..140eb6cd18752 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -1994,6 +1994,17 @@ pub enum UnknownPrefixSugg {
         style = "verbose"
     )]
     Whitespace(#[primary_span] Span),
+    #[multipart_suggestion(
+        parse_suggestion_str,
+        applicability = "maybe-incorrect",
+        style = "verbose"
+    )]
+    MeantStr {
+        #[suggestion_part(code = "\"")]
+        start: Span,
+        #[suggestion_part(code = "\"")]
+        end: Span,
+    },
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index f57945a52df37..659e0b63d2ed8 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -63,6 +63,7 @@ pub(crate) fn parse_token_trees<'psess, 'src>(
         cursor,
         override_span,
         nbsp_is_whitespace: false,
+        last_lifetime: None,
     };
     let (stream, res, unmatched_delims) =
         tokentrees::TokenTreesReader::parse_all_token_trees(string_reader);
@@ -105,6 +106,10 @@ struct StringReader<'psess, 'src> {
     /// in this file, it's safe to treat further occurrences of the non-breaking
     /// space character as whitespace.
     nbsp_is_whitespace: bool,
+
+    /// Track the `Span` for the leading `'` of the last lifetime. Used for
+    /// diagnostics to detect possible typo where `"` was meant.
+    last_lifetime: Option<Span>,
 }
 
 impl<'psess, 'src> StringReader<'psess, 'src> {
@@ -130,6 +135,18 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
 
             debug!("next_token: {:?}({:?})", token.kind, self.str_from(start));
 
+            if let rustc_lexer::TokenKind::Semi
+            | rustc_lexer::TokenKind::LineComment { .. }
+            | rustc_lexer::TokenKind::BlockComment { .. }
+            | rustc_lexer::TokenKind::CloseParen
+            | rustc_lexer::TokenKind::CloseBrace
+            | rustc_lexer::TokenKind::CloseBracket = token.kind
+            {
+                // Heuristic: we assume that it is unlikely we're dealing with an unterminated
+                // string surrounded by single quotes.
+                self.last_lifetime = None;
+            }
+
             // Now "cook" the token, converting the simple `rustc_lexer::TokenKind` enum into a
             // rich `rustc_ast::TokenKind`. This turns strings into interned symbols and runs
             // additional validation.
@@ -247,6 +264,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
                     // expansion purposes. See #12512 for the gory details of why
                     // this is necessary.
                     let lifetime_name = self.str_from(start);
+                    self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1)));
                     if starts_with_number {
                         let span = self.mk_sp(start, self.pos);
                         self.dcx().struct_err("lifetimes cannot start with a number")
@@ -395,10 +413,21 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
         match kind {
             rustc_lexer::LiteralKind::Char { terminated } => {
                 if !terminated {
-                    self.dcx()
+                    let mut err = self
+                        .dcx()
                         .struct_span_fatal(self.mk_sp(start, end), "unterminated character literal")
-                        .with_code(E0762)
-                        .emit()
+                        .with_code(E0762);
+                    if let Some(lt_sp) = self.last_lifetime {
+                        err.multipart_suggestion(
+                            "if you meant to write a `str` literal, use double quotes",
+                            vec![
+                                (lt_sp, "\"".to_string()),
+                                (self.mk_sp(start, start + BytePos(1)), "\"".to_string()),
+                            ],
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                    err.emit()
                 }
                 self.cook_unicode(token::Char, Mode::Char, start, end, 1, 1) // ' '
             }
@@ -673,7 +702,16 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
             let sugg = if prefix == "rb" {
                 Some(errors::UnknownPrefixSugg::UseBr(prefix_span))
             } else if expn_data.is_root() {
-                Some(errors::UnknownPrefixSugg::Whitespace(prefix_span.shrink_to_hi()))
+                if self.cursor.first() == '\''
+                    && let Some(start) = self.last_lifetime
+                {
+                    Some(errors::UnknownPrefixSugg::MeantStr {
+                        start,
+                        end: self.mk_sp(self.pos, self.pos + BytePos(1)),
+                    })
+                } else {
+                    Some(errors::UnknownPrefixSugg::Whitespace(prefix_span.shrink_to_hi()))
+                }
             } else {
                 None
             };
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-1.fixed b/tests/ui/lexer/lex-bad-str-literal-as-char-1.fixed
new file mode 100644
index 0000000000000..b12139b0b40e9
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-1.fixed
@@ -0,0 +1,6 @@
+//@ run-rustfix
+fn main() {
+    println!("1 + 1");
+    //~^ ERROR unterminated character literal
+    //~| ERROR lifetimes cannot start with a number
+}
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-1.rs b/tests/ui/lexer/lex-bad-str-literal-as-char-1.rs
new file mode 100644
index 0000000000000..6548792f33b4f
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-1.rs
@@ -0,0 +1,6 @@
+//@ run-rustfix
+fn main() {
+    println!('1 + 1');
+    //~^ ERROR unterminated character literal
+    //~| ERROR lifetimes cannot start with a number
+}
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr
new file mode 100644
index 0000000000000..675624cfa9415
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr
@@ -0,0 +1,20 @@
+error[E0762]: unterminated character literal
+  --> $DIR/lex-bad-str-literal-as-char-1.rs:3:20
+   |
+LL |     println!('1 + 1');
+   |                    ^^^
+   |
+help: if you meant to write a `str` literal, use double quotes
+   |
+LL |     println!("1 + 1");
+   |              ~     ~
+
+error: lifetimes cannot start with a number
+  --> $DIR/lex-bad-str-literal-as-char-1.rs:3:14
+   |
+LL |     println!('1 + 1');
+   |              ^^
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0762`.
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-2.fixed b/tests/ui/lexer/lex-bad-str-literal-as-char-2.fixed
new file mode 100644
index 0000000000000..3ccec537c6c34
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-2.fixed
@@ -0,0 +1,4 @@
+//@ run-rustfix
+fn main() {
+    println!(" 1 + 1"); //~ ERROR character literal may only contain one codepoint
+}
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-2.rs b/tests/ui/lexer/lex-bad-str-literal-as-char-2.rs
new file mode 100644
index 0000000000000..8af72e47dbb4b
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-2.rs
@@ -0,0 +1,4 @@
+//@ run-rustfix
+fn main() {
+    println!(' 1 + 1'); //~ ERROR character literal may only contain one codepoint
+}
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
new file mode 100644
index 0000000000000..8445d0595f3ed
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
@@ -0,0 +1,13 @@
+error: character literal may only contain one codepoint
+  --> $DIR/lex-bad-str-literal-as-char-2.rs:3:14
+   |
+LL |     println!(' 1 + 1');
+   |              ^^^^^^^^
+   |
+help: if you meant to write a `str` literal, use double quotes
+   |
+LL |     println!(" 1 + 1");
+   |              ~~~~~~~~
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.fixed b/tests/ui/lexer/lex-bad-str-literal-as-char-3.fixed
new file mode 100644
index 0000000000000..3fbe0ea1dabef
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.fixed
@@ -0,0 +1,4 @@
+//@ run-rustfix
+fn main() {
+    println!("hello world"); //~ ERROR unterminated character literal
+}
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
new file mode 100644
index 0000000000000..289f3f1d6570f
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
@@ -0,0 +1,4 @@
+//@ run-rustfix
+fn main() {
+    println!('hello world'); //~ ERROR unterminated character literal
+}
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr
new file mode 100644
index 0000000000000..ebfbeac02600b
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr
@@ -0,0 +1,14 @@
+error[E0762]: unterminated character literal
+  --> $DIR/lex-bad-str-literal-as-char-3.rs:3:26
+   |
+LL |     println!('hello world');
+   |                          ^^^^
+   |
+help: if you meant to write a `str` literal, use double quotes
+   |
+LL |     println!("hello world");
+   |              ~           ~
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0762`.

From 4a10b01f9504f8ad2ffb9b357845341f4fba6bf0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Sat, 9 Mar 2024 01:07:23 +0000
Subject: [PATCH 2/6] Use shorter span for existing `'` -> `"` structured
 suggestion

---
 compiler/rustc_infer/src/errors/mod.rs        | 13 +++++-------
 .../src/infer/error_reporting/mod.rs          | 14 ++++---------
 compiler/rustc_parse/src/errors.rs            | 11 +++++++++-
 .../src/lexer/unescape_error_reporting.rs     | 20 ++++++++++++++-----
 tests/ui/inference/str-as-char.stderr         |  4 ++--
 tests/ui/issues/issue-23589.stderr            |  2 +-
 tests/ui/lexer/lex-bad-char-literals-2.stderr |  2 +-
 tests/ui/lexer/lex-bad-char-literals-3.stderr |  4 ++--
 tests/ui/lexer/lex-bad-char-literals-5.stderr |  4 ++--
 tests/ui/lexer/lex-bad-char-literals-6.stderr |  6 +++---
 .../lex-bad-str-literal-as-char-2.stderr      |  2 +-
 tests/ui/parser/issues/issue-64732.stderr     |  4 ++--
 .../parser/unicode-character-literal.stderr   |  4 ++--
 tests/ui/str/str-as-char.stderr               |  2 +-
 14 files changed, 51 insertions(+), 41 deletions(-)

diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs
index a3cf0d8e5208a..d0b1f2848ff3f 100644
--- a/compiler/rustc_infer/src/errors/mod.rs
+++ b/compiler/rustc_infer/src/errors/mod.rs
@@ -1339,15 +1339,12 @@ pub enum TypeErrorAdditionalDiags {
         span: Span,
         code: String,
     },
-    #[suggestion(
-        infer_meant_str_literal,
-        code = "\"{code}\"",
-        applicability = "machine-applicable"
-    )]
+    #[multipart_suggestion(infer_meant_str_literal, applicability = "machine-applicable")]
     MeantStrLiteral {
-        #[primary_span]
-        span: Span,
-        code: String,
+        #[suggestion_part(code = "\"")]
+        start: Span,
+        #[suggestion_part(code = "\"")]
+        end: Span,
     },
     #[suggestion(
         infer_consider_specifying_length,
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 5d2a95593cd25..4b104756b181f 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -2078,16 +2078,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 // If a string was expected and the found expression is a character literal,
                 // perhaps the user meant to write `"s"` to specify a string literal.
                 (ty::Ref(_, r, _), ty::Char) if r.is_str() => {
-                    if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span) {
-                        if let Some(code) =
-                            code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
-                        {
-                            suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral {
-                                span,
-                                code: escape_literal(code),
-                            })
-                        }
-                    }
+                    suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral {
+                        start: span.with_hi(span.lo() + BytePos(1)),
+                        end: span.with_lo(span.hi() - BytePos(1)),
+                    })
                 }
                 // For code `if Some(..) = expr `, the type mismatch may be expected `bool` but found `()`,
                 // we try to suggest to add the missing `let` for `if let Some(..) = expr`
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index 140eb6cd18752..d12818444fc5f 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -2216,12 +2216,21 @@ pub enum MoreThanOneCharSugg {
         ch: String,
     },
     #[suggestion(parse_use_double_quotes, code = "{sugg}", applicability = "machine-applicable")]
-    Quotes {
+    QuotesFull {
         #[primary_span]
         span: Span,
         is_byte: bool,
         sugg: String,
     },
+    #[multipart_suggestion(parse_use_double_quotes, applicability = "machine-applicable")]
+    Quotes {
+        #[suggestion_part(code = "{prefix}\"")]
+        start: Span,
+        #[suggestion_part(code = "\"")]
+        end: Span,
+        is_byte: bool,
+        prefix: &'static str,
+    },
 }
 
 #[derive(Subdiagnostic)]
diff --git a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
index 3ebad6a9fd73e..1ac6b6fe11566 100644
--- a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
+++ b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
@@ -95,11 +95,21 @@ pub(crate) fn emit_unescape_error(
                     }
                     escaped.push(c);
                 }
-                let sugg = format!("{prefix}\"{escaped}\"");
-                MoreThanOneCharSugg::Quotes {
-                    span: full_lit_span,
-                    is_byte: mode == Mode::Byte,
-                    sugg,
+                if escaped.len() != lit.len() {
+                    let sugg = format!("{prefix}\"{escaped}\"");
+                    MoreThanOneCharSugg::QuotesFull {
+                        span: full_lit_span,
+                        is_byte: mode == Mode::Byte,
+                        sugg,
+                    }
+                } else {
+                    MoreThanOneCharSugg::Quotes {
+                        start: full_lit_span
+                            .with_hi(full_lit_span.lo() + BytePos((prefix.len() + 1) as u32)),
+                        end: full_lit_span.with_lo(full_lit_span.hi() - BytePos(1)),
+                        is_byte: mode == Mode::Byte,
+                        prefix,
+                    }
                 }
             });
             dcx.emit_err(UnescapeError::MoreThanOneChar {
diff --git a/tests/ui/inference/str-as-char.stderr b/tests/ui/inference/str-as-char.stderr
index 216f4cda69888..42302435c91bf 100644
--- a/tests/ui/inference/str-as-char.stderr
+++ b/tests/ui/inference/str-as-char.stderr
@@ -18,7 +18,7 @@ LL |     let _: &str = '\"\"\"';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let _: &str = "\"\"\"";
-   |                   ~~~~~~~~
+   |                   ~      ~
 
 error: character literal may only contain one codepoint
   --> $DIR/str-as-char.rs:10:19
@@ -42,7 +42,7 @@ LL |     let _: &str = 'a';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let _: &str = "a";
-   |                   ~~~
+   |                   ~ ~
 
 error: aborting due to 4 previous errors
 
diff --git a/tests/ui/issues/issue-23589.stderr b/tests/ui/issues/issue-23589.stderr
index 1a91f5e04dbce..bf055a7e31c59 100644
--- a/tests/ui/issues/issue-23589.stderr
+++ b/tests/ui/issues/issue-23589.stderr
@@ -18,7 +18,7 @@ LL |     let v: Vec(&str) = vec!['1', '2'];
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let v: Vec(&str) = vec!["1", '2'];
-   |                             ~~~
+   |                             ~ ~
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/lexer/lex-bad-char-literals-2.stderr b/tests/ui/lexer/lex-bad-char-literals-2.stderr
index 1518a37ab53f3..af64e6efe45ff 100644
--- a/tests/ui/lexer/lex-bad-char-literals-2.stderr
+++ b/tests/ui/lexer/lex-bad-char-literals-2.stderr
@@ -7,7 +7,7 @@ LL |     'nope'
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     "nope"
-   |     ~~~~~~
+   |     ~    ~
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/lexer/lex-bad-char-literals-3.stderr b/tests/ui/lexer/lex-bad-char-literals-3.stderr
index 62a5e424cb469..312b42fc83174 100644
--- a/tests/ui/lexer/lex-bad-char-literals-3.stderr
+++ b/tests/ui/lexer/lex-bad-char-literals-3.stderr
@@ -7,7 +7,7 @@ LL | static c: char = '●●';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL | static c: char = "●●";
-   |                  ~~~~
+   |                  ~  ~
 
 error: character literal may only contain one codepoint
   --> $DIR/lex-bad-char-literals-3.rs:5:20
@@ -18,7 +18,7 @@ LL |     let ch: &str = '●●';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let ch: &str = "●●";
-   |                    ~~~~
+   |                    ~  ~
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/lexer/lex-bad-char-literals-5.stderr b/tests/ui/lexer/lex-bad-char-literals-5.stderr
index 184817a6579d0..185f460b10def 100644
--- a/tests/ui/lexer/lex-bad-char-literals-5.stderr
+++ b/tests/ui/lexer/lex-bad-char-literals-5.stderr
@@ -7,7 +7,7 @@ LL | static c: char = '\x10\x10';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL | static c: char = "\x10\x10";
-   |                  ~~~~~~~~~~
+   |                  ~        ~
 
 error: character literal may only contain one codepoint
   --> $DIR/lex-bad-char-literals-5.rs:5:20
@@ -18,7 +18,7 @@ LL |     let ch: &str = '\x10\x10';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let ch: &str = "\x10\x10";
-   |                    ~~~~~~~~~~
+   |                    ~        ~
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/lexer/lex-bad-char-literals-6.stderr b/tests/ui/lexer/lex-bad-char-literals-6.stderr
index 2fe30304a50d6..f49e5a095b34a 100644
--- a/tests/ui/lexer/lex-bad-char-literals-6.stderr
+++ b/tests/ui/lexer/lex-bad-char-literals-6.stderr
@@ -7,7 +7,7 @@ LL |     let x: &str = 'ab';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let x: &str = "ab";
-   |                   ~~~~
+   |                   ~  ~
 
 error: character literal may only contain one codepoint
   --> $DIR/lex-bad-char-literals-6.rs:4:19
@@ -18,7 +18,7 @@ LL |     let y: char = 'cd';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let y: char = "cd";
-   |                   ~~~~
+   |                   ~  ~
 
 error: character literal may only contain one codepoint
   --> $DIR/lex-bad-char-literals-6.rs:6:13
@@ -29,7 +29,7 @@ LL |     let z = 'ef';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let z = "ef";
-   |             ~~~~
+   |             ~  ~
 
 error[E0308]: mismatched types
   --> $DIR/lex-bad-char-literals-6.rs:13:20
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
index 8445d0595f3ed..06b38522ad2dd 100644
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
@@ -7,7 +7,7 @@ LL |     println!(' 1 + 1');
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     println!(" 1 + 1");
-   |              ~~~~~~~~
+   |              ~      ~
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/parser/issues/issue-64732.stderr b/tests/ui/parser/issues/issue-64732.stderr
index 8046254937763..8893fa8aae239 100644
--- a/tests/ui/parser/issues/issue-64732.stderr
+++ b/tests/ui/parser/issues/issue-64732.stderr
@@ -7,7 +7,7 @@ LL |     let _foo = b'hello\0';
 help: if you meant to write a byte string literal, use double quotes
    |
 LL |     let _foo = b"hello\0";
-   |                ~~~~~~~~~~
+   |                ~~       ~
 
 error: character literal may only contain one codepoint
   --> $DIR/issue-64732.rs:6:16
@@ -18,7 +18,7 @@ LL |     let _bar = 'hello';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let _bar = "hello";
-   |                ~~~~~~~
+   |                ~     ~
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/parser/unicode-character-literal.stderr b/tests/ui/parser/unicode-character-literal.stderr
index 5cd3bd0fe69d7..1104eaeb8d478 100644
--- a/tests/ui/parser/unicode-character-literal.stderr
+++ b/tests/ui/parser/unicode-character-literal.stderr
@@ -12,7 +12,7 @@ LL |     let _spade = '♠️';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let _spade = "♠️";
-   |                  ~~~
+   |                  ~ ~
 
 error: character literal may only contain one codepoint
   --> $DIR/unicode-character-literal.rs:12:14
@@ -28,7 +28,7 @@ LL |     let _s = 'ṩ̂̊';
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     let _s = "ṩ̂̊";
-   |              ~~~
+   |              ~ ~
 
 error: character literal may only contain one codepoint
   --> $DIR/unicode-character-literal.rs:17:14
diff --git a/tests/ui/str/str-as-char.stderr b/tests/ui/str/str-as-char.stderr
index 44ec079e92918..03b2309a3171a 100644
--- a/tests/ui/str/str-as-char.stderr
+++ b/tests/ui/str/str-as-char.stderr
@@ -7,7 +7,7 @@ LL |     println!('●●');
 help: if you meant to write a `str` literal, use double quotes
    |
 LL |     println!("●●");
-   |              ~~~~
+   |              ~  ~
 
 error: aborting due to 1 previous error
 

From 999a0dc300b7f95eb7d83666514c4ceae76020f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Wed, 13 Mar 2024 23:52:04 +0000
Subject: [PATCH 3/6] review comment: `str` -> string in messages

---
 compiler/rustc_infer/messages.ftl                   | 2 +-
 compiler/rustc_parse/messages.ftl                   | 4 ++--
 compiler/rustc_parse/src/lexer/mod.rs               | 2 +-
 tests/ui/inference/str-as-char.stderr               | 8 ++++----
 tests/ui/issues/issue-23589.stderr                  | 2 +-
 tests/ui/lexer/lex-bad-char-literals-2.stderr       | 2 +-
 tests/ui/lexer/lex-bad-char-literals-3.stderr       | 4 ++--
 tests/ui/lexer/lex-bad-char-literals-5.stderr       | 4 ++--
 tests/ui/lexer/lex-bad-char-literals-6.stderr       | 6 +++---
 tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr | 2 +-
 tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr | 2 +-
 tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr | 2 +-
 tests/ui/parser/issues/issue-64732.rs               | 2 +-
 tests/ui/parser/issues/issue-64732.stderr           | 2 +-
 tests/ui/parser/unicode-character-literal.fixed     | 4 ++--
 tests/ui/parser/unicode-character-literal.rs        | 4 ++--
 tests/ui/parser/unicode-character-literal.stderr    | 4 ++--
 tests/ui/str/str-as-char.stderr                     | 2 +-
 18 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl
index e44a6ae3b3f2e..521c65c600918 100644
--- a/compiler/rustc_infer/messages.ftl
+++ b/compiler/rustc_infer/messages.ftl
@@ -169,7 +169,7 @@ infer_lifetime_param_suggestion_elided = each elided lifetime in input position
 
 infer_meant_byte_literal = if you meant to write a byte literal, prefix with `b`
 infer_meant_char_literal = if you meant to write a `char` literal, use single quotes
-infer_meant_str_literal = if you meant to write a `str` literal, use double quotes
+infer_meant_str_literal = if you meant to write a string literal, use double quotes
 infer_mismatched_static_lifetime = incompatible lifetime on type
 infer_more_targeted = {$has_param_name ->
     [true] `{$param_name}`
diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl
index 888c621f02afd..7f828c0274361 100644
--- a/compiler/rustc_parse/messages.ftl
+++ b/compiler/rustc_parse/messages.ftl
@@ -570,7 +570,7 @@ parse_more_than_one_char = character literal may only contain one codepoint
     .remove_non = consider removing the non-printing characters
     .use_double_quotes = if you meant to write a {$is_byte ->
         [true] byte string
-        *[false] `str`
+        *[false] string
         } literal, use double quotes
 
 parse_multiple_skipped_lines = multiple lines skipped by escaped newline
@@ -835,7 +835,7 @@ parse_unknown_prefix = prefix `{$prefix}` is unknown
     .label = unknown prefix
     .note =  prefixed identifiers and literals are reserved since Rust 2021
     .suggestion_br = use `br` for a raw byte string
-    .suggestion_str = if you meant to write a `str` literal, use double quotes
+    .suggestion_str = if you meant to write a string literal, use double quotes
     .suggestion_whitespace = consider inserting whitespace here
 
 parse_unknown_start_of_token = unknown start of token: {$escaped}
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index 659e0b63d2ed8..5583e49ba6011 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -419,7 +419,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
                         .with_code(E0762);
                     if let Some(lt_sp) = self.last_lifetime {
                         err.multipart_suggestion(
-                            "if you meant to write a `str` literal, use double quotes",
+                            "if you meant to write a string literal, use double quotes",
                             vec![
                                 (lt_sp, "\"".to_string()),
                                 (self.mk_sp(start, start + BytePos(1)), "\"".to_string()),
diff --git a/tests/ui/inference/str-as-char.stderr b/tests/ui/inference/str-as-char.stderr
index 42302435c91bf..4ca71c5f067fd 100644
--- a/tests/ui/inference/str-as-char.stderr
+++ b/tests/ui/inference/str-as-char.stderr
@@ -4,7 +4,7 @@ error: character literal may only contain one codepoint
 LL |     let _: &str = '"""';
    |                   ^^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let _: &str = "\"\"\"";
    |                   ~~~~~~~~
@@ -15,7 +15,7 @@ error: character literal may only contain one codepoint
 LL |     let _: &str = '\"\"\"';
    |                   ^^^^^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let _: &str = "\"\"\"";
    |                   ~      ~
@@ -26,7 +26,7 @@ error: character literal may only contain one codepoint
 LL |     let _: &str = '"\"\"\\"\\"';
    |                   ^^^^^^^^^^^^^^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let _: &str = "\"\"\\"\\"\\\"";
    |                   ~~~~~~~~~~~~~~~~~~~~
@@ -39,7 +39,7 @@ LL |     let _: &str = 'a';
    |            |
    |            expected due to this
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let _: &str = "a";
    |                   ~ ~
diff --git a/tests/ui/issues/issue-23589.stderr b/tests/ui/issues/issue-23589.stderr
index bf055a7e31c59..21d383b0e8ce2 100644
--- a/tests/ui/issues/issue-23589.stderr
+++ b/tests/ui/issues/issue-23589.stderr
@@ -15,7 +15,7 @@ error[E0308]: mismatched types
 LL |     let v: Vec(&str) = vec!['1', '2'];
    |                             ^^^ expected `&str`, found `char`
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let v: Vec(&str) = vec!["1", '2'];
    |                             ~ ~
diff --git a/tests/ui/lexer/lex-bad-char-literals-2.stderr b/tests/ui/lexer/lex-bad-char-literals-2.stderr
index af64e6efe45ff..76cde00404a15 100644
--- a/tests/ui/lexer/lex-bad-char-literals-2.stderr
+++ b/tests/ui/lexer/lex-bad-char-literals-2.stderr
@@ -4,7 +4,7 @@ error: character literal may only contain one codepoint
 LL |     'nope'
    |     ^^^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     "nope"
    |     ~    ~
diff --git a/tests/ui/lexer/lex-bad-char-literals-3.stderr b/tests/ui/lexer/lex-bad-char-literals-3.stderr
index 312b42fc83174..3f339b2ef7d93 100644
--- a/tests/ui/lexer/lex-bad-char-literals-3.stderr
+++ b/tests/ui/lexer/lex-bad-char-literals-3.stderr
@@ -4,7 +4,7 @@ error: character literal may only contain one codepoint
 LL | static c: char = '●●';
    |                  ^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL | static c: char = "●●";
    |                  ~  ~
@@ -15,7 +15,7 @@ error: character literal may only contain one codepoint
 LL |     let ch: &str = '●●';
    |                    ^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let ch: &str = "●●";
    |                    ~  ~
diff --git a/tests/ui/lexer/lex-bad-char-literals-5.stderr b/tests/ui/lexer/lex-bad-char-literals-5.stderr
index 185f460b10def..8004157e87f7a 100644
--- a/tests/ui/lexer/lex-bad-char-literals-5.stderr
+++ b/tests/ui/lexer/lex-bad-char-literals-5.stderr
@@ -4,7 +4,7 @@ error: character literal may only contain one codepoint
 LL | static c: char = '\x10\x10';
    |                  ^^^^^^^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL | static c: char = "\x10\x10";
    |                  ~        ~
@@ -15,7 +15,7 @@ error: character literal may only contain one codepoint
 LL |     let ch: &str = '\x10\x10';
    |                    ^^^^^^^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let ch: &str = "\x10\x10";
    |                    ~        ~
diff --git a/tests/ui/lexer/lex-bad-char-literals-6.stderr b/tests/ui/lexer/lex-bad-char-literals-6.stderr
index f49e5a095b34a..96d409d59bb25 100644
--- a/tests/ui/lexer/lex-bad-char-literals-6.stderr
+++ b/tests/ui/lexer/lex-bad-char-literals-6.stderr
@@ -4,7 +4,7 @@ error: character literal may only contain one codepoint
 LL |     let x: &str = 'ab';
    |                   ^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let x: &str = "ab";
    |                   ~  ~
@@ -15,7 +15,7 @@ error: character literal may only contain one codepoint
 LL |     let y: char = 'cd';
    |                   ^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let y: char = "cd";
    |                   ~  ~
@@ -26,7 +26,7 @@ error: character literal may only contain one codepoint
 LL |     let z = 'ef';
    |             ^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let z = "ef";
    |             ~  ~
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr
index 675624cfa9415..57c5f82704ec7 100644
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-1.stderr
@@ -4,7 +4,7 @@ error[E0762]: unterminated character literal
 LL |     println!('1 + 1');
    |                    ^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     println!("1 + 1");
    |              ~     ~
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
index 06b38522ad2dd..f64761af64193 100644
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-2.stderr
@@ -4,7 +4,7 @@ error: character literal may only contain one codepoint
 LL |     println!(' 1 + 1');
    |              ^^^^^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     println!(" 1 + 1");
    |              ~      ~
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr
index ebfbeac02600b..262f78569839d 100644
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr
@@ -4,7 +4,7 @@ error[E0762]: unterminated character literal
 LL |     println!('hello world');
    |                          ^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     println!("hello world");
    |              ~           ~
diff --git a/tests/ui/parser/issues/issue-64732.rs b/tests/ui/parser/issues/issue-64732.rs
index 2db51ea6042aa..ff0f97ea211dc 100644
--- a/tests/ui/parser/issues/issue-64732.rs
+++ b/tests/ui/parser/issues/issue-64732.rs
@@ -5,5 +5,5 @@ fn main() {
     //~| HELP if you meant to write a byte string literal, use double quotes
     let _bar = 'hello';
     //~^ ERROR character literal may only contain one codepoint
-    //~| HELP if you meant to write a `str` literal, use double quotes
+    //~| HELP if you meant to write a string literal, use double quotes
 }
diff --git a/tests/ui/parser/issues/issue-64732.stderr b/tests/ui/parser/issues/issue-64732.stderr
index 8893fa8aae239..7ec2df6d3bf7e 100644
--- a/tests/ui/parser/issues/issue-64732.stderr
+++ b/tests/ui/parser/issues/issue-64732.stderr
@@ -15,7 +15,7 @@ error: character literal may only contain one codepoint
 LL |     let _bar = 'hello';
    |                ^^^^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let _bar = "hello";
    |                ~     ~
diff --git a/tests/ui/parser/unicode-character-literal.fixed b/tests/ui/parser/unicode-character-literal.fixed
index 9e31890151cc1..e401ecaf5da77 100644
--- a/tests/ui/parser/unicode-character-literal.fixed
+++ b/tests/ui/parser/unicode-character-literal.fixed
@@ -7,12 +7,12 @@ fn main() {
     let _spade = "♠️";
     //~^ ERROR: character literal may only contain one codepoint
     //~| NOTE: this `♠` is followed by the combining mark `\u{fe0f}`
-    //~| HELP: if you meant to write a `str` literal, use double quotes
+    //~| HELP: if you meant to write a string literal, use double quotes
 
     let _s = "ṩ̂̊";
     //~^ ERROR: character literal may only contain one codepoint
     //~| NOTE: this `s` is followed by the combining marks `\u{323}\u{307}\u{302}\u{30a}`
-    //~| HELP: if you meant to write a `str` literal, use double quotes
+    //~| HELP: if you meant to write a string literal, use double quotes
 
     let _a = 'Å';
     //~^ ERROR: character literal may only contain one codepoint
diff --git a/tests/ui/parser/unicode-character-literal.rs b/tests/ui/parser/unicode-character-literal.rs
index d886e5b26a56b..428e4e1ac5a08 100644
--- a/tests/ui/parser/unicode-character-literal.rs
+++ b/tests/ui/parser/unicode-character-literal.rs
@@ -7,12 +7,12 @@ fn main() {
     let _spade = '♠️';
     //~^ ERROR: character literal may only contain one codepoint
     //~| NOTE: this `♠` is followed by the combining mark `\u{fe0f}`
-    //~| HELP: if you meant to write a `str` literal, use double quotes
+    //~| HELP: if you meant to write a string literal, use double quotes
 
     let _s = 'ṩ̂̊';
     //~^ ERROR: character literal may only contain one codepoint
     //~| NOTE: this `s` is followed by the combining marks `\u{323}\u{307}\u{302}\u{30a}`
-    //~| HELP: if you meant to write a `str` literal, use double quotes
+    //~| HELP: if you meant to write a string literal, use double quotes
 
     let _a = 'Å';
     //~^ ERROR: character literal may only contain one codepoint
diff --git a/tests/ui/parser/unicode-character-literal.stderr b/tests/ui/parser/unicode-character-literal.stderr
index 1104eaeb8d478..726cde2b413e2 100644
--- a/tests/ui/parser/unicode-character-literal.stderr
+++ b/tests/ui/parser/unicode-character-literal.stderr
@@ -9,7 +9,7 @@ note: this `♠` is followed by the combining mark `\u{fe0f}`
    |
 LL |     let _spade = '♠️';
    |                   ^
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let _spade = "♠️";
    |                  ~ ~
@@ -25,7 +25,7 @@ note: this `s` is followed by the combining marks `\u{323}\u{307}\u{302}\u{30a}`
    |
 LL |     let _s = 'ṩ̂̊';
    |               ^
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     let _s = "ṩ̂̊";
    |              ~ ~
diff --git a/tests/ui/str/str-as-char.stderr b/tests/ui/str/str-as-char.stderr
index 03b2309a3171a..0638d371c173b 100644
--- a/tests/ui/str/str-as-char.stderr
+++ b/tests/ui/str/str-as-char.stderr
@@ -4,7 +4,7 @@ error: character literal may only contain one codepoint
 LL |     println!('●●');
    |              ^^^^
    |
-help: if you meant to write a `str` literal, use double quotes
+help: if you meant to write a string literal, use double quotes
    |
 LL |     println!("●●");
    |              ~  ~

From 6f388ef1fb3964fb83bae5c277f9c83bbde4c76b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Thu, 14 Mar 2024 00:22:52 +0000
Subject: [PATCH 4/6] Extend test to trigger on 2015, 2018 and 2021 editions

---
 .../lexer/lex-bad-str-literal-as-char-3.fixed |  4 ---
 .../ui/lexer/lex-bad-str-literal-as-char-3.rs |  8 ++++--
 ...bad-str-literal-as-char-3.rust2015.stderr} |  4 +--
 ...-bad-str-literal-as-char-3.rust2018.stderr | 14 ++++++++++
 ...-bad-str-literal-as-char-3.rust2021.stderr | 26 +++++++++++++++++++
 5 files changed, 48 insertions(+), 8 deletions(-)
 delete mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-3.fixed
 rename tests/ui/lexer/{lex-bad-str-literal-as-char-3.stderr => lex-bad-str-literal-as-char-3.rust2015.stderr} (79%)
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2018.stderr
 create mode 100644 tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2021.stderr

diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.fixed b/tests/ui/lexer/lex-bad-str-literal-as-char-3.fixed
deleted file mode 100644
index 3fbe0ea1dabef..0000000000000
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-3.fixed
+++ /dev/null
@@ -1,4 +0,0 @@
-//@ run-rustfix
-fn main() {
-    println!("hello world"); //~ ERROR unterminated character literal
-}
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
index 289f3f1d6570f..2c0eda97853b0 100644
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
@@ -1,4 +1,8 @@
-//@ run-rustfix
+//@ revisions: rust2015 rust2018 rust2021
+//@[rust2018] edition:2018
+//@[rust2021] edition:2021
 fn main() {
-    println!('hello world'); //~ ERROR unterminated character literal
+    println!('hello world');
+    //[rust2015,rust2018,rust2021]~^ ERROR unterminated character literal
+    //[rust2021]~^^ ERROR prefix `world` is unknown
 }
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2015.stderr
similarity index 79%
rename from tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr
rename to tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2015.stderr
index 262f78569839d..06f127426679f 100644
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-3.stderr
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2015.stderr
@@ -1,8 +1,8 @@
 error[E0762]: unterminated character literal
-  --> $DIR/lex-bad-str-literal-as-char-3.rs:3:26
+  --> $DIR/lex-bad-str-literal-as-char-3.rs:5:26
    |
 LL |     println!('hello world');
-   |                          ^^^^
+   |                          ^^^
    |
 help: if you meant to write a string literal, use double quotes
    |
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2018.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2018.stderr
new file mode 100644
index 0000000000000..06f127426679f
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2018.stderr
@@ -0,0 +1,14 @@
+error[E0762]: unterminated character literal
+  --> $DIR/lex-bad-str-literal-as-char-3.rs:5:26
+   |
+LL |     println!('hello world');
+   |                          ^^^
+   |
+help: if you meant to write a string literal, use double quotes
+   |
+LL |     println!("hello world");
+   |              ~           ~
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0762`.
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2021.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2021.stderr
new file mode 100644
index 0000000000000..4170560cfcb04
--- /dev/null
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2021.stderr
@@ -0,0 +1,26 @@
+error: prefix `world` is unknown
+  --> $DIR/lex-bad-str-literal-as-char-3.rs:5:21
+   |
+LL |     println!('hello world');
+   |                     ^^^^^ unknown prefix
+   |
+   = note: prefixed identifiers and literals are reserved since Rust 2021
+help: if you meant to write a string literal, use double quotes
+   |
+LL |     println!("hello world");
+   |              ~           ~
+
+error[E0762]: unterminated character literal
+  --> $DIR/lex-bad-str-literal-as-char-3.rs:5:26
+   |
+LL |     println!('hello world');
+   |                          ^^^
+   |
+help: if you meant to write a string literal, use double quotes
+   |
+LL |     println!("hello world");
+   |              ~           ~
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0762`.

From ea1883d7b2207d1a0f08046f11ca493803bc8a55 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Thu, 14 Mar 2024 00:40:25 +0000
Subject: [PATCH 5/6] Silence redundant error on char literal that was meant to
 be a string in 2021 edition

---
 compiler/rustc_lexer/src/cursor.rs                 |  9 +++++++++
 compiler/rustc_parse/src/lexer/mod.rs              | 11 ++++++++++-
 tests/ui/lexer/lex-bad-str-literal-as-char-3.rs    |  1 -
 .../lex-bad-str-literal-as-char-3.rust2021.stderr  | 14 +-------------
 4 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/compiler/rustc_lexer/src/cursor.rs b/compiler/rustc_lexer/src/cursor.rs
index 8d8cc9ecc3e9f..d173c3ac0327b 100644
--- a/compiler/rustc_lexer/src/cursor.rs
+++ b/compiler/rustc_lexer/src/cursor.rs
@@ -59,6 +59,15 @@ impl<'a> Cursor<'a> {
         iter.next().unwrap_or(EOF_CHAR)
     }
 
+    /// Peeks the third symbol from the input stream without consuming it.
+    pub fn third(&self) -> char {
+        // `.next()` optimizes better than `.nth(1)`
+        let mut iter = self.chars.clone();
+        iter.next();
+        iter.next();
+        iter.next().unwrap_or(EOF_CHAR)
+    }
+
     /// Checks if there is nothing more to consume.
     pub(crate) fn is_eof(&self) -> bool {
         self.chars.as_str().is_empty()
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index 5583e49ba6011..63b2b47630b29 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -698,13 +698,17 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
         let expn_data = prefix_span.ctxt().outer_expn_data();
 
         if expn_data.edition >= Edition::Edition2021 {
+            let mut silence = false;
             // In Rust 2021, this is a hard error.
             let sugg = if prefix == "rb" {
                 Some(errors::UnknownPrefixSugg::UseBr(prefix_span))
             } else if expn_data.is_root() {
                 if self.cursor.first() == '\''
                     && let Some(start) = self.last_lifetime
+                    && self.cursor.third() != '\''
                 {
+                    // An "unclosed `char`" error will be emitted already, silence redundant error.
+                    silence = true;
                     Some(errors::UnknownPrefixSugg::MeantStr {
                         start,
                         end: self.mk_sp(self.pos, self.pos + BytePos(1)),
@@ -715,7 +719,12 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
             } else {
                 None
             };
-            self.dcx().emit_err(errors::UnknownPrefix { span: prefix_span, prefix, sugg });
+            let err = errors::UnknownPrefix { span: prefix_span, prefix, sugg };
+            if silence {
+                self.dcx().create_err(err).delay_as_bug();
+            } else {
+                self.dcx().emit_err(err);
+            }
         } else {
             // Before Rust 2021, only emit a lint for migration.
             self.psess.buffer_lint_with_diagnostic(
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
index 2c0eda97853b0..0ae227da5f1e6 100644
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rs
@@ -4,5 +4,4 @@
 fn main() {
     println!('hello world');
     //[rust2015,rust2018,rust2021]~^ ERROR unterminated character literal
-    //[rust2021]~^^ ERROR prefix `world` is unknown
 }
diff --git a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2021.stderr b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2021.stderr
index 4170560cfcb04..06f127426679f 100644
--- a/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2021.stderr
+++ b/tests/ui/lexer/lex-bad-str-literal-as-char-3.rust2021.stderr
@@ -1,15 +1,3 @@
-error: prefix `world` is unknown
-  --> $DIR/lex-bad-str-literal-as-char-3.rs:5:21
-   |
-LL |     println!('hello world');
-   |                     ^^^^^ unknown prefix
-   |
-   = note: prefixed identifiers and literals are reserved since Rust 2021
-help: if you meant to write a string literal, use double quotes
-   |
-LL |     println!("hello world");
-   |              ~           ~
-
 error[E0762]: unterminated character literal
   --> $DIR/lex-bad-str-literal-as-char-3.rs:5:26
    |
@@ -21,6 +9,6 @@ help: if you meant to write a string literal, use double quotes
 LL |     println!("hello world");
    |              ~           ~
 
-error: aborting due to 2 previous errors
+error: aborting due to 1 previous error
 
 For more information about this error, try `rustc --explain E0762`.

From f4d30b156b2958ba01b59a1751e16b5f6e157fcb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Sun, 17 Mar 2024 23:46:39 +0000
Subject: [PATCH 6/6] fix rustdoc test

---
 compiler/rustc_parse/src/lexer/unescape_error_reporting.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
index 1ac6b6fe11566..fa242a32a1814 100644
--- a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
+++ b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
@@ -95,7 +95,7 @@ pub(crate) fn emit_unescape_error(
                     }
                     escaped.push(c);
                 }
-                if escaped.len() != lit.len() {
+                if escaped.len() != lit.len() || full_lit_span.is_empty() {
                     let sugg = format!("{prefix}\"{escaped}\"");
                     MoreThanOneCharSugg::QuotesFull {
                         span: full_lit_span,