From 49d2d5a1161720ccd5b76ac2afbdceb6ea7e2e6e Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Fri, 31 Jan 2025 15:02:41 +0100
Subject: [PATCH 01/10] Extract `unescape` from `rustc_lexer` into its own
 crate

---
 Cargo.lock                                            |  7 +++++++
 compiler/rustc_ast/Cargo.toml                         |  1 +
 compiler/rustc_ast/src/util/literal.rs                |  2 +-
 compiler/rustc_lexer/src/lib.rs                       |  1 -
 compiler/rustc_parse/Cargo.toml                       |  1 +
 compiler/rustc_parse/src/lexer/mod.rs                 |  6 +++---
 .../rustc_parse/src/lexer/unescape_error_reporting.rs |  2 +-
 compiler/rustc_parse/src/parser/expr.rs               |  2 +-
 compiler/rustc_parse_format/Cargo.toml                |  1 +
 compiler/rustc_parse_format/src/lib.rs                | 11 ++++++-----
 library/literal-escaper/Cargo.toml                    | 10 ++++++++++
 library/literal-escaper/README.md                     |  4 ++++
 .../unescape.rs => library/literal-escaper/src/lib.rs |  0
 .../unescape => library/literal-escaper/src}/tests.rs |  0
 14 files changed, 36 insertions(+), 12 deletions(-)
 create mode 100644 library/literal-escaper/Cargo.toml
 create mode 100644 library/literal-escaper/README.md
 rename compiler/rustc_lexer/src/unescape.rs => library/literal-escaper/src/lib.rs (100%)
 rename {compiler/rustc_lexer/src/unescape => library/literal-escaper/src}/tests.rs (100%)

diff --git a/Cargo.lock b/Cargo.lock
index fc4c7c9888fa6..63441814d4a1f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2151,6 +2151,10 @@ version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
 
+[[package]]
+name = "literal-escaper"
+version = "0.0.1"
+
 [[package]]
 name = "lld-wrapper"
 version = "0.1.0"
@@ -3366,6 +3370,7 @@ name = "rustc_ast"
 version = "0.0.0"
 dependencies = [
  "bitflags",
+ "literal-escaper",
  "memchr",
  "rustc_ast_ir",
  "rustc_data_structures",
@@ -4325,6 +4330,7 @@ name = "rustc_parse"
 version = "0.0.0"
 dependencies = [
  "bitflags",
+ "literal-escaper",
  "rustc_ast",
  "rustc_ast_pretty",
  "rustc_data_structures",
@@ -4347,6 +4353,7 @@ dependencies = [
 name = "rustc_parse_format"
 version = "0.0.0"
 dependencies = [
+ "literal-escaper",
  "rustc_index",
  "rustc_lexer",
 ]
diff --git a/compiler/rustc_ast/Cargo.toml b/compiler/rustc_ast/Cargo.toml
index 34c612dac692b..176c2d6f8a791 100644
--- a/compiler/rustc_ast/Cargo.toml
+++ b/compiler/rustc_ast/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
 [dependencies]
 # tidy-alphabetical-start
 bitflags = "2.4.1"
+literal-escaper = { path = "../../library/literal-escaper" }
 memchr = "2.7.4"
 rustc_ast_ir = { path = "../rustc_ast_ir" }
 rustc_data_structures = { path = "../rustc_data_structures" }
diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs
index 6896ac723fa58..121331ece6d64 100644
--- a/compiler/rustc_ast/src/util/literal.rs
+++ b/compiler/rustc_ast/src/util/literal.rs
@@ -2,7 +2,7 @@
 
 use std::{ascii, fmt, str};
 
-use rustc_lexer::unescape::{
+use literal_escaper::{
     MixedUnit, Mode, byte_from_char, unescape_byte, unescape_char, unescape_mixed, unescape_unicode,
 };
 use rustc_span::{Span, Symbol, kw, sym};
diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs
index c63ab77decac9..334f451a39c71 100644
--- a/compiler/rustc_lexer/src/lib.rs
+++ b/compiler/rustc_lexer/src/lib.rs
@@ -27,7 +27,6 @@
 // tidy-alphabetical-end
 
 mod cursor;
-pub mod unescape;
 
 #[cfg(test)]
 mod tests;
diff --git a/compiler/rustc_parse/Cargo.toml b/compiler/rustc_parse/Cargo.toml
index 2360914a0aba1..109e1f5c0931e 100644
--- a/compiler/rustc_parse/Cargo.toml
+++ b/compiler/rustc_parse/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
 [dependencies]
 # tidy-alphabetical-start
 bitflags = "2.4.1"
+literal-escaper = { path = "../../library/literal-escaper" }
 rustc_ast = { path = "../rustc_ast" }
 rustc_ast_pretty = { path = "../rustc_ast_pretty" }
 rustc_data_structures = { path = "../rustc_data_structures" }
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index 792e2cc26ef1c..cefaf13e31fbe 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -1,12 +1,12 @@
 use std::ops::Range;
 
+use literal_escaper::{self, EscapeError, Mode};
 use rustc_ast::ast::{self, AttrStyle};
 use rustc_ast::token::{self, CommentKind, Delimiter, IdentIsRaw, Token, TokenKind};
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::util::unicode::contains_text_flow_control_chars;
 use rustc_errors::codes::*;
 use rustc_errors::{Applicability, Diag, DiagCtxtHandle, StashKey};
-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::{
@@ -970,7 +970,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> {
         postfix_len: u32,
     ) -> (token::LitKind, Symbol) {
         self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| {
-            unescape::unescape_unicode(src, mode, &mut |span, result| {
+            literal_escaper::unescape_unicode(src, mode, &mut |span, result| {
                 callback(span, result.map(drop))
             })
         })
@@ -986,7 +986,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> {
         postfix_len: u32,
     ) -> (token::LitKind, Symbol) {
         self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| {
-            unescape::unescape_mixed(src, mode, &mut |span, result| {
+            literal_escaper::unescape_mixed(src, mode, &mut |span, result| {
                 callback(span, result.map(drop))
             })
         })
diff --git a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
index 2e066f0179c3f..e8aa400e73d44 100644
--- a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
+++ b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
@@ -3,8 +3,8 @@
 use std::iter::once;
 use std::ops::Range;
 
+use literal_escaper::{EscapeError, Mode};
 use rustc_errors::{Applicability, DiagCtxtHandle, ErrorGuaranteed};
-use rustc_lexer::unescape::{EscapeError, Mode};
 use rustc_span::{BytePos, Span};
 use tracing::debug;
 
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index e0e6c2177da54..0b745217bc8be 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -6,6 +6,7 @@ use core::ops::{Bound, ControlFlow};
 use ast::mut_visit::{self, MutVisitor};
 use ast::token::IdentIsRaw;
 use ast::{CoroutineKind, ForLoopKind, GenBlockKind, MatchKind, Pat, Path, PathSegment, Recovered};
+use literal_escaper::unescape_char;
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, Delimiter, Token, TokenKind};
 use rustc_ast::tokenstream::TokenTree;
@@ -21,7 +22,6 @@ use rustc_ast::{
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_errors::{Applicability, Diag, PResult, StashKey, Subdiagnostic};
-use rustc_lexer::unescape::unescape_char;
 use rustc_macros::Subdiagnostic;
 use rustc_session::errors::{ExprParenthesesNeeded, report_lit_error};
 use rustc_session::lint::BuiltinLintDiag;
diff --git a/compiler/rustc_parse_format/Cargo.toml b/compiler/rustc_parse_format/Cargo.toml
index 707c4e318474a..51ea46f4c9b40 100644
--- a/compiler/rustc_parse_format/Cargo.toml
+++ b/compiler/rustc_parse_format/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
 
 [dependencies]
 # tidy-alphabetical-start
+literal-escaper = { path = "../../library/literal-escaper" }
 rustc_index = { path = "../rustc_index", default-features = false }
 rustc_lexer = { path = "../rustc_lexer" }
 # tidy-alphabetical-end
diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs
index 3b985621b5772..7e89f9b079b62 100644
--- a/compiler/rustc_parse_format/src/lib.rs
+++ b/compiler/rustc_parse_format/src/lib.rs
@@ -19,7 +19,6 @@
 pub use Alignment::*;
 pub use Count::*;
 pub use Position::*;
-use rustc_lexer::unescape;
 
 // Note: copied from rustc_span
 /// Range inside of a `Span` used for diagnostics when we only have access to relative positions.
@@ -1095,12 +1094,14 @@ fn find_width_map_from_snippet(
 fn unescape_string(string: &str) -> Option<String> {
     let mut buf = String::new();
     let mut ok = true;
-    unescape::unescape_unicode(string, unescape::Mode::Str, &mut |_, unescaped_char| {
-        match unescaped_char {
+    literal_escaper::unescape_unicode(
+        string,
+        literal_escaper::Mode::Str,
+        &mut |_, unescaped_char| match unescaped_char {
             Ok(c) => buf.push(c),
             Err(_) => ok = false,
-        }
-    });
+        },
+    );
 
     ok.then_some(buf)
 }
diff --git a/library/literal-escaper/Cargo.toml b/library/literal-escaper/Cargo.toml
new file mode 100644
index 0000000000000..708fcd3cacb69
--- /dev/null
+++ b/library/literal-escaper/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "literal-escaper"
+version = "0.0.0"
+edition = "2021"
+
+[dependencies]
+std = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-std' }
+
+[features]
+rustc-dep-of-std = ["dep:std"]
diff --git a/library/literal-escaper/README.md b/library/literal-escaper/README.md
new file mode 100644
index 0000000000000..5384ac4556a13
--- /dev/null
+++ b/library/literal-escaper/README.md
@@ -0,0 +1,4 @@
+# literal-escaper
+
+This crate provides code to unescape string literals. It is used by `rustc_lexer`
+and `proc-macro`.
diff --git a/compiler/rustc_lexer/src/unescape.rs b/library/literal-escaper/src/lib.rs
similarity index 100%
rename from compiler/rustc_lexer/src/unescape.rs
rename to library/literal-escaper/src/lib.rs
diff --git a/compiler/rustc_lexer/src/unescape/tests.rs b/library/literal-escaper/src/tests.rs
similarity index 100%
rename from compiler/rustc_lexer/src/unescape/tests.rs
rename to library/literal-escaper/src/tests.rs

From b993f9c835a3ed2119d45597f74e3eaedbf806e7 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Fri, 31 Jan 2025 15:45:18 +0100
Subject: [PATCH 02/10] Add `_value` methods to proc_macro lib

---
 Cargo.lock                    |  11 +++-
 library/Cargo.lock            |   8 +++
 library/proc_macro/Cargo.toml |   1 +
 library/proc_macro/src/lib.rs | 114 ++++++++++++++++++++++++++++++++++
 4 files changed, 133 insertions(+), 1 deletion(-)

diff --git a/Cargo.lock b/Cargo.lock
index 63441814d4a1f..06c1394295769 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2153,7 +2153,10 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
 
 [[package]]
 name = "literal-escaper"
-version = "0.0.1"
+version = "0.0.0"
+dependencies = [
+ "rustc-std-workspace-std 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "lld-wrapper"
@@ -3332,6 +3335,12 @@ version = "1.0.1"
 name = "rustc-std-workspace-std"
 version = "1.0.1"
 
+[[package]]
+name = "rustc-std-workspace-std"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aba676a20abe46e5b0f1b0deae474aaaf31407e6c71147159890574599da04ef"
+
 [[package]]
 name = "rustc_abi"
 version = "0.0.0"
diff --git a/library/Cargo.lock b/library/Cargo.lock
index 8b78908e6d730..f769fb2e1e1d3 100644
--- a/library/Cargo.lock
+++ b/library/Cargo.lock
@@ -158,6 +158,13 @@ dependencies = [
  "rustc-std-workspace-core",
 ]
 
+[[package]]
+name = "literal-escaper"
+version = "0.0.0"
+dependencies = [
+ "rustc-std-workspace-std",
+]
+
 [[package]]
 name = "memchr"
 version = "2.7.4"
@@ -220,6 +227,7 @@ name = "proc_macro"
 version = "0.0.0"
 dependencies = [
  "core",
+ "literal-escaper",
  "std",
 ]
 
diff --git a/library/proc_macro/Cargo.toml b/library/proc_macro/Cargo.toml
index e54a50aa15c61..e5c90309f16d0 100644
--- a/library/proc_macro/Cargo.toml
+++ b/library/proc_macro/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.0.0"
 edition = "2021"
 
 [dependencies]
+literal-escaper = { path = "../literal-escaper", features = ["rustc-dep-of-std"] }
 std = { path = "../std" }
 # Workaround: when documenting this crate rustdoc will try to load crate named
 # `core` when resolving doc links. Without this line a different `core` will be
diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs
index 6611ce30a1b01..57dd47f106089 100644
--- a/library/proc_macro/src/lib.rs
+++ b/library/proc_macro/src/lib.rs
@@ -28,6 +28,7 @@
 #![feature(restricted_std)]
 #![feature(rustc_attrs)]
 #![feature(extend_one)]
+#![feature(stmt_expr_attributes)]
 #![recursion_limit = "256"]
 #![allow(internal_features)]
 #![deny(ffi_unwind_calls)]
@@ -50,11 +51,23 @@ use std::{error, fmt};
 
 #[unstable(feature = "proc_macro_diagnostic", issue = "54140")]
 pub use diagnostic::{Diagnostic, Level, MultiSpan};
+#[unstable(feature = "proc_macro_value", issue = "136652")]
+pub use literal_escaper::EscapeError;
+use literal_escaper::{MixedUnit, Mode, byte_from_char, unescape_mixed, unescape_unicode};
 #[unstable(feature = "proc_macro_totokens", issue = "130977")]
 pub use to_tokens::ToTokens;
 
 use crate::escape::{EscapeOptions, escape_bytes};
 
+/// Errors returned when trying to retrieve a literal unescaped value.
+#[unstable(feature = "proc_macro_value", issue = "136652")]
+pub enum ConversionErrorKind {
+    /// The literal failed to be escaped, take a look at [`EscapeError`] for more information.
+    FailedToUnescape(EscapeError),
+    /// Trying to convert a literal with the wrong type.
+    InvalidLiteralKind,
+}
+
 /// Determines whether proc_macro has been made accessible to the currently
 /// running program.
 ///
@@ -1450,6 +1463,107 @@ impl Literal {
             }
         })
     }
+
+    /// Returns the unescaped string value if the current literal is a string or a string literal.
+    #[unstable(feature = "proc_macro_value", issue = "136652")]
+    pub fn str_value(&self) -> Result<String, ConversionErrorKind> {
+        self.0.symbol.with(|symbol| match self.0.kind {
+            bridge::LitKind::Str => {
+                if symbol.contains('\\') {
+                    let mut buf = String::with_capacity(symbol.len());
+                    let mut error = None;
+                    // Force-inlining here is aggressive but the closure is
+                    // called on every char in the string, so it can be hot in
+                    // programs with many long strings containing escapes.
+                    unescape_unicode(
+                        symbol,
+                        Mode::Str,
+                        &mut #[inline(always)]
+                        |_, c| match c {
+                            Ok(c) => buf.push(c),
+                            Err(err) => {
+                                if err.is_fatal() {
+                                    error = Some(ConversionErrorKind::FailedToUnescape(err));
+                                }
+                            }
+                        },
+                    );
+                    if let Some(error) = error { Err(error) } else { Ok(buf) }
+                } else {
+                    Ok(symbol.to_string())
+                }
+            }
+            bridge::LitKind::StrRaw(_) => Ok(symbol.to_string()),
+            _ => Err(ConversionErrorKind::InvalidLiteralKind),
+        })
+    }
+
+    /// Returns the unescaped string value if the current literal is a c-string or a c-string
+    /// literal.
+    #[unstable(feature = "proc_macro_value", issue = "136652")]
+    pub fn cstr_value(&self) -> Result<Vec<u8>, ConversionErrorKind> {
+        self.0.symbol.with(|symbol| match self.0.kind {
+            bridge::LitKind::CStr => {
+                let mut error = None;
+                let mut buf = Vec::with_capacity(symbol.len());
+
+                unescape_mixed(symbol, Mode::CStr, &mut |_span, c| match c {
+                    Ok(MixedUnit::Char(c)) => {
+                        buf.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes())
+                    }
+                    Ok(MixedUnit::HighByte(b)) => buf.push(b),
+                    Err(err) => {
+                        if err.is_fatal() {
+                            error = Some(ConversionErrorKind::FailedToUnescape(err));
+                        }
+                    }
+                });
+                if let Some(error) = error {
+                    Err(error)
+                } else {
+                    buf.push(0);
+                    Ok(buf)
+                }
+            }
+            bridge::LitKind::CStrRaw(_) => {
+                // Raw strings have no escapes so we can convert the symbol
+                // directly to a `Lrc<u8>` after appending the terminating NUL
+                // char.
+                let mut buf = symbol.to_owned().into_bytes();
+                buf.push(0);
+                Ok(buf)
+            }
+            _ => Err(ConversionErrorKind::InvalidLiteralKind),
+        })
+    }
+
+    /// Returns the unescaped string value if the current literal is a byte string or a byte string
+    /// literal.
+    #[unstable(feature = "proc_macro_value", issue = "136652")]
+    pub fn byte_str_value(&self) -> Result<Vec<u8>, ConversionErrorKind> {
+        self.0.symbol.with(|symbol| match self.0.kind {
+            bridge::LitKind::ByteStr => {
+                let mut buf = Vec::with_capacity(symbol.len());
+                let mut error = None;
+
+                unescape_unicode(symbol, Mode::ByteStr, &mut |_, c| match c {
+                    Ok(c) => buf.push(byte_from_char(c)),
+                    Err(err) => {
+                        if err.is_fatal() {
+                            error = Some(ConversionErrorKind::FailedToUnescape(err));
+                        }
+                    }
+                });
+                if let Some(error) = error { Err(error) } else { Ok(buf) }
+            }
+            bridge::LitKind::ByteStrRaw(_) => {
+                // Raw strings have no escapes so we can convert the symbol
+                // directly to a `Lrc<u8>`.
+                Ok(symbol.to_owned().into_bytes())
+            }
+            _ => Err(ConversionErrorKind::InvalidLiteralKind),
+        })
+    }
 }
 
 /// Parse a single literal from its stringified representation.

From 615a9cd10a0d99f12c8b07a9e9fbff52b08e2139 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Fri, 31 Jan 2025 23:12:15 +0100
Subject: [PATCH 03/10] Ignore duplicated dep for `literal-escaper`

---
 src/bootstrap/src/core/metadata.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/bootstrap/src/core/metadata.rs b/src/bootstrap/src/core/metadata.rs
index 983674d2c6835..e6b01b2e2d95a 100644
--- a/src/bootstrap/src/core/metadata.rs
+++ b/src/bootstrap/src/core/metadata.rs
@@ -63,6 +63,11 @@ pub fn build(build: &mut Build) {
             let relative_path = krate.local_path(build);
             build.crates.insert(name.clone(), krate);
             let existing_path = build.crate_paths.insert(relative_path, name);
+            // `literal-escaper` is both a dependency of `compiler/rustc_lexer` and of
+            // `library/proc-macro`, making it appear multiple times in the workspace.
+            if existing_path.as_deref() == Some("literal-escaper") {
+                continue;
+            }
             assert!(
                 existing_path.is_none(),
                 "multiple crates with the same path: {}",

From 94f0f2b603bdd10fabc7e06e29d25f230d22a93f Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Fri, 31 Jan 2025 23:42:09 +0100
Subject: [PATCH 04/10] Reexport `literal-escaper` from `rustc_lexer` to allow
 rust-analyzer to compile

---
 Cargo.lock                      | 1 +
 compiler/rustc_lexer/Cargo.toml | 1 +
 compiler/rustc_lexer/src/lib.rs | 3 +++
 3 files changed, 5 insertions(+)

diff --git a/Cargo.lock b/Cargo.lock
index 06c1394295769..e0bf24f1eda71 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4074,6 +4074,7 @@ name = "rustc_lexer"
 version = "0.0.0"
 dependencies = [
  "expect-test",
+ "literal-escaper",
  "memchr",
  "unicode-properties",
  "unicode-xid",
diff --git a/compiler/rustc_lexer/Cargo.toml b/compiler/rustc_lexer/Cargo.toml
index 4b3492fdeda25..5789cd0195883 100644
--- a/compiler/rustc_lexer/Cargo.toml
+++ b/compiler/rustc_lexer/Cargo.toml
@@ -16,6 +16,7 @@ Rust lexer used by rustc. No stability guarantees are provided.
 [dependencies]
 memchr = "2.7.4"
 unicode-xid = "0.2.0"
+literal-escaper = { path = "../../library/literal-escaper" }
 
 [dependencies.unicode-properties]
 version = "0.1.0"
diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs
index 334f451a39c71..587ef89566a7e 100644
--- a/compiler/rustc_lexer/src/lib.rs
+++ b/compiler/rustc_lexer/src/lib.rs
@@ -31,6 +31,9 @@ mod cursor;
 #[cfg(test)]
 mod tests;
 
+// FIXME: This is needed for rust-analyzer. Remove this dependency once rust-analyzer uses
+// `literal-escaper`.
+pub use literal_escaper as unescape;
 use unicode_properties::UnicodeEmoji;
 pub use unicode_xid::UNICODE_VERSION as UNICODE_XID_VERSION;
 

From e256a21734dbd88bed0ec6934e5d6b2ab2754927 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Sat, 1 Feb 2025 00:07:21 +0100
Subject: [PATCH 05/10] Add `literal-escaper` and `rustc-std-workspace-std` to
 the allowed rustc deps list

---
 src/tools/tidy/src/deps.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index faa0db27b2b05..5d871f0ab26f1 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -320,6 +320,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
     "libloading",
     "linux-raw-sys",
     "litemap",
+    "literal-escaper",
     "lock_api",
     "log",
     "matchers",
@@ -366,6 +367,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
     "rustc-rayon",
     "rustc-rayon-core",
     "rustc-stable-hash",
+    "rustc-std-workspace-std",
     "rustc_apfloat",
     "rustc_version",
     "rustix",

From d40ed632b6909f4ebf0b3cbda98615b9c2acd65b Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Mon, 3 Feb 2025 23:09:24 +0100
Subject: [PATCH 06/10] Fix bootstrap `build_all` test

---
 src/bootstrap/src/core/builder/tests.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index 74e1ed5c63763..d72e185f51a74 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -647,7 +647,7 @@ mod dist {
         let mut builder = Builder::new(&build);
         builder.run_step_descriptions(
             &Builder::get_step_descriptions(Kind::Build),
-            &["compiler/rustc".into(), "library".into()],
+            &["compiler/rustc".into(), "std".into()],
         );
 
         assert_eq!(

From 3c33cbe7789bf25611842b261f7fa07d592a672e Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Thu, 6 Feb 2025 20:55:54 +0100
Subject: [PATCH 07/10] Add ui test for ensuring that users cannot use
 `literal-escaper` crate for the time being

---
 tests/ui/feature-gates/literal-escaper.rs     |  3 +++
 tests/ui/feature-gates/literal-escaper.stderr | 13 +++++++++++++
 2 files changed, 16 insertions(+)
 create mode 100644 tests/ui/feature-gates/literal-escaper.rs
 create mode 100644 tests/ui/feature-gates/literal-escaper.stderr

diff --git a/tests/ui/feature-gates/literal-escaper.rs b/tests/ui/feature-gates/literal-escaper.rs
new file mode 100644
index 0000000000000..7c145fca7dec2
--- /dev/null
+++ b/tests/ui/feature-gates/literal-escaper.rs
@@ -0,0 +1,3 @@
+#![crate_type = "lib"]
+
+extern crate literal_escaper; //~ ERROR
diff --git a/tests/ui/feature-gates/literal-escaper.stderr b/tests/ui/feature-gates/literal-escaper.stderr
new file mode 100644
index 0000000000000..edddb6504f575
--- /dev/null
+++ b/tests/ui/feature-gates/literal-escaper.stderr
@@ -0,0 +1,13 @@
+error[E0658]: use of unstable library feature `rustc_private`: this crate is being loaded from the sysroot, an unstable location; did you mean to load this crate from crates.io via `Cargo.toml` instead?
+  --> $DIR/literal-escaper.rs:3:1
+   |
+LL | extern crate literal_escaper;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #27812 <https://github.com/rust-lang/rust/issues/27812> for more information
+   = help: add `#![feature(rustc_private)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0658`.

From ec4b3c2779883a6b7ebb1ce5954904ef2c95d3a4 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Sun, 16 Mar 2025 14:28:10 +0100
Subject: [PATCH 08/10] Add test for new proc_macro literal methods

---
 library/literal-escaper/README.md             |  2 +-
 library/proc_macro/src/lib.rs                 |  1 +
 tests/ui/proc-macro/auxiliary/api/literal.rs  | 53 ++++++++++++++++++-
 .../auxiliary/api/proc_macro_api_tests.rs     |  1 +
 4 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/library/literal-escaper/README.md b/library/literal-escaper/README.md
index 5384ac4556a13..9986d2451c759 100644
--- a/library/literal-escaper/README.md
+++ b/library/literal-escaper/README.md
@@ -1,4 +1,4 @@
 # literal-escaper
 
 This crate provides code to unescape string literals. It is used by `rustc_lexer`
-and `proc-macro`.
+and `proc_macro`.
diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs
index 57dd47f106089..b1de87a4b0819 100644
--- a/library/proc_macro/src/lib.rs
+++ b/library/proc_macro/src/lib.rs
@@ -61,6 +61,7 @@ use crate::escape::{EscapeOptions, escape_bytes};
 
 /// Errors returned when trying to retrieve a literal unescaped value.
 #[unstable(feature = "proc_macro_value", issue = "136652")]
+#[derive(Debug, PartialEq, Eq)]
 pub enum ConversionErrorKind {
     /// The literal failed to be escaped, take a look at [`EscapeError`] for more information.
     FailedToUnescape(EscapeError),
diff --git a/tests/ui/proc-macro/auxiliary/api/literal.rs b/tests/ui/proc-macro/auxiliary/api/literal.rs
index 7109340bb645b..941de1521ade9 100644
--- a/tests/ui/proc-macro/auxiliary/api/literal.rs
+++ b/tests/ui/proc-macro/auxiliary/api/literal.rs
@@ -1,10 +1,11 @@
 // ignore-tidy-linelength
 
-use proc_macro::Literal;
+use proc_macro::{ConversionErrorKind, Literal};
 
 pub fn test() {
     test_display_literal();
     test_parse_literal();
+    test_str_value_methods();
 }
 
 fn test_display_literal() {
@@ -81,3 +82,53 @@ fn test_parse_literal() {
     assert!("- 10".parse::<Literal>().is_err());
     assert!("-'x'".parse::<Literal>().is_err());
 }
+
+fn test_str_value_methods() {
+    // Testing `str_value`
+    let lit = "\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.str_value(), Ok("\n".to_string()));
+
+    let lit = "r#\"\n\"#".parse::<Literal>().unwrap();
+    assert_eq!(lit.str_value(), Ok("\n".to_string()));
+
+    let lit = "1".parse::<Literal>().unwrap();
+    assert_eq!(lit.str_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "b\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.str_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "c\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.str_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    // Testing `cstr_value`
+    let lit = "\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.cstr_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "r#\"\n\"#".parse::<Literal>().unwrap();
+    assert_eq!(lit.cstr_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "1".parse::<Literal>().unwrap();
+    assert_eq!(lit.cstr_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "b\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.cstr_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "c\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.cstr_value(), Ok(vec![b'\n', 0]));
+
+    // Testing `byte_str_value`
+    let lit = "\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.byte_str_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "r#\"\n\"#".parse::<Literal>().unwrap();
+    assert_eq!(lit.byte_str_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "1".parse::<Literal>().unwrap();
+    assert_eq!(lit.byte_str_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+
+    let lit = "b\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.byte_str_value(), Ok(vec![b'\n']));
+
+    let lit = "c\"\n\"".parse::<Literal>().unwrap();
+    assert_eq!(lit.byte_str_value(), Err(ConversionErrorKind::InvalidLiteralKind));
+}
diff --git a/tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs b/tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs
index abd667d8ce1d0..390d46852cd54 100644
--- a/tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs
+++ b/tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs
@@ -1,6 +1,7 @@
 //@ edition: 2021
 
 #![feature(proc_macro_span)]
+#![feature(proc_macro_value)]
 #![deny(dead_code)] // catch if a test function is never called
 
 extern crate proc_macro;

From 417bfe2125cfb371c3ce4b80437152e429b0e45c Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Sun, 16 Mar 2025 21:46:39 +0100
Subject: [PATCH 09/10] Exclude `literal-escaper` from `library` workspace

---
 library/Cargo.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/library/Cargo.toml b/library/Cargo.toml
index 1205f7c9ed6b5..1e2d996278e77 100644
--- a/library/Cargo.toml
+++ b/library/Cargo.toml
@@ -7,6 +7,7 @@ members = [
 ]
 
 exclude = [
+  "literal-escaper",
   # stdarch has its own Cargo workspace
   "stdarch",
   "windows_targets"

From 4394f94023526f81b1c45f6e74a17360f31522e4 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Mon, 17 Mar 2025 10:43:28 +0100
Subject: [PATCH 10/10] Only add `rustc_randomized_layouts` if the crate has it

---
 src/bootstrap/src/lib.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index bc146eda1d5eb..8db2b4b5b3adc 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -683,7 +683,7 @@ impl Build {
             features.push("llvm");
         }
         // keep in sync with `bootstrap/compile.rs:rustc_cargo_env`
-        if self.config.rust_randomize_layout {
+        if self.config.rust_randomize_layout && check("rustc_randomized_layouts") {
             features.push("rustc_randomized_layouts");
         }