Skip to content

Commit 3855b8b

Browse files
committed
Make {integer}::from_str_radix constant
1 parent 7e0ed43 commit 3855b8b

File tree

10 files changed

+239
-166
lines changed

10 files changed

+239
-166
lines changed

library/core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
#![feature(const_heap)]
138138
#![feature(const_hint_assert_unchecked)]
139139
#![feature(const_index_range_slice_index)]
140+
#![feature(const_int_from_str)]
140141
#![feature(const_intrinsic_copy)]
141142
#![feature(const_intrinsic_forget)]
142143
#![feature(const_ipv4)]

library/core/src/num/error.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,9 @@ pub enum IntErrorKind {
113113
impl ParseIntError {
114114
/// Outputs the detailed cause of parsing an integer failing.
115115
#[must_use]
116+
#[rustc_const_unstable(feature = "const_int_from_str", issue = "59133")]
116117
#[stable(feature = "int_error_matching", since = "1.55.0")]
117-
pub fn kind(&self) -> &IntErrorKind {
118+
pub const fn kind(&self) -> &IntErrorKind {
118119
&self.kind
119120
}
120121
}

library/core/src/num/int_macros.rs

-26
Original file line numberDiff line numberDiff line change
@@ -60,32 +60,6 @@ macro_rules! int_impl {
6060
#[stable(feature = "int_bits_const", since = "1.53.0")]
6161
pub const BITS: u32 = <$UnsignedT>::BITS;
6262

63-
/// Converts a string slice in a given base to an integer.
64-
///
65-
/// The string is expected to be an optional `+` or `-` sign followed by digits.
66-
/// Leading and trailing whitespace represent an error. Digits are a subset of these characters,
67-
/// depending on `radix`:
68-
///
69-
/// * `0-9`
70-
/// * `a-z`
71-
/// * `A-Z`
72-
///
73-
/// # Panics
74-
///
75-
/// This function panics if `radix` is not in the range from 2 to 36.
76-
///
77-
/// # Examples
78-
///
79-
/// Basic usage:
80-
///
81-
/// ```
82-
#[doc = concat!("assert_eq!(", stringify!($SelfT), "::from_str_radix(\"A\", 16), Ok(10));")]
83-
/// ```
84-
#[stable(feature = "rust1", since = "1.0.0")]
85-
pub fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseIntError> {
86-
from_str_radix(src, radix)
87-
}
88-
8963
/// Returns the number of ones in the binary representation of `self`.
9064
///
9165
/// # Examples

library/core/src/num/mod.rs

+183-110
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::ascii;
66
use crate::hint;
77
use crate::intrinsics;
88
use crate::mem;
9-
use crate::ops::{Add, Mul, Sub};
109
use crate::str::FromStr;
1110

1211
// Used because the `?` operator is not allowed in a const context.
@@ -1386,144 +1385,218 @@ pub enum FpCategory {
13861385
Normal,
13871386
}
13881387

1389-
#[doc(hidden)]
1390-
trait FromStrRadixHelper:
1391-
PartialOrd + Copy + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self>
1392-
{
1393-
const MIN: Self;
1394-
fn from_u32(u: u32) -> Self;
1395-
fn checked_mul(&self, other: u32) -> Option<Self>;
1396-
fn checked_sub(&self, other: u32) -> Option<Self>;
1397-
fn checked_add(&self, other: u32) -> Option<Self>;
1398-
}
1399-
14001388
macro_rules! from_str_radix_int_impl {
14011389
($($t:ty)*) => {$(
14021390
#[stable(feature = "rust1", since = "1.0.0")]
14031391
impl FromStr for $t {
14041392
type Err = ParseIntError;
14051393
fn from_str(src: &str) -> Result<Self, ParseIntError> {
1406-
from_str_radix(src, 10)
1394+
<$t>::from_str_radix(src, 10)
14071395
}
14081396
}
14091397
)*}
14101398
}
14111399
from_str_radix_int_impl! { isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 }
14121400

1413-
macro_rules! impl_helper_for {
1414-
($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
1415-
const MIN: Self = Self::MIN;
1416-
#[inline]
1417-
fn from_u32(u: u32) -> Self { u as Self }
1418-
#[inline]
1419-
fn checked_mul(&self, other: u32) -> Option<Self> {
1420-
Self::checked_mul(*self, other as Self)
1421-
}
1422-
#[inline]
1423-
fn checked_sub(&self, other: u32) -> Option<Self> {
1424-
Self::checked_sub(*self, other as Self)
1425-
}
1426-
#[inline]
1427-
fn checked_add(&self, other: u32) -> Option<Self> {
1428-
Self::checked_add(*self, other as Self)
1429-
}
1430-
})*)
1431-
}
1432-
impl_helper_for! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
1433-
14341401
/// Determines if a string of text of that length of that radix could be guaranteed to be
14351402
/// stored in the given type T.
14361403
/// Note that if the radix is known to the compiler, it is just the check of digits.len that
14371404
/// is done at runtime.
14381405
#[doc(hidden)]
14391406
#[inline(always)]
14401407
#[unstable(issue = "none", feature = "std_internals")]
1441-
pub fn can_not_overflow<T>(radix: u32, is_signed_ty: bool, digits: &[u8]) -> bool {
1408+
pub const fn can_not_overflow<T>(radix: u32, is_signed_ty: bool, digits: &[u8]) -> bool {
14421409
radix <= 16 && digits.len() <= mem::size_of::<T>() * 2 - is_signed_ty as usize
14431410
}
14441411

1445-
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
1446-
use self::IntErrorKind::*;
1447-
use self::ParseIntError as PIE;
1412+
#[track_caller]
1413+
const fn from_str_radix_panic_ct(_radix: u32) -> ! {
1414+
panic!("from_str_radix_int: must lie in the range `[2, 36]`");
1415+
}
14481416

1449-
assert!(
1450-
(2..=36).contains(&radix),
1451-
"from_str_radix_int: must lie in the range `[2, 36]` - found {}",
1452-
radix
1453-
);
1417+
#[track_caller]
1418+
fn from_str_radix_panic_rt(radix: u32) -> ! {
1419+
panic!("from_str_radix_int: must lie in the range `[2, 36]` - found {}", radix);
1420+
}
14541421

1455-
if src.is_empty() {
1456-
return Err(PIE { kind: Empty });
1422+
#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))]
1423+
#[cfg_attr(feature = "panic_immediate_abort", inline)]
1424+
#[cold]
1425+
#[track_caller]
1426+
const fn from_str_radix_assert(radix: u32) {
1427+
if 2 > radix || radix > 36 {
1428+
// The only difference between these two functions is their panic message.
1429+
intrinsics::const_eval_select((radix,), from_str_radix_panic_ct, from_str_radix_panic_rt);
14571430
}
1431+
}
14581432

1459-
let is_signed_ty = T::from_u32(0) > T::MIN;
1460-
1461-
// all valid digits are ascii, so we will just iterate over the utf8 bytes
1462-
// and cast them to chars. .to_digit() will safely return None for anything
1463-
// other than a valid ascii digit for the given radix, including the first-byte
1464-
// of multi-byte sequences
1465-
let src = src.as_bytes();
1433+
macro_rules! from_str_radix {
1434+
($($int_ty:ty)+) => {$(
1435+
impl $int_ty {
1436+
/// Converts a string slice in a given base to an integer.
1437+
///
1438+
/// The string is expected to be an optional `+` sign
1439+
/// followed by digits.
1440+
/// Leading and trailing whitespace represent an error.
1441+
/// Digits are a subset of these characters, depending on `radix`:
1442+
///
1443+
/// * `0-9`
1444+
/// * `a-z`
1445+
/// * `A-Z`
1446+
///
1447+
/// # Panics
1448+
///
1449+
/// This function panics if `radix` is not in the range from 2 to 36.
1450+
///
1451+
/// # Examples
1452+
///
1453+
/// Basic usage:
1454+
///
1455+
/// ```
1456+
#[doc = concat!("assert_eq!(", stringify!($int_ty), "::from_str_radix(\"A\", 16), Ok(10));")]
1457+
/// ```
1458+
#[stable(feature = "rust1", since = "1.0.0")]
1459+
#[rustc_const_unstable(feature = "const_int_from_str", issue = "59133")]
1460+
pub const fn from_str_radix(src: &str, radix: u32) -> Result<$int_ty, ParseIntError> {
1461+
use self::IntErrorKind::*;
1462+
use self::ParseIntError as PIE;
1463+
1464+
from_str_radix_assert(radix);
1465+
1466+
if src.is_empty() {
1467+
return Err(PIE { kind: Empty });
1468+
}
14661469

1467-
let (is_positive, digits) = match src[0] {
1468-
b'+' | b'-' if src[1..].is_empty() => {
1469-
return Err(PIE { kind: InvalidDigit });
1470-
}
1471-
b'+' => (true, &src[1..]),
1472-
b'-' if is_signed_ty => (false, &src[1..]),
1473-
_ => (true, src),
1474-
};
1470+
#[allow(unused_comparisons)]
1471+
let is_signed_ty = 0 > <$int_ty>::MIN;
1472+
1473+
// all valid digits are ascii, so we will just iterate over the utf8 bytes
1474+
// and cast them to chars. .to_digit() will safely return None for anything
1475+
// other than a valid ascii digit for the given radix, including the first-byte
1476+
// of multi-byte sequences
1477+
let src = src.as_bytes();
1478+
1479+
let (is_positive, mut digits) = match src {
1480+
[b'+' | b'-'] => {
1481+
return Err(PIE { kind: InvalidDigit });
1482+
}
1483+
[b'+', rest @ ..] => (true, rest),
1484+
[b'-', rest @ ..] if is_signed_ty => (false, rest),
1485+
_ => (true, src),
1486+
};
1487+
1488+
let mut result = 0;
1489+
1490+
macro_rules! unwrap_or_PIE {
1491+
($option:expr, $kind:ident) => {
1492+
match $option {
1493+
Some(value) => value,
1494+
None => return Err(PIE { kind: $kind }),
1495+
}
1496+
};
1497+
}
14751498

1476-
let mut result = T::from_u32(0);
1477-
1478-
if can_not_overflow::<T>(radix, is_signed_ty, digits) {
1479-
// If the len of the str is short compared to the range of the type
1480-
// we are parsing into, then we can be certain that an overflow will not occur.
1481-
// This bound is when `radix.pow(digits.len()) - 1 <= T::MAX` but the condition
1482-
// above is a faster (conservative) approximation of this.
1483-
//
1484-
// Consider radix 16 as it has the highest information density per digit and will thus overflow the earliest:
1485-
// `u8::MAX` is `ff` - any str of len 2 is guaranteed to not overflow.
1486-
// `i8::MAX` is `7f` - only a str of len 1 is guaranteed to not overflow.
1487-
macro_rules! run_unchecked_loop {
1488-
($unchecked_additive_op:expr) => {
1489-
for &c in digits {
1490-
result = result * T::from_u32(radix);
1491-
let x = (c as char).to_digit(radix).ok_or(PIE { kind: InvalidDigit })?;
1492-
result = $unchecked_additive_op(result, T::from_u32(x));
1499+
if can_not_overflow::<$int_ty>(radix, is_signed_ty, digits) {
1500+
// If the len of the str is short compared to the range of the type
1501+
// we are parsing into, then we can be certain that an overflow will not occur.
1502+
// This bound is when `radix.pow(digits.len()) - 1 <= T::MAX` but the condition
1503+
// above is a faster (conservative) approximation of this.
1504+
//
1505+
// Consider radix 16 as it has the highest information density per digit and will thus overflow the earliest:
1506+
// `u8::MAX` is `ff` - any str of len 2 is guaranteed to not overflow.
1507+
// `i8::MAX` is `7f` - only a str of len 1 is guaranteed to not overflow.
1508+
macro_rules! run_unchecked_loop {
1509+
($unchecked_additive_op:tt) => {{
1510+
while let [c, rest @ ..] = digits {
1511+
result = result * (radix as $int_ty);
1512+
let x = unwrap_or_PIE!((*c as char).to_digit(radix), InvalidDigit);
1513+
result = result $unchecked_additive_op (x as $int_ty);
1514+
digits = rest;
1515+
}
1516+
}};
1517+
}
1518+
if is_positive {
1519+
run_unchecked_loop!(+)
1520+
} else {
1521+
run_unchecked_loop!(-)
1522+
};
1523+
} else {
1524+
macro_rules! run_checked_loop {
1525+
($checked_additive_op:ident, $overflow_err:ident) => {{
1526+
while let [c, rest @ ..] = digits {
1527+
// When `radix` is passed in as a literal, rather than doing a slow `imul`
1528+
// the compiler can use shifts if `radix` can be expressed as a
1529+
// sum of powers of 2 (x*10 can be written as x*8 + x*2).
1530+
// When the compiler can't use these optimisations,
1531+
// the latency of the multiplication can be hidden by issuing it
1532+
// before the result is needed to improve performance on
1533+
// modern out-of-order CPU as multiplication here is slower
1534+
// than the other instructions, we can get the end result faster
1535+
// doing multiplication first and let the CPU spends other cycles
1536+
// doing other computation and get multiplication result later.
1537+
let mul = result.checked_mul(radix as $int_ty);
1538+
let x = unwrap_or_PIE!((*c as char).to_digit(radix), InvalidDigit) as $int_ty;
1539+
result = unwrap_or_PIE!(mul, $overflow_err);
1540+
result = unwrap_or_PIE!(<$int_ty>::$checked_additive_op(result, x), $overflow_err);
1541+
digits = rest;
1542+
}
1543+
}};
1544+
}
1545+
if is_positive {
1546+
run_checked_loop!(checked_add, PosOverflow)
1547+
} else {
1548+
run_checked_loop!(checked_sub, NegOverflow)
1549+
};
14931550
}
1494-
};
1551+
Ok(result)
1552+
}
14951553
}
1496-
if is_positive {
1497-
run_unchecked_loop!(<T as core::ops::Add>::add)
1498-
} else {
1499-
run_unchecked_loop!(<T as core::ops::Sub>::sub)
1500-
};
1501-
} else {
1502-
macro_rules! run_checked_loop {
1503-
($checked_additive_op:ident, $overflow_err:expr) => {
1504-
for &c in digits {
1505-
// When `radix` is passed in as a literal, rather than doing a slow `imul`
1506-
// the compiler can use shifts if `radix` can be expressed as a
1507-
// sum of powers of 2 (x*10 can be written as x*8 + x*2).
1508-
// When the compiler can't use these optimisations,
1509-
// the latency of the multiplication can be hidden by issuing it
1510-
// before the result is needed to improve performance on
1511-
// modern out-of-order CPU as multiplication here is slower
1512-
// than the other instructions, we can get the end result faster
1513-
// doing multiplication first and let the CPU spends other cycles
1514-
// doing other computation and get multiplication result later.
1515-
let mul = result.checked_mul(radix);
1516-
let x = (c as char).to_digit(radix).ok_or(PIE { kind: InvalidDigit })?;
1517-
result = mul.ok_or_else($overflow_err)?;
1518-
result = T::$checked_additive_op(&result, x).ok_or_else($overflow_err)?;
1519-
}
1520-
};
1554+
)+}
1555+
}
1556+
1557+
from_str_radix! { i8 u8 i16 u16 i32 u32 i64 u64 i128 u128 }
1558+
1559+
// Re-use the relevant implementation of from_str_radix for isize and usize to avoid outputting two
1560+
// identical functions.
1561+
macro_rules! from_str_radix_size_impl {
1562+
($($t:ident $size:ty),*) => {$(
1563+
impl $size {
1564+
/// Converts a string slice in a given base to an integer.
1565+
///
1566+
/// The string is expected to be an optional `+` sign
1567+
/// followed by digits.
1568+
/// Leading and trailing whitespace represent an error.
1569+
/// Digits are a subset of these characters, depending on `radix`:
1570+
///
1571+
/// * `0-9`
1572+
/// * `a-z`
1573+
/// * `A-Z`
1574+
///
1575+
/// # Panics
1576+
///
1577+
/// This function panics if `radix` is not in the range from 2 to 36.
1578+
///
1579+
/// # Examples
1580+
///
1581+
/// Basic usage:
1582+
///
1583+
/// ```
1584+
#[doc = concat!("assert_eq!(", stringify!($size), "::from_str_radix(\"A\", 16), Ok(10));")]
1585+
/// ```
1586+
#[stable(feature = "rust1", since = "1.0.0")]
1587+
#[rustc_const_unstable(feature = "const_int_from_str", issue = "59133")]
1588+
pub const fn from_str_radix(src: &str, radix: u32) -> Result<$size, ParseIntError> {
1589+
match <$t>::from_str_radix(src, radix) {
1590+
Ok(x) => Ok(x as $size),
1591+
Err(e) => Err(e),
1592+
}
15211593
}
1522-
if is_positive {
1523-
run_checked_loop!(checked_add, || PIE { kind: PosOverflow })
1524-
} else {
1525-
run_checked_loop!(checked_sub, || PIE { kind: NegOverflow })
1526-
};
1527-
}
1528-
Ok(result)
1594+
})*}
15291595
}
1596+
1597+
#[cfg(target_pointer_width = "16")]
1598+
from_str_radix_size_impl! { i16 isize, u16 usize }
1599+
#[cfg(target_pointer_width = "32")]
1600+
from_str_radix_size_impl! { i32 isize, u32 usize }
1601+
#[cfg(target_pointer_width = "64")]
1602+
from_str_radix_size_impl! { i64 isize, u64 usize }

0 commit comments

Comments
 (0)