Skip to content

Commit 1851b79

Browse files
Add _value methods to proc_macro lib
1 parent 9e57988 commit 1851b79

File tree

4 files changed

+133
-1
lines changed

4 files changed

+133
-1
lines changed

Cargo.lock

+10-1
Original file line numberDiff line numberDiff line change
@@ -2127,7 +2127,10 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
21272127

21282128
[[package]]
21292129
name = "literal-escaper"
2130-
version = "0.0.1"
2130+
version = "0.0.0"
2131+
dependencies = [
2132+
"rustc-std-workspace-std 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
2133+
]
21312134

21322135
[[package]]
21332136
name = "lld-wrapper"
@@ -3282,6 +3285,12 @@ version = "1.0.1"
32823285
name = "rustc-std-workspace-std"
32833286
version = "1.0.1"
32843287

3288+
[[package]]
3289+
name = "rustc-std-workspace-std"
3290+
version = "1.0.1"
3291+
source = "registry+https://github.com/rust-lang/crates.io-index"
3292+
checksum = "aba676a20abe46e5b0f1b0deae474aaaf31407e6c71147159890574599da04ef"
3293+
32853294
[[package]]
32863295
name = "rustc_abi"
32873296
version = "0.0.0"

library/Cargo.lock

+8
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ dependencies = [
158158
"rustc-std-workspace-core",
159159
]
160160

161+
[[package]]
162+
name = "literal-escaper"
163+
version = "0.0.0"
164+
dependencies = [
165+
"rustc-std-workspace-std",
166+
]
167+
161168
[[package]]
162169
name = "memchr"
163170
version = "2.7.4"
@@ -220,6 +227,7 @@ name = "proc_macro"
220227
version = "0.0.0"
221228
dependencies = [
222229
"core",
230+
"literal-escaper",
223231
"std",
224232
]
225233

library/proc_macro/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.0.0"
44
edition = "2021"
55

66
[dependencies]
7+
literal-escaper = { path = "../literal-escaper", features = ["rustc-dep-of-std"] }
78
std = { path = "../std" }
89
# Workaround: when documenting this crate rustdoc will try to load crate named
910
# `core` when resolving doc links. Without this line a different `core` will be

library/proc_macro/src/lib.rs

+114
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#![feature(restricted_std)]
2929
#![feature(rustc_attrs)]
3030
#![feature(extend_one)]
31+
#![feature(stmt_expr_attributes)]
3132
#![recursion_limit = "256"]
3233
#![allow(internal_features)]
3334
#![deny(ffi_unwind_calls)]
@@ -50,11 +51,23 @@ use std::{error, fmt};
5051

5152
#[unstable(feature = "proc_macro_diagnostic", issue = "54140")]
5253
pub use diagnostic::{Diagnostic, Level, MultiSpan};
54+
#[stable(feature = "proc_macro_value", since = "1.86.0")]
55+
pub use literal_escaper::EscapeError;
56+
use literal_escaper::{MixedUnit, Mode, byte_from_char, unescape_mixed, unescape_unicode};
5357
#[unstable(feature = "proc_macro_totokens", issue = "130977")]
5458
pub use to_tokens::ToTokens;
5559

5660
use crate::escape::{EscapeOptions, escape_bytes};
5761

62+
/// Errors returned when trying to retrieve a literal unescaped value.
63+
#[stable(feature = "proc_macro_value", since = "1.86.0")]
64+
pub enum ConversionErrorKind {
65+
/// The literal failed to be escaped, take a look at [`EscapeError`] for more information.
66+
FailedToUnescape(EscapeError),
67+
/// Trying to convert a literal with the wrong type.
68+
InvalidLiteralKind,
69+
}
70+
5871
/// Determines whether proc_macro has been made accessible to the currently
5972
/// running program.
6073
///
@@ -1450,6 +1463,107 @@ impl Literal {
14501463
}
14511464
})
14521465
}
1466+
1467+
/// Returns the unescaped string value if the current literal is a string or a string literal.
1468+
#[stable(feature = "proc_macro_value", since = "1.86.0")]
1469+
pub fn str_value(&self) -> Result<String, ConversionErrorKind> {
1470+
self.0.symbol.with(|symbol| match self.0.kind {
1471+
bridge::LitKind::Str => {
1472+
if symbol.contains('\\') {
1473+
let mut buf = String::with_capacity(symbol.len());
1474+
let mut error = None;
1475+
// Force-inlining here is aggressive but the closure is
1476+
// called on every char in the string, so it can be hot in
1477+
// programs with many long strings containing escapes.
1478+
unescape_unicode(
1479+
symbol,
1480+
Mode::Str,
1481+
&mut #[inline(always)]
1482+
|_, c| match c {
1483+
Ok(c) => buf.push(c),
1484+
Err(err) => {
1485+
if err.is_fatal() {
1486+
error = Some(ConversionErrorKind::FailedToUnescape(err));
1487+
}
1488+
}
1489+
},
1490+
);
1491+
if let Some(error) = error { Err(error) } else { Ok(buf) }
1492+
} else {
1493+
Ok(symbol.to_string())
1494+
}
1495+
}
1496+
bridge::LitKind::StrRaw(_) => Ok(symbol.to_string()),
1497+
_ => Err(ConversionErrorKind::InvalidLiteralKind),
1498+
})
1499+
}
1500+
1501+
/// Returns the unescaped string value if the current literal is a c-string or a c-string
1502+
/// literal.
1503+
#[stable(feature = "proc_macro_value", since = "1.86.0")]
1504+
pub fn cstr_value(&self) -> Result<Vec<u8>, ConversionErrorKind> {
1505+
self.0.symbol.with(|symbol| match self.0.kind {
1506+
bridge::LitKind::CStr => {
1507+
let mut error = None;
1508+
let mut buf = Vec::with_capacity(symbol.len());
1509+
1510+
unescape_mixed(symbol, Mode::CStr, &mut |_span, c| match c {
1511+
Ok(MixedUnit::Char(c)) => {
1512+
buf.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes())
1513+
}
1514+
Ok(MixedUnit::HighByte(b)) => buf.push(b),
1515+
Err(err) => {
1516+
if err.is_fatal() {
1517+
error = Some(ConversionErrorKind::FailedToUnescape(err));
1518+
}
1519+
}
1520+
});
1521+
if let Some(error) = error {
1522+
Err(error)
1523+
} else {
1524+
buf.push(0);
1525+
Ok(buf)
1526+
}
1527+
}
1528+
bridge::LitKind::CStrRaw(_) => {
1529+
// Raw strings have no escapes so we can convert the symbol
1530+
// directly to a `Lrc<u8>` after appending the terminating NUL
1531+
// char.
1532+
let mut buf = symbol.to_owned().into_bytes();
1533+
buf.push(0);
1534+
Ok(buf)
1535+
}
1536+
_ => Err(ConversionErrorKind::InvalidLiteralKind),
1537+
})
1538+
}
1539+
1540+
/// Returns the unescaped string value if the current literal is a byte string or a byte string
1541+
/// literal.
1542+
#[stable(feature = "proc_macro_value", since = "1.86.0")]
1543+
pub fn byte_str_value(&self) -> Result<Vec<u8>, ConversionErrorKind> {
1544+
self.0.symbol.with(|symbol| match self.0.kind {
1545+
bridge::LitKind::ByteStr => {
1546+
let mut buf = Vec::with_capacity(symbol.len());
1547+
let mut error = None;
1548+
1549+
unescape_unicode(symbol, Mode::ByteStr, &mut |_, c| match c {
1550+
Ok(c) => buf.push(byte_from_char(c)),
1551+
Err(err) => {
1552+
if err.is_fatal() {
1553+
error = Some(ConversionErrorKind::FailedToUnescape(err));
1554+
}
1555+
}
1556+
});
1557+
if let Some(error) = error { Err(error) } else { Ok(buf) }
1558+
}
1559+
bridge::LitKind::ByteStrRaw(_) => {
1560+
// Raw strings have no escapes so we can convert the symbol
1561+
// directly to a `Lrc<u8>`.
1562+
Ok(symbol.to_owned().into_bytes())
1563+
}
1564+
_ => Err(ConversionErrorKind::InvalidLiteralKind),
1565+
})
1566+
}
14531567
}
14541568

14551569
/// Parse a single literal from its stringified representation.

0 commit comments

Comments
 (0)