about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-10-09 23:03:47 +0200
committerGitHub <noreply@github.com>2024-10-09 23:03:47 +0200
commitb41e939cb566e21db21144184752e223bb16509d (patch)
tree52d2f45911d293be7bf327fa1157c3d68a1ac373
parenta1eceec00b2684f947481696ae2322e20d59db60 (diff)
parent321a5db7d4026cf703d6deebc413c855ba133142 (diff)
downloadrust-b41e939cb566e21db21144184752e223bb16509d.tar.gz
rust-b41e939cb566e21db21144184752e223bb16509d.zip
Rollup merge of #123951 - pitaj:reserve-guarded-strings, r=traviscross
Reserve guarded string literals (RFC 3593)

Implementation for RFC 3593, including:
- lexer / parser changes
- diagnostics
- migration lint
- tests

We reserve `#"`, `##"`, `###"`, `####`, and any other string of four or more repeated `#`. This avoids infinite lookahead in the lexer, though we still use infinite lookahead in the parser to provide better forward compatibility diagnostics.

This PR does not implement any special lexing of the string internals:
- strings preceded by one or more `#` are denied
- regardless of the number of trailing `#`
- string contents are lexed as if it was just a bare `"string"`

Tracking issue: #123735
RFC: rust-lang/rfcs#3593
-rw-r--r--compiler/rustc_lexer/src/lib.rs93
-rw-r--r--compiler/rustc_lint/messages.ftl3
-rw-r--r--compiler/rustc_lint/src/context/diagnostics.rs3
-rw-r--r--compiler/rustc_lint/src/lints.rs7
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs41
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs2
-rw-r--r--compiler/rustc_parse/messages.ftl4
-rw-r--r--compiler/rustc_parse/src/errors.rs18
-rw-r--r--compiler/rustc_parse/src/lexer/mod.rs84
-rw-r--r--src/librustdoc/html/highlight.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/lexed_str.rs6
-rw-r--r--tests/ui/rust-2024/auxiliary/reserved-guarded-strings-macro-2021.rs20
-rw-r--r--tests/ui/rust-2024/auxiliary/reserved-guarded-strings-macro-2024.rs21
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings-lexing.rs80
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings-lexing.stderr271
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings-migration.fixed99
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings-migration.rs99
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings-migration.stderr293
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings-via-macro-2.rs18
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings-via-macro-2.stderr20
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings-via-macro.rs12
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings.rs74
-rw-r--r--tests/ui/rust-2024/reserved-guarded-strings.stderr254
23 files changed, 1514 insertions, 9 deletions
diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs
index 60aab668cba..b0ab50dd773 100644
--- a/compiler/rustc_lexer/src/lib.rs
+++ b/compiler/rustc_lexer/src/lib.rs
@@ -104,6 +104,12 @@ pub enum TokenKind {
     /// for emoji identifier recovery, as those are not meant to be ever accepted.
     InvalidPrefix,
 
+    /// Guarded string literal prefix: `#"` or `##`.
+    ///
+    /// Used for reserving "guarded strings" (RFC 3598) in edition 2024.
+    /// Split into the component tokens on older editions.
+    GuardedStrPrefix,
+
     /// Examples: `12u8`, `1.0e-40`, `b"123"`. Note that `_` is an invalid
     /// suffix, but may be present here on string and float literals. Users of
     /// this type will need to check for and reject that case.
@@ -191,30 +197,41 @@ pub enum DocStyle {
 /// `rustc_ast::ast::LitKind`).
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub enum LiteralKind {
-    /// "12_u8", "0o100", "0b120i99", "1f32".
+    /// `12_u8`, `0o100`, `0b120i99`, `1f32`.
     Int { base: Base, empty_int: bool },
-    /// "12.34f32", "1e3", but not "1f32".
+    /// `12.34f32`, `1e3`, but not `1f32`.
     Float { base: Base, empty_exponent: bool },
-    /// "'a'", "'\\'", "'''", "';"
+    /// `'a'`, `'\\'`, `'''`, `';`
     Char { terminated: bool },
-    /// "b'a'", "b'\\'", "b'''", "b';"
+    /// `b'a'`, `b'\\'`, `b'''`, `b';`
     Byte { terminated: bool },
-    /// ""abc"", ""abc"
+    /// `"abc"`, `"abc`
     Str { terminated: bool },
-    /// "b"abc"", "b"abc"
+    /// `b"abc"`, `b"abc`
     ByteStr { terminated: bool },
     /// `c"abc"`, `c"abc`
     CStr { terminated: bool },
-    /// "r"abc"", "r#"abc"#", "r####"ab"###"c"####", "r#"a". `None` indicates
+    /// `r"abc"`, `r#"abc"#`, `r####"ab"###"c"####`, `r#"a`. `None` indicates
     /// an invalid literal.
     RawStr { n_hashes: Option<u8> },
-    /// "br"abc"", "br#"abc"#", "br####"ab"###"c"####", "br#"a". `None`
+    /// `br"abc"`, `br#"abc"#`, `br####"ab"###"c"####`, `br#"a`. `None`
     /// indicates an invalid literal.
     RawByteStr { n_hashes: Option<u8> },
     /// `cr"abc"`, "cr#"abc"#", `cr#"a`. `None` indicates an invalid literal.
     RawCStr { n_hashes: Option<u8> },
 }
 
+/// `#"abc"#`, `##"a"` (fewer closing), or even `#"a` (unterminated).
+///
+/// Can capture fewer closing hashes than starting hashes,
+/// for more efficient lexing and better backwards diagnostics.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct GuardedStr {
+    pub n_hashes: u32,
+    pub terminated: bool,
+    pub token_len: u32,
+}
+
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub enum RawStrError {
     /// Non `#` characters exist between `r` and `"`, e.g. `r##~"abcde"##`
@@ -403,6 +420,12 @@ impl Cursor<'_> {
                 TokenKind::Literal { kind: literal_kind, suffix_start }
             }
 
+            // Guarded string literal prefix: `#"` or `##`
+            '#' if matches!(self.first(), '"' | '#') => {
+                self.bump();
+                TokenKind::GuardedStrPrefix
+            }
+
             // One-symbol tokens.
             ';' => Semi,
             ',' => Comma,
@@ -780,6 +803,60 @@ impl Cursor<'_> {
         false
     }
 
+    /// Attempt to lex for a guarded string literal.
+    ///
+    /// Used by `rustc_parse::lexer` to lex for guarded strings
+    /// conditionally based on edition.
+    ///
+    /// Note: this will not reset the `Cursor` when a
+    /// guarded string is not found. It is the caller's
+    /// responsibility to do so.
+    pub fn guarded_double_quoted_string(&mut self) -> Option<GuardedStr> {
+        debug_assert!(self.prev() != '#');
+
+        let mut n_start_hashes: u32 = 0;
+        while self.first() == '#' {
+            n_start_hashes += 1;
+            self.bump();
+        }
+
+        if self.first() != '"' {
+            return None;
+        }
+        self.bump();
+        debug_assert!(self.prev() == '"');
+
+        // Lex the string itself as a normal string literal
+        // so we can recover that for older editions later.
+        let terminated = self.double_quoted_string();
+        if !terminated {
+            let token_len = self.pos_within_token();
+            self.reset_pos_within_token();
+
+            return Some(GuardedStr { n_hashes: n_start_hashes, terminated: false, token_len });
+        }
+
+        // Consume closing '#' symbols.
+        // Note that this will not consume extra trailing `#` characters:
+        // `###"abcde"####` is lexed as a `GuardedStr { n_end_hashes: 3, .. }`
+        // followed by a `#` token.
+        let mut n_end_hashes = 0;
+        while self.first() == '#' && n_end_hashes < n_start_hashes {
+            n_end_hashes += 1;
+            self.bump();
+        }
+
+        // Reserved syntax, always an error, so it doesn't matter if
+        // `n_start_hashes != n_end_hashes`.
+
+        self.eat_literal_suffix();
+
+        let token_len = self.pos_within_token();
+        self.reset_pos_within_token();
+
+        Some(GuardedStr { n_hashes: n_start_hashes, terminated: true, token_len })
+    }
+
     /// Eats the double-quoted string and returns `n_hashes` and an error if encountered.
     fn raw_double_quoted_string(&mut self, prefix_len: u32) -> Result<u8, RawStrError> {
         // Wrap the actual function to handle the error with too many hashes.
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index d3799594871..e733e92c7cb 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -740,6 +740,9 @@ lint_reserved_prefix = prefix `{$prefix}` is unknown
     .label = unknown prefix
     .suggestion = insert whitespace here to avoid this being parsed as a prefix in Rust 2021
 
+lint_reserved_string = will be parsed as a guarded string in Rust 2024
+    .suggestion = insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+
 lint_shadowed_into_iter =
     this method call resolves to `<&{$target} as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<{$target} as IntoIterator>::into_iter` in Rust {$edition}
     .use_iter_suggestion = use `.iter()` instead of `.into_iter()` to avoid ambiguity
diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs
index b5ab56912cb..565c3c04252 100644
--- a/compiler/rustc_lint/src/context/diagnostics.rs
+++ b/compiler/rustc_lint/src/context/diagnostics.rs
@@ -176,6 +176,9 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: &
             lints::RawPrefix { label: label_span, suggestion: label_span.shrink_to_hi() }
                 .decorate_lint(diag);
         }
+        BuiltinLintDiag::ReservedString(suggestion) => {
+            lints::ReservedString { suggestion }.decorate_lint(diag);
+        }
         BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => {
             lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag);
         }
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index a861894a444..87afeca0b28 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -3053,3 +3053,10 @@ pub(crate) enum MutRefSugg {
 #[derive(LintDiagnostic)]
 #[diag(lint_unqualified_local_imports)]
 pub(crate) struct UnqualifiedLocalImportsDiag {}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_reserved_string)]
+pub(crate) struct ReservedString {
+    #[suggestion(code = " ", applicability = "machine-applicable")]
+    pub suggestion: Span,
+}
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index a123059df8f..827791c54be 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -92,6 +92,7 @@ declare_lint_pass! {
         RUST_2021_INCOMPATIBLE_OR_PATTERNS,
         RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
         RUST_2021_PRELUDE_COLLISIONS,
+        RUST_2024_GUARDED_STRING_INCOMPATIBLE_SYNTAX,
         RUST_2024_INCOMPATIBLE_PAT,
         RUST_2024_PRELUDE_COLLISIONS,
         SELF_CONSTRUCTOR_FROM_OUTER_ITEM,
@@ -4996,3 +4997,43 @@ declare_lint! {
     Warn,
     "detects pointer to integer transmutes in const functions and associated constants",
 }
+
+declare_lint! {
+    /// The `rust_2024_guarded_string_incompatible_syntax` lint detects `#` tokens
+    /// that will be parsed as part of a guarded string literal in Rust 2024.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2021,compile_fail
+    /// #![deny(rust_2024_guarded_string_incompatible_syntax)]
+    ///
+    /// macro_rules! m {
+    ///     (# $x:expr #) => ();
+    ///     (# $x:expr) => ();
+    /// }
+    ///
+    /// m!(#"hey"#);
+    /// m!(#"hello");
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Prior to Rust 2024, `#"hey"#` is three tokens: the first `#`
+    /// followed by the string literal `"hey"` then the final `#`.
+    /// In Rust 2024, the whole sequence is considered a single token.
+    ///
+    /// This lint suggests to add whitespace between the leading `#`
+    /// and the string to keep them separated in Rust 2024.
+    // Allow this lint -- rustdoc doesn't yet support threading edition into this lint's parser.
+    #[allow(rustdoc::invalid_rust_codeblocks)]
+    pub RUST_2024_GUARDED_STRING_INCOMPATIBLE_SYNTAX,
+    Allow,
+    "will be parsed as a guarded string in Rust 2024",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024),
+        reference: "issue #123735 <https://github.com/rust-lang/rust/issues/123735>",
+    };
+    crate_level_only
+}
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 386918a5c41..c01fa5c54d6 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -614,6 +614,8 @@ pub enum BuiltinLintDiag {
     ReservedPrefix(Span, String),
     /// `'r#` in edition < 2021.
     RawPrefix(Span),
+    /// `##` or `#"` is edition < 2024.
+    ReservedString(Span),
     TrailingMacro(bool, Ident),
     BreakWithLabelAndLoop(Span),
     UnicodeTextFlow(Span, String),
diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl
index 948199fd55c..ba5e2ddf4fc 100644
--- a/compiler/rustc_parse/messages.ftl
+++ b/compiler/rustc_parse/messages.ftl
@@ -706,6 +706,10 @@ parse_require_colon_after_labeled_expression = labeled expression must be follow
     .label = the label
     .suggestion = add `:` after the label
 
+parse_reserved_string = invalid string literal
+    .note = unprefixed guarded string literals are reserved for future use since Rust 2024
+    .suggestion_whitespace = consider inserting whitespace here
+
 parse_return_types_use_thin_arrow = return types are denoted using `->`
     .suggestion = use `->` instead
 
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index dade3912751..124975f67f1 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -2111,6 +2111,24 @@ pub(crate) enum UnknownPrefixSugg {
 }
 
 #[derive(Diagnostic)]
+#[diag(parse_reserved_string)]
+#[note]
+pub(crate) struct ReservedString {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub sugg: Option<GuardedStringSugg>,
+}
+#[derive(Subdiagnostic)]
+#[suggestion(
+    parse_suggestion_whitespace,
+    code = " ",
+    applicability = "maybe-incorrect",
+    style = "verbose"
+)]
+pub(crate) struct GuardedStringSugg(#[primary_span] pub Span);
+
+#[derive(Diagnostic)]
 #[diag(parse_too_many_hashes)]
 pub(crate) struct TooManyHashes {
     #[primary_span]
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index 3e46fc93fa4..d627ef3d2cb 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -10,7 +10,8 @@ use rustc_lexer::unescape::{self, EscapeError, Mode};
 use rustc_lexer::{Base, Cursor, DocStyle, LiteralKind, RawStrError};
 use rustc_session::lint::BuiltinLintDiag;
 use rustc_session::lint::builtin::{
-    RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX, TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
+    RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX, RUST_2024_GUARDED_STRING_INCOMPATIBLE_SYNTAX,
+    TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
 };
 use rustc_session::parse::ParseSess;
 use rustc_span::symbol::Symbol;
@@ -251,6 +252,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
                     let prefix_span = self.mk_sp(start, lit_start);
                     return (Token::new(self.ident(start), prefix_span), preceded_by_whitespace);
                 }
+                rustc_lexer::TokenKind::GuardedStrPrefix => self.maybe_report_guarded_str(start, str_before),
                 rustc_lexer::TokenKind::Literal { kind, suffix_start } => {
                     let suffix_start = start + BytePos(suffix_start);
                     let (kind, symbol) = self.cook_lexer_literal(start, suffix_start, kind);
@@ -781,6 +783,86 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
         }
     }
 
+    /// Detect guarded string literal syntax
+    ///
+    /// RFC 3598 reserved this syntax for future use. As of Rust 2024,
+    /// using this syntax produces an error. In earlier editions, however, it
+    /// only results in an (allowed by default) lint, and is treated as
+    /// separate tokens.
+    fn maybe_report_guarded_str(&mut self, start: BytePos, str_before: &'src str) -> TokenKind {
+        let span = self.mk_sp(start, self.pos);
+        let edition2024 = span.edition().at_least_rust_2024();
+
+        let space_pos = start + BytePos(1);
+        let space_span = self.mk_sp(space_pos, space_pos);
+
+        let mut cursor = Cursor::new(str_before);
+
+        let (span, unterminated) = match cursor.guarded_double_quoted_string() {
+            Some(rustc_lexer::GuardedStr { n_hashes, terminated, token_len }) => {
+                let end = start + BytePos(token_len);
+                let span = self.mk_sp(start, end);
+                let str_start = start + BytePos(n_hashes);
+
+                if edition2024 {
+                    self.cursor = cursor;
+                    self.pos = end;
+                }
+
+                let unterminated = if terminated { None } else { Some(str_start) };
+
+                (span, unterminated)
+            }
+            _ => {
+                // We should only get here in the `##+` case.
+                debug_assert_eq!(self.str_from_to(start, start + BytePos(2)), "##");
+
+                (span, None)
+            }
+        };
+        if edition2024 {
+            if let Some(str_start) = unterminated {
+                // Only a fatal error if string is unterminated.
+                self.dcx()
+                    .struct_span_fatal(
+                        self.mk_sp(str_start, self.pos),
+                        "unterminated double quote string",
+                    )
+                    .with_code(E0765)
+                    .emit()
+            }
+
+            let sugg = if span.from_expansion() {
+                None
+            } else {
+                Some(errors::GuardedStringSugg(space_span))
+            };
+
+            // In Edition 2024 and later, emit a hard error.
+            let err = self.dcx().emit_err(errors::ReservedString { span, sugg });
+
+            token::Literal(token::Lit {
+                kind: token::Err(err),
+                symbol: self.symbol_from_to(start, self.pos),
+                suffix: None,
+            })
+        } else {
+            // Before Rust 2024, only emit a lint for migration.
+            self.psess.buffer_lint(
+                RUST_2024_GUARDED_STRING_INCOMPATIBLE_SYNTAX,
+                span,
+                ast::CRATE_NODE_ID,
+                BuiltinLintDiag::ReservedString(space_span),
+            );
+
+            // For backwards compatibility, roll back to after just the first `#`
+            // and return the `Pound` token.
+            self.pos = start + BytePos(1);
+            self.cursor = Cursor::new(&str_before[1..]);
+            token::Pound
+        }
+    }
+
     fn report_too_many_hashes(&self, start: BytePos, num: u32) -> ! {
         self.dcx().emit_fatal(errors::TooManyHashes { span: self.mk_sp(start, self.pos), num });
     }
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index e7ddd4b73b4..b68b7295096 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -845,6 +845,7 @@ impl<'src> Classifier<'src> {
                 // Number literals.
                 LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number,
             },
+            TokenKind::GuardedStrPrefix => return no_highlight(sink),
             TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
                 self.in_macro = true;
                 sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) });
diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs
index dceac815e0b..7ea23b4f752 100644
--- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs
@@ -187,6 +187,12 @@ impl<'a> Converter<'a> {
                 }
 
                 rustc_lexer::TokenKind::RawIdent => IDENT,
+
+                rustc_lexer::TokenKind::GuardedStrPrefix => {
+                    err = "Invalid string literal (reserved syntax)";
+                    ERROR
+                },
+
                 rustc_lexer::TokenKind::Literal { kind, .. } => {
                     self.extend_literal(token_text.len(), kind);
                     return;
diff --git a/tests/ui/rust-2024/auxiliary/reserved-guarded-strings-macro-2021.rs b/tests/ui/rust-2024/auxiliary/reserved-guarded-strings-macro-2021.rs
new file mode 100644
index 00000000000..81080fcdce3
--- /dev/null
+++ b/tests/ui/rust-2024/auxiliary/reserved-guarded-strings-macro-2021.rs
@@ -0,0 +1,20 @@
+//@ force-host
+//@ edition:2021
+//@ no-prefer-dynamic
+
+#![crate_type = "proc-macro"]
+
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use std::str::FromStr;
+
+#[proc_macro]
+pub fn number_of_tokens_in_a_guarded_string_literal(_: TokenStream) -> TokenStream {
+    TokenStream::from_str("#\"abc\"#").unwrap().into_iter().count().to_string().parse().unwrap()
+}
+
+#[proc_macro]
+pub fn number_of_tokens_in_a_guarded_unterminated_string_literal(_: TokenStream) -> TokenStream {
+    TokenStream::from_str("#\"abc\"").unwrap().into_iter().count().to_string().parse().unwrap()
+}
diff --git a/tests/ui/rust-2024/auxiliary/reserved-guarded-strings-macro-2024.rs b/tests/ui/rust-2024/auxiliary/reserved-guarded-strings-macro-2024.rs
new file mode 100644
index 00000000000..2c3dc30f0ae
--- /dev/null
+++ b/tests/ui/rust-2024/auxiliary/reserved-guarded-strings-macro-2024.rs
@@ -0,0 +1,21 @@
+//@ force-host
+//@ compile-flags: -Zunstable-options
+//@ edition:2024
+//@ no-prefer-dynamic
+
+#![crate_type = "proc-macro"]
+
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use std::str::FromStr;
+
+#[proc_macro]
+pub fn number_of_tokens_in_a_guarded_string_literal(_: TokenStream) -> TokenStream {
+    TokenStream::from_str("#\"abc\"#").unwrap().into_iter().count().to_string().parse().unwrap()
+}
+
+#[proc_macro]
+pub fn number_of_tokens_in_a_guarded_unterminated_string_literal(_: TokenStream) -> TokenStream {
+    TokenStream::from_str("#\"abc\"").unwrap().into_iter().count().to_string().parse().unwrap()
+}
diff --git a/tests/ui/rust-2024/reserved-guarded-strings-lexing.rs b/tests/ui/rust-2024/reserved-guarded-strings-lexing.rs
new file mode 100644
index 00000000000..83e0dcbb4be
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings-lexing.rs
@@ -0,0 +1,80 @@
+//@ edition:2021
+// ignore-tidy-linelength
+
+#![warn(rust_2024_guarded_string_incompatible_syntax)]
+
+macro_rules! demo2 {
+    ( $a:tt $b:tt ) => { println!("two tokens") };
+}
+
+macro_rules! demo3 {
+    ( $a:tt $b:tt $c:tt ) => { println!("three tokens") };
+}
+
+macro_rules! demo4 {
+    ( $a:tt $b:tt $c:tt $d:tt ) => { println!("four tokens") };
+}
+
+macro_rules! demo5 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt ) => { println!("five tokens") };
+}
+
+macro_rules! demo7 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt $f:tt $g:tt ) => { println!("seven tokens") };
+}
+
+
+fn main() {
+    demo3!(## "foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!(### "foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!(## "foo"#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo7!(### "foo"###);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+
+    demo5!(###"foo"#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo5!(#"foo"###);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!("foo"###);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+
+    // Non-ascii identifiers
+    demo2!(Ñ"foo");
+    //~^ ERROR prefix `Ñ` is unknown
+    demo4!(Ñ#""#);
+    //~^ ERROR prefix `Ñ` is unknown
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(🙃#"");
+    //~^ ERROR identifiers cannot contain emoji
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+}
diff --git a/tests/ui/rust-2024/reserved-guarded-strings-lexing.stderr b/tests/ui/rust-2024/reserved-guarded-strings-lexing.stderr
new file mode 100644
index 00000000000..e2e1ac42f05
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings-lexing.stderr
@@ -0,0 +1,271 @@
+error: prefix `Ñ` is unknown
+  --> $DIR/reserved-guarded-strings-lexing.rs:70:12
+   |
+LL |     demo2!(Ñ"foo");
+   |            ^ unknown prefix
+   |
+   = note: prefixed identifiers and literals are reserved since Rust 2021
+help: consider inserting whitespace here
+   |
+LL |     demo2!(Ñ "foo");
+   |             +
+
+error: prefix `Ñ` is unknown
+  --> $DIR/reserved-guarded-strings-lexing.rs:72:12
+   |
+LL |     demo4!(Ñ#""#);
+   |            ^ unknown prefix
+   |
+   = note: prefixed identifiers and literals are reserved since Rust 2021
+help: consider inserting whitespace here
+   |
+LL |     demo4!(Ñ #""#);
+   |             +
+
+error: identifiers cannot contain emoji: `🙃`
+  --> $DIR/reserved-guarded-strings-lexing.rs:76:12
+   |
+LL |     demo3!(🙃#"");
+   |            ^^
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:28:12
+   |
+LL |     demo3!(## "foo");
+   |            ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+note: the lint level is defined here
+  --> $DIR/reserved-guarded-strings-lexing.rs:4:9
+   |
+LL | #![warn(rust_2024_guarded_string_incompatible_syntax)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(# # "foo");
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:31:12
+   |
+LL |     demo4!(### "foo");
+   |            ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(# ## "foo");
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:31:13
+   |
+LL |     demo4!(### "foo");
+   |             ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(## # "foo");
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:36:12
+   |
+LL |     demo4!(## "foo"#);
+   |            ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(# # "foo"#);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:39:12
+   |
+LL |     demo7!(### "foo"###);
+   |            ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo7!(# ## "foo"###);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:39:13
+   |
+LL |     demo7!(### "foo"###);
+   |             ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo7!(## # "foo"###);
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:39:21
+   |
+LL |     demo7!(### "foo"###);
+   |                     ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo7!(### "foo"# ##);
+   |                      +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:39:22
+   |
+LL |     demo7!(### "foo"###);
+   |                      ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo7!(### "foo"## #);
+   |                       +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:49:12
+   |
+LL |     demo5!(###"foo"#);
+   |            ^^^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(# ##"foo"#);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:49:13
+   |
+LL |     demo5!(###"foo"#);
+   |             ^^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(## #"foo"#);
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:49:14
+   |
+LL |     demo5!(###"foo"#);
+   |              ^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(### "foo"#);
+   |               +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:56:12
+   |
+LL |     demo5!(#"foo"###);
+   |            ^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(# "foo"###);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:56:18
+   |
+LL |     demo5!(#"foo"###);
+   |                  ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(#"foo"# ##);
+   |                   +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:56:19
+   |
+LL |     demo5!(#"foo"###);
+   |                   ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(#"foo"## #);
+   |                    +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:63:17
+   |
+LL |     demo4!("foo"###);
+   |                 ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!("foo"# ##);
+   |                  +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:63:18
+   |
+LL |     demo4!("foo"###);
+   |                  ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!("foo"## #);
+   |                   +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:72:13
+   |
+LL |     demo4!(Ñ#""#);
+   |             ^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(Ñ# ""#);
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-lexing.rs:76:13
+   |
+LL |     demo3!(🙃#"");
+   |              ^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(🙃# "");
+   |               +
+
+error: aborting due to 3 previous errors; 18 warnings emitted
+
diff --git a/tests/ui/rust-2024/reserved-guarded-strings-migration.fixed b/tests/ui/rust-2024/reserved-guarded-strings-migration.fixed
new file mode 100644
index 00000000000..d92df7b5375
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings-migration.fixed
@@ -0,0 +1,99 @@
+//@ check-pass
+//@ run-rustfix
+//@ edition:2021
+
+#![warn(rust_2024_guarded_string_incompatible_syntax)]
+
+macro_rules! demo1 {
+    ( $a:tt ) => { println!("one tokens") };
+}
+
+macro_rules! demo2 {
+    ( $a:tt $b:tt ) => { println!("two tokens") };
+}
+
+macro_rules! demo3 {
+    ( $a:tt $b:tt $c:tt ) => { println!("three tokens") };
+}
+
+macro_rules! demo4 {
+    ( $a:tt $b:tt $c:tt $d:tt ) => { println!("four tokens") };
+}
+
+macro_rules! demo5 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt ) => { println!("five tokens") };
+}
+
+macro_rules! demo6 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt $f:tt ) => { println!("six tokens") };
+}
+
+
+fn main() {
+    demo1!("");
+    demo2!(# "");
+    demo3!(# ""#);
+    demo2!(# "foo");
+    demo3!(# "foo"#);
+    demo2!("foo"#);
+
+    demo3!(# # "foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!(# # # "foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!(# # "foo"#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo6!(# # # "foo"# #);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+
+    demo4!("foo"# # #);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+
+    demo2!(# "");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(# ""#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(# # "");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo2!(# "foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(# # "foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(# "foo"#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!(# # "foo"#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo5!(# # "foo"# #);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+}
diff --git a/tests/ui/rust-2024/reserved-guarded-strings-migration.rs b/tests/ui/rust-2024/reserved-guarded-strings-migration.rs
new file mode 100644
index 00000000000..5905f2abe32
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings-migration.rs
@@ -0,0 +1,99 @@
+//@ check-pass
+//@ run-rustfix
+//@ edition:2021
+
+#![warn(rust_2024_guarded_string_incompatible_syntax)]
+
+macro_rules! demo1 {
+    ( $a:tt ) => { println!("one tokens") };
+}
+
+macro_rules! demo2 {
+    ( $a:tt $b:tt ) => { println!("two tokens") };
+}
+
+macro_rules! demo3 {
+    ( $a:tt $b:tt $c:tt ) => { println!("three tokens") };
+}
+
+macro_rules! demo4 {
+    ( $a:tt $b:tt $c:tt $d:tt ) => { println!("four tokens") };
+}
+
+macro_rules! demo5 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt ) => { println!("five tokens") };
+}
+
+macro_rules! demo6 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt $f:tt ) => { println!("six tokens") };
+}
+
+
+fn main() {
+    demo1!("");
+    demo2!(# "");
+    demo3!(# ""#);
+    demo2!(# "foo");
+    demo3!(# "foo"#);
+    demo2!("foo"#);
+
+    demo3!(## "foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!(### "foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!(## "foo"#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo6!(### "foo"##);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+
+    demo4!("foo"###);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+
+    demo2!(#"");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(#""#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(##"");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo2!(#"foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(##"foo");
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo3!(#"foo"#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo4!(##"foo"#);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    demo5!(##"foo"##);
+    //~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+    //~| WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
+    //~| WARNING hard error in Rust 2024
+}
diff --git a/tests/ui/rust-2024/reserved-guarded-strings-migration.stderr b/tests/ui/rust-2024/reserved-guarded-strings-migration.stderr
new file mode 100644
index 00000000000..d7f8e5c9b4b
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings-migration.stderr
@@ -0,0 +1,293 @@
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:40:12
+   |
+LL |     demo3!(## "foo");
+   |            ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+note: the lint level is defined here
+  --> $DIR/reserved-guarded-strings-migration.rs:5:9
+   |
+LL | #![warn(rust_2024_guarded_string_incompatible_syntax)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(# # "foo");
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:43:12
+   |
+LL |     demo4!(### "foo");
+   |            ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(# ## "foo");
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:43:13
+   |
+LL |     demo4!(### "foo");
+   |             ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(## # "foo");
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:48:12
+   |
+LL |     demo4!(## "foo"#);
+   |            ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(# # "foo"#);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:51:12
+   |
+LL |     demo6!(### "foo"##);
+   |            ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo6!(# ## "foo"##);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:51:13
+   |
+LL |     demo6!(### "foo"##);
+   |             ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo6!(## # "foo"##);
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:51:21
+   |
+LL |     demo6!(### "foo"##);
+   |                     ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo6!(### "foo"# #);
+   |                      +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:59:17
+   |
+LL |     demo4!("foo"###);
+   |                 ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!("foo"# ##);
+   |                  +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:59:18
+   |
+LL |     demo4!("foo"###);
+   |                  ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!("foo"## #);
+   |                   +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:65:12
+   |
+LL |     demo2!(#"");
+   |            ^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo2!(# "");
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:68:12
+   |
+LL |     demo3!(#""#);
+   |            ^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(# ""#);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:71:12
+   |
+LL |     demo3!(##"");
+   |            ^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(# #"");
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:71:13
+   |
+LL |     demo3!(##"");
+   |             ^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(## "");
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:76:12
+   |
+LL |     demo2!(#"foo");
+   |            ^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo2!(# "foo");
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:79:12
+   |
+LL |     demo3!(##"foo");
+   |            ^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(# #"foo");
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:79:13
+   |
+LL |     demo3!(##"foo");
+   |             ^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(## "foo");
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:84:12
+   |
+LL |     demo3!(#"foo"#);
+   |            ^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo3!(# "foo"#);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:87:12
+   |
+LL |     demo4!(##"foo"#);
+   |            ^^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(# #"foo"#);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:87:13
+   |
+LL |     demo4!(##"foo"#);
+   |             ^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo4!(## "foo"#);
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:92:12
+   |
+LL |     demo5!(##"foo"##);
+   |            ^^^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(# #"foo"##);
+   |             +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:92:13
+   |
+LL |     demo5!(##"foo"##);
+   |             ^^^^^^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(## "foo"##);
+   |              +
+
+warning: will be parsed as a guarded string in Rust 2024
+  --> $DIR/reserved-guarded-strings-migration.rs:92:19
+   |
+LL |     demo5!(##"foo"##);
+   |                   ^^
+   |
+   = warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
+   = note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
+help: insert whitespace here to avoid this being parsed as a guarded string in Rust 2024
+   |
+LL |     demo5!(##"foo"# #);
+   |                    +
+
+warning: 22 warnings emitted
+
diff --git a/tests/ui/rust-2024/reserved-guarded-strings-via-macro-2.rs b/tests/ui/rust-2024/reserved-guarded-strings-via-macro-2.rs
new file mode 100644
index 00000000000..3f9f373ba22
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings-via-macro-2.rs
@@ -0,0 +1,18 @@
+//@ edition:2021
+//@ aux-build:reserved-guarded-strings-macro-2021.rs
+//@ aux-build:reserved-guarded-strings-macro-2024.rs
+
+extern crate reserved_guarded_strings_macro_2021 as m2021;
+extern crate reserved_guarded_strings_macro_2024 as m2024;
+
+fn main() {
+    // Ok:
+    m2021::number_of_tokens_in_a_guarded_string_literal!();
+    m2021::number_of_tokens_in_a_guarded_unterminated_string_literal!();
+
+    // Error, even though *this* crate is 2021:
+    m2024::number_of_tokens_in_a_guarded_string_literal!();
+    //~^ ERROR invalid string literal
+    m2024::number_of_tokens_in_a_guarded_unterminated_string_literal!();
+    //~^ ERROR invalid string literal
+}
diff --git a/tests/ui/rust-2024/reserved-guarded-strings-via-macro-2.stderr b/tests/ui/rust-2024/reserved-guarded-strings-via-macro-2.stderr
new file mode 100644
index 00000000000..1074c8a682b
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings-via-macro-2.stderr
@@ -0,0 +1,20 @@
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings-via-macro-2.rs:14:5
+   |
+LL |     m2024::number_of_tokens_in_a_guarded_string_literal!();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+   = note: this error originates in the macro `m2024::number_of_tokens_in_a_guarded_string_literal` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings-via-macro-2.rs:16:5
+   |
+LL |     m2024::number_of_tokens_in_a_guarded_unterminated_string_literal!();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+   = note: this error originates in the macro `m2024::number_of_tokens_in_a_guarded_unterminated_string_literal` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/ui/rust-2024/reserved-guarded-strings-via-macro.rs b/tests/ui/rust-2024/reserved-guarded-strings-via-macro.rs
new file mode 100644
index 00000000000..f9e3c1e3c51
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings-via-macro.rs
@@ -0,0 +1,12 @@
+//@ run-pass
+//@ compile-flags: -Zunstable-options
+//@ edition:2024
+//@ aux-build:reserved-guarded-strings-macro-2021.rs
+
+extern crate reserved_guarded_strings_macro_2021 as m2021;
+
+fn main() {
+    // Ok, even though *this* crate is 2024:
+    assert_eq!(m2021::number_of_tokens_in_a_guarded_string_literal!(), 3);
+    assert_eq!(m2021::number_of_tokens_in_a_guarded_unterminated_string_literal!(), 2);
+}
diff --git a/tests/ui/rust-2024/reserved-guarded-strings.rs b/tests/ui/rust-2024/reserved-guarded-strings.rs
new file mode 100644
index 00000000000..dab97039be0
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings.rs
@@ -0,0 +1,74 @@
+//@ compile-flags: -Zunstable-options
+//@ edition:2024
+// ignore-tidy-linelength
+
+macro_rules! demo1 {
+    ( $a:tt ) => { println!("one tokens") };
+}
+
+macro_rules! demo2 {
+    ( $a:tt $b:tt ) => { println!("two tokens") };
+}
+
+macro_rules! demo3 {
+    ( $a:tt $b:tt $c:tt ) => { println!("three tokens") };
+}
+
+macro_rules! demo4 {
+    ( $a:tt $b:tt $c:tt $d:tt ) => { println!("four tokens") };
+}
+
+macro_rules! demo5 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt ) => { println!("five tokens") };
+}
+
+macro_rules! demo6 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt $f:tt ) => { println!("six tokens") };
+}
+
+macro_rules! demo7 {
+    ( $a:tt $b:tt $c:tt $d:tt $e:tt $f:tt $g:tt ) => { println!("seven tokens") };
+}
+
+macro_rules! demon {
+    ( $($n:tt)* ) => { println!("unknown number of tokens") };
+}
+
+fn main() {
+    demo1!("");
+    demo2!(# "");
+    demo3!(# ""#);
+    demo2!(# "foo");
+    demo3!(# "foo"#);
+    demo2!("foo"#);
+
+    demo2!(blah"xx"); //~ ERROR prefix `blah` is unknown
+    demo2!(blah#"xx"#);
+    //~^ ERROR prefix `blah` is unknown
+    //~| ERROR invalid string literal
+
+    demo2!(## "foo"); //~ ERROR invalid string literal
+    demo3!("foo"###); //~ ERROR invalid string literal
+    demo3!(### "foo"); //~ ERROR invalid string literal
+    demo3!(## "foo"#); //~ ERROR invalid string literal
+    demo5!(### "foo"###);
+    //~^ ERROR invalid string literal
+    //~| ERROR invalid string literal
+
+    demo1!(#""); //~ ERROR invalid string literal
+    demo1!(#""#); //~ ERROR invalid string literal
+    demo1!(####""); //~ ERROR invalid string literal
+    demo1!(#"foo"); //~ ERROR invalid string literal
+    demo1!(###"foo"); //~ ERROR invalid string literal
+    demo1!(#"foo"#); //~ ERROR invalid string literal
+    demo1!(###"foo"#); //~ ERROR invalid string literal
+    demo1!(###"foo"##); //~ ERROR invalid string literal
+    demo1!(###"foo"###); //~ ERROR invalid string literal
+    demo2!(#"foo"###);
+    //~^ ERROR invalid string literal
+    //~| ERROR invalid string literal
+
+    // More than 255 hashes
+    demon!(####################################################################################################################################################################################################################################################################"foo");
+    //~^ ERROR invalid string literal
+}
diff --git a/tests/ui/rust-2024/reserved-guarded-strings.stderr b/tests/ui/rust-2024/reserved-guarded-strings.stderr
new file mode 100644
index 00000000000..f465ba7944a
--- /dev/null
+++ b/tests/ui/rust-2024/reserved-guarded-strings.stderr
@@ -0,0 +1,254 @@
+error: prefix `blah` is unknown
+  --> $DIR/reserved-guarded-strings.rs:45:12
+   |
+LL |     demo2!(blah"xx");
+   |            ^^^^ unknown prefix
+   |
+   = note: prefixed identifiers and literals are reserved since Rust 2021
+help: consider inserting whitespace here
+   |
+LL |     demo2!(blah "xx");
+   |                +
+
+error: prefix `blah` is unknown
+  --> $DIR/reserved-guarded-strings.rs:46:12
+   |
+LL |     demo2!(blah#"xx"#);
+   |            ^^^^ unknown prefix
+   |
+   = note: prefixed identifiers and literals are reserved since Rust 2021
+help: consider inserting whitespace here
+   |
+LL |     demo2!(blah #"xx"#);
+   |                +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:46:16
+   |
+LL |     demo2!(blah#"xx"#);
+   |                ^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo2!(blah# "xx"#);
+   |                 +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:50:12
+   |
+LL |     demo2!(## "foo");
+   |            ^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo2!(# # "foo");
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:51:17
+   |
+LL |     demo3!("foo"###);
+   |                 ^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo3!("foo"# ##);
+   |                  +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:52:12
+   |
+LL |     demo3!(### "foo");
+   |            ^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo3!(# ## "foo");
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:53:12
+   |
+LL |     demo3!(## "foo"#);
+   |            ^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo3!(# # "foo"#);
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:54:12
+   |
+LL |     demo5!(### "foo"###);
+   |            ^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo5!(# ## "foo"###);
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:54:21
+   |
+LL |     demo5!(### "foo"###);
+   |                     ^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo5!(### "foo"# ##);
+   |                      +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:58:12
+   |
+LL |     demo1!(#"");
+   |            ^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# "");
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:59:12
+   |
+LL |     demo1!(#""#);
+   |            ^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# ""#);
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:60:12
+   |
+LL |     demo1!(####"");
+   |            ^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# ###"");
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:61:12
+   |
+LL |     demo1!(#"foo");
+   |            ^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# "foo");
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:62:12
+   |
+LL |     demo1!(###"foo");
+   |            ^^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# ##"foo");
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:63:12
+   |
+LL |     demo1!(#"foo"#);
+   |            ^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# "foo"#);
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:64:12
+   |
+LL |     demo1!(###"foo"#);
+   |            ^^^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# ##"foo"#);
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:65:12
+   |
+LL |     demo1!(###"foo"##);
+   |            ^^^^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# ##"foo"##);
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:66:12
+   |
+LL |     demo1!(###"foo"###);
+   |            ^^^^^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo1!(# ##"foo"###);
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:67:12
+   |
+LL |     demo2!(#"foo"###);
+   |            ^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo2!(# "foo"###);
+   |             +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:67:19
+   |
+LL |     demo2!(#"foo"###);
+   |                   ^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demo2!(#"foo"## #);
+   |                    +
+
+error: invalid string literal
+  --> $DIR/reserved-guarded-strings.rs:72:12
+   |
+LL | ...n!(####################################################################################################################################################################################################################################################################"foo...
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: unprefixed guarded string literals are reserved for future use since Rust 2024
+help: consider inserting whitespace here
+   |
+LL |     demon!(# ###################################################################################################################################################################################################################################################################"foo");
+   |             +
+
+error: aborting due to 21 previous errors
+