Skip to content

Commit 4c039a8

Browse files
committed
Add Literal methods from proc_macro_value feature
1 parent 885fde9 commit 4c039a8

File tree

2 files changed

+146
-3
lines changed

2 files changed

+146
-3
lines changed

src/lib.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,14 @@ mod imp;
164164
#[cfg(span_locations)]
165165
mod location;
166166

167+
#[cfg(procmacro2_semver_exempt)]
168+
#[allow(dead_code)]
169+
mod rustc_literal_escaper;
170+
167171
use crate::extra::DelimSpan;
168172
use crate::marker::{ProcMacroAutoTraits, MARKER};
173+
#[cfg(procmacro2_semver_exempt)]
174+
use crate::rustc_literal_escaper::MixedUnit;
169175
use core::cmp::Ordering;
170176
use core::fmt::{self, Debug, Display};
171177
use core::hash::{Hash, Hasher};
@@ -182,6 +188,10 @@ use std::path::PathBuf;
182188
#[cfg_attr(docsrs, doc(cfg(feature = "span-locations")))]
183189
pub use crate::location::LineColumn;
184190

191+
#[cfg(procmacro2_semver_exempt)]
192+
#[cfg_attr(docsrs, doc(cfg(procmacro2_semver_exempt)))]
193+
pub use crate::rustc_literal_escaper::EscapeError;
194+
185195
/// An abstract stream of tokens, or more concretely a sequence of token trees.
186196
///
187197
/// This type provides interfaces for iterating over token trees and for
@@ -1263,6 +1273,112 @@ impl Literal {
12631273
self.inner.subspan(range).map(Span::_new)
12641274
}
12651275

1276+
/// Returns the unescaped string value if this is a string literal.
1277+
#[cfg(procmacro2_semver_exempt)]
1278+
pub fn str_value(&self) -> Result<String, ConversionErrorKind> {
1279+
let repr = self.to_string();
1280+
1281+
if repr.starts_with('"') && repr[1..].ends_with('"') {
1282+
let quoted = &repr[1..repr.len() - 1];
1283+
let mut value = String::with_capacity(quoted.len());
1284+
let mut error = None;
1285+
rustc_literal_escaper::unescape_str(quoted, |_range, res| match res {
1286+
Ok(ch) => value.push(ch),
1287+
Err(err) => {
1288+
if err.is_fatal() {
1289+
error = Some(ConversionErrorKind::FailedToUnescape(err));
1290+
}
1291+
}
1292+
});
1293+
return match error {
1294+
Some(error) => Err(error),
1295+
None => Ok(value),
1296+
};
1297+
}
1298+
1299+
if repr.starts_with('r') {
1300+
if let Some(raw) = get_raw(&repr[1..]) {
1301+
return Ok(raw.to_owned());
1302+
}
1303+
}
1304+
1305+
Err(ConversionErrorKind::InvalidLiteralKind)
1306+
}
1307+
1308+
/// Returns the unescaped string value (including nul terminator) if this is
1309+
/// a c-string literal.
1310+
#[cfg(procmacro2_semver_exempt)]
1311+
pub fn cstr_value(&self) -> Result<Vec<u8>, ConversionErrorKind> {
1312+
let repr = self.to_string();
1313+
1314+
if repr.starts_with("c\"") && repr[2..].ends_with('"') {
1315+
let quoted = &repr[2..repr.len() - 1];
1316+
let mut value = Vec::with_capacity(quoted.len());
1317+
let mut error = None;
1318+
rustc_literal_escaper::unescape_c_str(quoted, |_range, res| match res {
1319+
Ok(MixedUnit::Char(ch)) => {
1320+
value.extend_from_slice(ch.get().encode_utf8(&mut [0; 4]).as_bytes());
1321+
}
1322+
Ok(MixedUnit::HighByte(byte)) => value.push(byte.get()),
1323+
Err(err) => {
1324+
if err.is_fatal() {
1325+
error = Some(ConversionErrorKind::FailedToUnescape(err));
1326+
}
1327+
}
1328+
});
1329+
return match error {
1330+
Some(error) => Err(error),
1331+
None => {
1332+
value.push(b'\0');
1333+
Ok(value)
1334+
}
1335+
};
1336+
}
1337+
1338+
if repr.starts_with("cr") {
1339+
if let Some(raw) = get_raw(&repr[2..]) {
1340+
let mut value = Vec::with_capacity(raw.len() + 1);
1341+
value.extend_from_slice(raw.as_bytes());
1342+
value.push(b'\0');
1343+
return Ok(value);
1344+
}
1345+
}
1346+
1347+
Err(ConversionErrorKind::InvalidLiteralKind)
1348+
}
1349+
1350+
/// Returns the unescaped string value if this is a byte string literal.
1351+
#[cfg(procmacro2_semver_exempt)]
1352+
pub fn byte_str_value(&self) -> Result<Vec<u8>, ConversionErrorKind> {
1353+
let repr = self.to_string();
1354+
1355+
if repr.starts_with("b\"") && repr[2..].ends_with('"') {
1356+
let quoted = &repr[2..repr.len() - 1];
1357+
let mut value = Vec::with_capacity(quoted.len());
1358+
let mut error = None;
1359+
rustc_literal_escaper::unescape_byte_str(quoted, |_range, res| match res {
1360+
Ok(byte) => value.push(byte),
1361+
Err(err) => {
1362+
if err.is_fatal() {
1363+
error = Some(ConversionErrorKind::FailedToUnescape(err));
1364+
}
1365+
}
1366+
});
1367+
return match error {
1368+
Some(error) => Err(error),
1369+
None => Ok(value),
1370+
};
1371+
}
1372+
1373+
if repr.starts_with("br") {
1374+
if let Some(raw) = get_raw(&repr[2..]) {
1375+
return Ok(raw.as_bytes().to_owned());
1376+
}
1377+
}
1378+
1379+
Err(ConversionErrorKind::InvalidLiteralKind)
1380+
}
1381+
12661382
// Intended for the `quote!` macro to use when constructing a proc-macro2
12671383
// token out of a macro_rules $:literal token, which is already known to be
12681384
// a valid literal. This avoids reparsing/validating the literal's string
@@ -1299,6 +1415,33 @@ impl Display for Literal {
12991415
}
13001416
}
13011417

1418+
/// Error when retrieving a string literal's unescaped value.
1419+
#[cfg(procmacro2_semver_exempt)]
1420+
#[derive(Debug, PartialEq, Eq)]
1421+
pub enum ConversionErrorKind {
1422+
/// The literal is of the right string kind, but its contents are malformed
1423+
/// in a way that cannot be unescaped to a value.
1424+
FailedToUnescape(EscapeError),
1425+
/// The literal is not of the string kind whose value was requested, for
1426+
/// example byte string vs UTF-8 string.
1427+
InvalidLiteralKind,
1428+
}
1429+
1430+
// ###"..."### -> ...
1431+
#[cfg(procmacro2_semver_exempt)]
1432+
fn get_raw(repr: &str) -> Option<&str> {
1433+
let pounds = repr.len() - repr.trim_start_matches('#').len();
1434+
if repr.len() >= pounds + 1 + 1 + pounds
1435+
&& repr[pounds..].starts_with('"')
1436+
&& repr.trim_end_matches('#').len() + pounds == repr.len()
1437+
&& repr[..repr.len() - pounds].ends_with('"')
1438+
{
1439+
Some(&repr[pounds + 1..repr.len() - pounds - 1])
1440+
} else {
1441+
None
1442+
}
1443+
}
1444+
13021445
/// Public implementation details for the `TokenStream` type, such as iterators.
13031446
pub mod token_stream {
13041447
use crate::marker::{ProcMacroAutoTraits, MARKER};

src/rustc_literal_escaper.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Vendored from rustc-literal-escaper v0.0.5
2+
// https://github.com/rust-lang/literal-escaper/tree/v0.0.5
3+
14
//! Utilities for validating (raw) string, char, and byte literals and
25
//! turning escape sequences into the values they represent.
36
@@ -6,9 +9,6 @@ use std::num::NonZero;
69
use std::ops::Range;
710
use std::str::Chars;
811

9-
#[cfg(test)]
10-
mod tests;
11-
1212
/// Errors and warnings that can occur during string, char, and byte unescaping.
1313
///
1414
/// Mostly relating to malformed escape sequences, but also a few other problems.

0 commit comments

Comments
 (0)