From 321a5db7d4026cf703d6deebc413c855ba133142 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Sun, 14 Apr 2024 17:37:45 -0600
Subject: Reserve guarded string literals (RFC 3593)
---
compiler/rustc_parse/messages.ftl | 4 ++
compiler/rustc_parse/src/errors.rs | 18 ++++++++
compiler/rustc_parse/src/lexer/mod.rs | 84 ++++++++++++++++++++++++++++++++++-
3 files changed, 105 insertions(+), 1 deletion(-)
(limited to 'compiler/rustc_parse')
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
@@ -2110,6 +2110,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,
+}
+#[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 {
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 });
}
--
cgit 1.4.1-3-g733a5