From ff9178b1a6a1c84c975c54f903dc3f89c7c66173 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 2 Nov 2024 23:23:08 +0100 Subject: [PATCH] add const_panic macro to make it easier to fall back to non-formatting panic in const --- core/src/char/methods.rs | 43 +++++++++++-------------- core/src/macros/mod.rs | 61 +++++++++++++++++++++++++++++++++++ core/src/num/f128.rs | 20 ++++++------ core/src/num/f16.rs | 20 ++++++------ core/src/num/f32.rs | 19 ++++++----- core/src/num/f64.rs | 19 ++++++----- core/src/num/mod.rs | 21 +++++-------- core/src/slice/index.rs | 68 +++++++++++----------------------------- 8 files changed, 142 insertions(+), 129 deletions(-) diff --git a/core/src/char/methods.rs b/core/src/char/methods.rs index 206bbf5690ef1..228883acca986 100644 --- a/core/src/char/methods.rs +++ b/core/src/char/methods.rs @@ -1,7 +1,7 @@ //! impl char {} use super::*; -use crate::intrinsics::const_eval_select; +use crate::macros::const_panic; use crate::slice; use crate::str::from_utf8_unchecked_mut; use crate::unicode::printable::is_printable; @@ -1774,17 +1774,7 @@ const fn len_utf16(code: u32) -> usize { #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_char_encode_utf8", since = "1.83.0"))] #[doc(hidden)] #[inline] -#[rustc_allow_const_fn_unstable(const_eval_select)] pub const fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] { - const fn panic_at_const(_code: u32, _len: usize, _dst_len: usize) { - // Note that we cannot format in constant expressions. - panic!("encode_utf8: buffer does not have enough bytes to encode code point"); - } - fn panic_at_rt(code: u32, len: usize, dst_len: usize) { - panic!( - "encode_utf8: need {len} bytes to encode U+{code:04X} but buffer has just {dst_len}", - ); - } let len = len_utf8(code); match (len, &mut *dst) { (1, [a, ..]) => { @@ -1805,8 +1795,15 @@ pub const fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] { *c = (code >> 6 & 0x3F) as u8 | TAG_CONT; *d = (code & 0x3F) as u8 | TAG_CONT; } - // FIXME(const-hack): We would prefer to have streamlined panics when formatters become const-friendly. - _ => const_eval_select((code, len, dst.len()), panic_at_const, panic_at_rt), + _ => { + const_panic!( + "encode_utf8: buffer does not have enough bytes to encode code point", + "encode_utf8: need {len} bytes to encode U+{code:04X} but buffer has just {dst_len}", + code: u32 = code, + len: usize = len, + dst_len: usize = dst.len(), + ) + } }; // SAFETY: `<&mut [u8]>::as_mut_ptr` is guaranteed to return a valid pointer and `len` has been tested to be within bounds. unsafe { slice::from_raw_parts_mut(dst.as_mut_ptr(), len) } @@ -1827,15 +1824,6 @@ pub const fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] { #[doc(hidden)] #[inline] pub const fn encode_utf16_raw(mut code: u32, dst: &mut [u16]) -> &mut [u16] { - const fn panic_at_const(_code: u32, _len: usize, _dst_len: usize) { - // Note that we cannot format in constant expressions. - panic!("encode_utf16: buffer does not have enough bytes to encode code point"); - } - fn panic_at_rt(code: u32, len: usize, dst_len: usize) { - panic!( - "encode_utf16: need {len} bytes to encode U+{code:04X} but buffer has just {dst_len}", - ); - } let len = len_utf16(code); match (len, &mut *dst) { (1, [a, ..]) => { @@ -1846,8 +1834,15 @@ pub const fn encode_utf16_raw(mut code: u32, dst: &mut [u16]) -> &mut [u16] { *a = (code >> 10) as u16 | 0xD800; *b = (code & 0x3FF) as u16 | 0xDC00; } - // FIXME(const-hack): We would prefer to have streamlined panics when formatters become const-friendly. - _ => const_eval_select((code, len, dst.len()), panic_at_const, panic_at_rt), + _ => { + const_panic!( + "encode_utf16: buffer does not have enough bytes to encode code point", + "encode_utf16: need {len} bytes to encode U+{code:04X} but buffer has just {dst_len}", + code: u32 = code, + len: usize = len, + dst_len: usize = dst.len(), + ) + } }; // SAFETY: `<&mut [u16]>::as_mut_ptr` is guaranteed to return a valid pointer and `len` has been tested to be within bounds. unsafe { slice::from_raw_parts_mut(dst.as_mut_ptr(), len) } diff --git a/core/src/macros/mod.rs b/core/src/macros/mod.rs index 771c2d31b60e0..9a91ff82acd7c 100644 --- a/core/src/macros/mod.rs +++ b/core/src/macros/mod.rs @@ -12,6 +12,54 @@ macro_rules! panic { }; } +/// Helper macro for panicking in a `const fn`. +/// Invoke as: +/// ```rust,ignore (just an example) +/// core::macros::const_panic!("boring message", "flavored message {a} {b:?}", a: u32 = foo.len(), b: Something = bar); +/// ``` +/// where the first message will be printed in const-eval, +/// and the second message will be printed at runtime. +// All uses of this macro are FIXME(const-hack). +#[unstable(feature = "panic_internals", issue = "none")] +#[doc(hidden)] +pub macro const_panic { + ($const_msg:literal, $runtime_msg:literal, $($arg:ident : $ty:ty = $val:expr),* $(,)?) => {{ + #[inline] + #[track_caller] + fn runtime($($arg: $ty),*) -> ! { + $crate::panic!($runtime_msg); + } + + #[inline] + #[track_caller] + const fn compiletime($(_: $ty),*) -> ! { + $crate::panic!($const_msg); + } + + // Wrap call to `const_eval_select` in a function so that we can + // add the `rustc_allow_const_fn_unstable`. This is okay to do + // because both variants will panic, just with different messages. + #[rustc_allow_const_fn_unstable(const_eval_select)] + #[inline(always)] + #[track_caller] + #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_panic", since = "CURRENT_RUSTC_VERSION"))] + const fn do_panic($($arg: $ty),*) -> ! { + $crate::intrinsics::const_eval_select(($($arg),* ,), compiletime, runtime) + } + + do_panic($($val),*) + }}, + // We support leaving away the `val` expressions for *all* arguments + // (but not for *some* arguments, that's too tricky). + ($const_msg:literal, $runtime_msg:literal, $($arg:ident : $ty:ty),* $(,)?) => { + $crate::macros::const_panic!( + $const_msg, + $runtime_msg, + $($arg: $ty = $arg),* + ) + }, +} + /// Asserts that two expressions are equal to each other (using [`PartialEq`]). /// /// Assertions are always checked in both debug and release builds, and cannot @@ -196,6 +244,19 @@ pub macro assert_matches { }, } +/// A version of `assert` that prints a non-formatting message in const contexts. +/// +/// See [`const_panic!`]. +#[unstable(feature = "panic_internals", issue = "none")] +#[doc(hidden)] +pub macro const_assert { + ($condition: expr, $const_msg:literal, $runtime_msg:literal, $($arg:tt)*) => {{ + if !$crate::intrinsics::likely($condition) { + $crate::macros::const_panic!($const_msg, $runtime_msg, $($arg)*) + } + }} +} + /// A macro for defining `#[cfg]` match-like statements. /// /// It is similar to the `if/elif` C preprocessor macro by allowing definition of a cascade of diff --git a/core/src/num/f128.rs b/core/src/num/f128.rs index e8161cce2fe29..7709e7de01b0b 100644 --- a/core/src/num/f128.rs +++ b/core/src/num/f128.rs @@ -14,6 +14,7 @@ use crate::convert::FloatToInt; #[cfg(not(test))] use crate::intrinsics; +use crate::macros::const_assert; use crate::mem; use crate::num::FpCategory; @@ -1263,17 +1264,14 @@ impl f128 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] pub const fn clamp(mut self, min: f128, max: f128) -> f128 { - #[inline] // inline to avoid LLVM crash - const fn assert_at_const(min: f128, max: f128) { - // Note that we cannot format in constant expressions. - assert!(min <= max, "min > max, or either was NaN"); - } - #[inline] // inline to avoid codegen regression - fn assert_at_rt(min: f128, max: f128) { - assert!(min <= max, "min > max, or either was NaN. min = {min:?}, max = {max:?}"); - } - // FIXME(const-hack): We would prefer to have streamlined panics when formatters become const-friendly. - intrinsics::const_eval_select((min, max), assert_at_const, assert_at_rt); + const_assert!( + min <= max, + "min > max, or either was NaN", + "min > max, or either was NaN. min = {min:?}, max = {max:?}", + min: f128, + max: f128, + ); + if self < min { self = min; } diff --git a/core/src/num/f16.rs b/core/src/num/f16.rs index 8b3f3b7d19bf7..eb0225c58b837 100644 --- a/core/src/num/f16.rs +++ b/core/src/num/f16.rs @@ -14,6 +14,7 @@ use crate::convert::FloatToInt; #[cfg(not(test))] use crate::intrinsics; +use crate::macros::const_assert; use crate::mem; use crate::num::FpCategory; @@ -1238,17 +1239,14 @@ impl f16 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[must_use = "method returns a new number and does not mutate the original value"] pub const fn clamp(mut self, min: f16, max: f16) -> f16 { - #[inline] // inline to avoid LLVM crash - const fn assert_at_const(min: f16, max: f16) { - // Note that we cannot format in constant expressions. - assert!(min <= max, "min > max, or either was NaN"); - } - #[inline] // inline to avoid codegen regression - fn assert_at_rt(min: f16, max: f16) { - assert!(min <= max, "min > max, or either was NaN. min = {min:?}, max = {max:?}"); - } - // FIXME(const-hack): We would prefer to have streamlined panics when formatters become const-friendly. - intrinsics::const_eval_select((min, max), assert_at_const, assert_at_rt); + const_assert!( + min <= max, + "min > max, or either was NaN", + "min > max, or either was NaN. min = {min:?}, max = {max:?}", + min: f16, + max: f16, + ); + if self < min { self = min; } diff --git a/core/src/num/f32.rs b/core/src/num/f32.rs index a01761ee5d4a3..a29f47b888eb9 100644 --- a/core/src/num/f32.rs +++ b/core/src/num/f32.rs @@ -14,6 +14,7 @@ use crate::convert::FloatToInt; #[cfg(not(test))] use crate::intrinsics; +use crate::macros::const_assert; use crate::mem; use crate::num::FpCategory; @@ -1409,16 +1410,14 @@ impl f32 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[inline] pub const fn clamp(mut self, min: f32, max: f32) -> f32 { - const fn assert_at_const(min: f32, max: f32) { - // Note that we cannot format in constant expressions. - assert!(min <= max, "min > max, or either was NaN"); - } - #[inline] // inline to avoid codegen regression - fn assert_at_rt(min: f32, max: f32) { - assert!(min <= max, "min > max, or either was NaN. min = {min:?}, max = {max:?}"); - } - // FIXME(const-hack): We would prefer to have streamlined panics when formatters become const-friendly. - intrinsics::const_eval_select((min, max), assert_at_const, assert_at_rt); + const_assert!( + min <= max, + "min > max, or either was NaN", + "min > max, or either was NaN. min = {min:?}, max = {max:?}", + min: f32, + max: f32, + ); + if self < min { self = min; } diff --git a/core/src/num/f64.rs b/core/src/num/f64.rs index 2995e41cd6ea0..d4d364e5972a2 100644 --- a/core/src/num/f64.rs +++ b/core/src/num/f64.rs @@ -14,6 +14,7 @@ use crate::convert::FloatToInt; #[cfg(not(test))] use crate::intrinsics; +use crate::macros::const_assert; use crate::mem; use crate::num::FpCategory; @@ -1409,16 +1410,14 @@ impl f64 { #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] #[inline] pub const fn clamp(mut self, min: f64, max: f64) -> f64 { - const fn assert_at_const(min: f64, max: f64) { - // Note that we cannot format in constant expressions. - assert!(min <= max, "min > max, or either was NaN"); - } - #[inline] // inline to avoid codegen regression - fn assert_at_rt(min: f64, max: f64) { - assert!(min <= max, "min > max, or either was NaN. min = {min:?}, max = {max:?}"); - } - // FIXME(const-hack): We would prefer to have streamlined panics when formatters become const-friendly. - intrinsics::const_eval_select((min, max), assert_at_const, assert_at_rt); + const_assert!( + min <= max, + "min > max, or either was NaN", + "min > max, or either was NaN. min = {min:?}, max = {max:?}", + min: f64, + max: f64, + ); + if self < min { self = min; } diff --git a/core/src/num/mod.rs b/core/src/num/mod.rs index 6a0b40ff51771..1ebff1c9f4485 100644 --- a/core/src/num/mod.rs +++ b/core/src/num/mod.rs @@ -2,6 +2,7 @@ #![stable(feature = "rust1", since = "1.0.0")] +use crate::macros::const_panic; use crate::str::FromStr; use crate::ub_checks::assert_unsafe_precondition; use crate::{ascii, intrinsics, mem}; @@ -1460,24 +1461,16 @@ pub const fn can_not_overflow(radix: u32, is_signed_ty: bool, digits: &[u8]) radix <= 16 && digits.len() <= mem::size_of::() * 2 - is_signed_ty as usize } -#[track_caller] -const fn from_str_radix_panic_ct(_radix: u32) -> ! { - panic!("from_str_radix_int: must lie in the range `[2, 36]`"); -} - -#[track_caller] -fn from_str_radix_panic_rt(radix: u32) -> ! { - panic!("from_str_radix_int: must lie in the range `[2, 36]` - found {}", radix); -} - #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))] #[cfg_attr(feature = "panic_immediate_abort", inline)] #[cold] #[track_caller] -#[rustc_allow_const_fn_unstable(const_eval_select)] -const fn from_str_radix_panic(radix: u32) { - // The only difference between these two functions is their panic message. - intrinsics::const_eval_select((radix,), from_str_radix_panic_ct, from_str_radix_panic_rt); +const fn from_str_radix_panic(radix: u32) -> ! { + const_panic!( + "from_str_radix_int: must lie in the range `[2, 36]`", + "from_str_radix_int: must lie in the range `[2, 36]` - found {radix}", + radix: u32 = radix, + ) } macro_rules! from_str_radix { diff --git a/core/src/slice/index.rs b/core/src/slice/index.rs index 231ab7396adfd..ebb4bdb144909 100644 --- a/core/src/slice/index.rs +++ b/core/src/slice/index.rs @@ -1,6 +1,6 @@ //! Indexing implementations for `[T]`. -use crate::intrinsics::const_eval_select; +use crate::macros::const_panic; use crate::ub_checks::assert_unsafe_precondition; use crate::{ops, range}; @@ -31,67 +31,37 @@ where #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] #[cfg_attr(feature = "panic_immediate_abort", inline)] #[track_caller] -#[rustc_allow_const_fn_unstable(const_eval_select)] const fn slice_start_index_len_fail(index: usize, len: usize) -> ! { - // FIXME(const-hack): once integer formatting in panics is possible, we - // should use the same implementation at compiletime and runtime. - const_eval_select((index, len), slice_start_index_len_fail_ct, slice_start_index_len_fail_rt) -} - -#[inline] -#[track_caller] -fn slice_start_index_len_fail_rt(index: usize, len: usize) -> ! { - panic!("range start index {index} out of range for slice of length {len}"); -} - -#[inline] -#[track_caller] -const fn slice_start_index_len_fail_ct(_: usize, _: usize) -> ! { - panic!("slice start index is out of range for slice"); + const_panic!( + "slice start index is out of range for slice", + "range start index {index} out of range for slice of length {len}", + index: usize, + len: usize, + ) } #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] #[cfg_attr(feature = "panic_immediate_abort", inline)] #[track_caller] -#[rustc_allow_const_fn_unstable(const_eval_select)] const fn slice_end_index_len_fail(index: usize, len: usize) -> ! { - // FIXME(const-hack): once integer formatting in panics is possible, we - // should use the same implementation at compiletime and runtime. - const_eval_select((index, len), slice_end_index_len_fail_ct, slice_end_index_len_fail_rt) -} - -#[inline] -#[track_caller] -fn slice_end_index_len_fail_rt(index: usize, len: usize) -> ! { - panic!("range end index {index} out of range for slice of length {len}"); -} - -#[inline] -#[track_caller] -const fn slice_end_index_len_fail_ct(_: usize, _: usize) -> ! { - panic!("slice end index is out of range for slice"); + const_panic!( + "slice end index is out of range for slice", + "range end index {index} out of range for slice of length {len}", + index: usize, + len: usize, + ) } #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] #[cfg_attr(feature = "panic_immediate_abort", inline)] #[track_caller] -#[rustc_allow_const_fn_unstable(const_eval_select)] const fn slice_index_order_fail(index: usize, end: usize) -> ! { - // FIXME(const-hack): once integer formatting in panics is possible, we - // should use the same implementation at compiletime and runtime. - const_eval_select((index, end), slice_index_order_fail_ct, slice_index_order_fail_rt) -} - -#[inline] -#[track_caller] -fn slice_index_order_fail_rt(index: usize, end: usize) -> ! { - panic!("slice index starts at {index} but ends at {end}"); -} - -#[inline] -#[track_caller] -const fn slice_index_order_fail_ct(_: usize, _: usize) -> ! { - panic!("slice index start is larger than end"); + const_panic!( + "slice index start is larger than end", + "slice index starts at {index} but ends at {end}", + index: usize, + end: usize, + ) } #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]