Skip to content

Commit ae03536

Browse files
Add _value API for number literals in proc-macro
1 parent fda6d37 commit ae03536

3 files changed

Lines changed: 172 additions & 0 deletions

File tree

library/proc_macro/src/lib.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ mod escape;
4545
mod to_tokens;
4646

4747
use core::ops::BitOr;
48+
use std::borrow::Cow;
4849
use std::ffi::CStr;
4950
use std::ops::{Range, RangeBounds};
5051
use std::path::PathBuf;
@@ -1183,6 +1184,62 @@ macro_rules! unsuffixed_int_literals {
11831184
)*)
11841185
}
11851186

1187+
macro_rules! integer_values {
1188+
($($nb:ident => $fn_name:ident,)+) => {
1189+
$(
1190+
#[doc = concat!(
1191+
"Returns the unescaped `",
1192+
stringify!($nb),
1193+
"` value if the literal is a `",
1194+
stringify!($nb),
1195+
"` or if it's an \"unmarked\" integer which doesn't overflow.")]
1196+
#[unstable(feature = "proc_macro_value", issue = "136652")]
1197+
pub fn $fn_name(&self) -> Result<$nb, ConversionErrorKind> {
1198+
if self.0.kind != bridge::LitKind::Integer {
1199+
return Err(ConversionErrorKind::InvalidLiteralKind);
1200+
}
1201+
self.with_symbol_and_suffix(|symbol, suffix| {
1202+
match suffix {
1203+
stringify!($nb) | "" => {
1204+
let (number, base) = parse_number(symbol);
1205+
$nb::from_str_radix(&number, base as u32).map_err(|_| ConversionErrorKind::InvalidLiteralKind)
1206+
}
1207+
_ => Err(ConversionErrorKind::InvalidLiteralKind),
1208+
}
1209+
})
1210+
}
1211+
)+
1212+
}
1213+
}
1214+
1215+
macro_rules! float_values {
1216+
($($nb:ident => $fn_name:ident,)+) => {
1217+
$(
1218+
#[doc = concat!(
1219+
"Returns the unescaped `",
1220+
stringify!($nb),
1221+
"` value if the literal is a `",
1222+
stringify!($nb),
1223+
"` or if it's an \"unmarked\" float which doesn't overflow.")]
1224+
#[unstable(feature = "proc_macro_value", issue = "136652")]
1225+
pub fn $fn_name(&self) -> Result<$nb, ConversionErrorKind> {
1226+
if self.0.kind != bridge::LitKind::Float {
1227+
return Err(ConversionErrorKind::InvalidLiteralKind);
1228+
}
1229+
self.with_symbol_and_suffix(|symbol, suffix| {
1230+
match suffix {
1231+
stringify!($nb) | "" => {
1232+
let number = only_digits(symbol);
1233+
$nb::from_str(&number).map_err(|_| ConversionErrorKind::InvalidLiteralKind)
1234+
}
1235+
_ => Err(ConversionErrorKind::InvalidLiteralKind),
1236+
}
1237+
})
1238+
}
1239+
)+
1240+
}
1241+
}
1242+
11861243
impl Literal {
11871244
fn new(kind: bridge::LitKind, value: &str, suffix: Option<&str>) -> Self {
11881245
Literal(bridge::Literal {
@@ -1578,6 +1635,98 @@ impl Literal {
15781635
_ => Err(ConversionErrorKind::InvalidLiteralKind),
15791636
})
15801637
}
1638+
1639+
integer_values! {
1640+
u8 => u8_value,
1641+
u16 => u16_value,
1642+
u32 => u32_value,
1643+
u64 => u64_value,
1644+
u128 => u128_value,
1645+
i8 => i8_value,
1646+
i16 => i16_value,
1647+
i32 => i32_value,
1648+
i64 => i64_value,
1649+
i128 => i128_value,
1650+
}
1651+
1652+
float_values! {
1653+
// FIXME: To be uncommented when `f16` is stable.
1654+
// f16 => f16_value,
1655+
f32 => f32_value,
1656+
f64 => f64_value,
1657+
// FIXME: `f128` doesn't implement `FromStr` for the moment so we cannot obtain it from
1658+
// a `&str`. To be uncommented when it's added.
1659+
// f128 => f128_value,
1660+
}
1661+
}
1662+
1663+
#[repr(u32)]
1664+
#[derive(PartialEq, Eq)]
1665+
enum Base {
1666+
Decimal = 10,
1667+
Binary = 2,
1668+
Octal = 8,
1669+
Hexadecimal = 16,
1670+
}
1671+
1672+
fn parse_number(value: &str) -> (String, Base) {
1673+
let mut iter = value.as_bytes().iter().copied();
1674+
let Some(first_digit) = iter.next() else {
1675+
return ("0".into(), Base::Decimal);
1676+
};
1677+
let Some(second_digit) = iter.next() else {
1678+
let mut output = String::with_capacity(1);
1679+
output.push(first_digit as char);
1680+
return (output, Base::Decimal);
1681+
};
1682+
1683+
let mut base = Base::Decimal;
1684+
if first_digit == b'0' {
1685+
// Attempt to parse encoding base.
1686+
match second_digit {
1687+
b'b' => {
1688+
base = Base::Binary;
1689+
}
1690+
b'o' => {
1691+
base = Base::Octal;
1692+
}
1693+
b'x' => {
1694+
base = Base::Hexadecimal;
1695+
}
1696+
_ => {}
1697+
}
1698+
}
1699+
1700+
let mut output = if base == Base::Decimal {
1701+
let mut output = String::with_capacity(value.len());
1702+
output.push(first_digit as char);
1703+
output.push(second_digit as char);
1704+
output
1705+
} else {
1706+
String::with_capacity(value.len() - 2)
1707+
};
1708+
1709+
for c in iter {
1710+
if c != b'_' {
1711+
output.push(c as char);
1712+
}
1713+
}
1714+
1715+
(output, base)
1716+
}
1717+
1718+
fn only_digits(value_s: &str) -> Cow<'_, str> {
1719+
let value = value_s.as_bytes();
1720+
if value.iter().copied().all(|c| c != b'_' && c != b'f') {
1721+
return Cow::Borrowed(value_s);
1722+
}
1723+
let mut output = String::with_capacity(value.len());
1724+
for c in value.iter().copied() {
1725+
if c != b'_' {
1726+
output.push(c as char);
1727+
}
1728+
}
1729+
Cow::Owned(output)
15811730
}
15821731

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

tests/ui/proc-macro/auxiliary/api/literal.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use proc_macro::Literal;
55
pub fn test() {
66
test_display_literal();
77
test_parse_literal();
8+
test_literal_value();
89
}
910

1011
fn test_display_literal() {
@@ -81,3 +82,24 @@ fn test_parse_literal() {
8182
assert!("- 10".parse::<Literal>().is_err());
8283
assert!("-'x'".parse::<Literal>().is_err());
8384
}
85+
86+
fn test_literal_value() {
87+
assert!(Literal::u32_suffixed(10).u8_value().is_err());
88+
assert!(Literal::u32_suffixed(10).f32_value().is_err());
89+
assert!(Literal::u32_suffixed(10).u64_value().is_err());
90+
assert_eq!(Literal::u32_suffixed(10).u32_value(), Ok(10u32));
91+
assert_eq!(Literal::u32_unsuffixed(10).u8_value(), Ok(10u8));
92+
assert!(Literal::u32_unsuffixed(400).u8_value().is_err());
93+
assert_eq!(Literal::u32_unsuffixed(400).u16_value(), Ok(400u16));
94+
assert!(Literal::i32_unsuffixed(-400).u16_value().is_err());
95+
assert_eq!(Literal::i32_unsuffixed(-400).i16_value(), Ok(-400i16));
96+
assert_eq!(Literal::u32_unsuffixed(0xff).u16_value(), Ok(0xffu16));
97+
assert_eq!(Literal::u32_unsuffixed(0b11).u16_value(), Ok(0b11u16));
98+
assert_eq!(Literal::u32_unsuffixed(0o11).u16_value(), Ok(0o11u16));
99+
100+
assert!(Literal::f32_suffixed(10.).u8_value().is_err());
101+
assert!(Literal::f32_suffixed(10.).f64_value().is_err());
102+
assert_eq!(Literal::f32_suffixed(10.).f32_value().map(|f| f.to_string()), Ok("10".to_string()));
103+
assert_eq!(Literal::f32_unsuffixed(10.).f32_value().map(|f| f.to_string()), Ok("10".to_string()));
104+
assert_eq!(Literal::f32_unsuffixed(10.).f64_value().map(|f| f.to_string()), Ok("10".to_string()));
105+
}

tests/ui/proc-macro/auxiliary/api/proc_macro_api_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//@ edition: 2021
22

33
#![feature(proc_macro_span)]
4+
#![feature(proc_macro_value)]
45
#![deny(dead_code)] // catch if a test function is never called
56

67
extern crate proc_macro;

0 commit comments

Comments
 (0)