Rollup merge of #140544 - m-ou-se:format-args-const-cleanup, r=fee1-dead

Clean up "const" situation in format_args!().

This cleans up the "const" situation in the format_args!() expansion/lowering.

Rather than marking the Argument::new_display etc. functions as non-const, this marks the Arguments::new_v1 functions as non-const.

Example expansion/lowering of format_args!() in const:

```rust
// Error: cannot call non-const formatting macro in constant functions
const {
    fmt::Arguments::new_v1( // Now the error is produced here.
        &["Hello, ", "!\n"],
        &[
            fmt::Argument::new_display(&world) // The error used to be produced here.
        ],
    )
}
```
This commit is contained in:
Guillaume Gomez
2025-05-01 22:27:24 +02:00
committed by GitHub
7 changed files with 77 additions and 63 deletions

View File

@@ -352,7 +352,7 @@ fn build_error_for_const_call<'tcx>(
); );
err err
} }
_ if tcx.opt_parent(callee) == tcx.get_diagnostic_item(sym::ArgumentMethods) => { _ if tcx.opt_parent(callee) == tcx.get_diagnostic_item(sym::FmtArgumentsNew) => {
ccx.dcx().create_err(errors::NonConstFmtMacroCall { ccx.dcx().create_err(errors::NonConstFmtMacroCall {
span, span,
kind: ccx.const_kind(), kind: ccx.const_kind(),

View File

@@ -174,7 +174,6 @@ symbols! {
Arc, Arc,
ArcWeak, ArcWeak,
Argument, Argument,
ArgumentMethods,
ArrayIntoIter, ArrayIntoIter,
AsMut, AsMut,
AsRef, AsRef,
@@ -249,6 +248,7 @@ symbols! {
Error, Error,
File, File,
FileType, FileType,
FmtArgumentsNew,
Fn, Fn,
FnMut, FnMut,
FnOnce, FnOnce,

View File

@@ -622,44 +622,9 @@ pub struct Arguments<'a> {
args: &'a [rt::Argument<'a>], args: &'a [rt::Argument<'a>],
} }
/// Used by the format_args!() macro to create a fmt::Arguments object.
#[doc(hidden)] #[doc(hidden)]
#[unstable(feature = "fmt_internals", issue = "none")] #[unstable(feature = "fmt_internals", issue = "none")]
impl<'a> Arguments<'a> { impl<'a> Arguments<'a> {
#[inline]
pub const fn new_const<const N: usize>(pieces: &'a [&'static str; N]) -> Self {
const { assert!(N <= 1) };
Arguments { pieces, fmt: None, args: &[] }
}
/// When using the format_args!() macro, this function is used to generate the
/// Arguments structure.
#[inline]
pub const fn new_v1<const P: usize, const A: usize>(
pieces: &'a [&'static str; P],
args: &'a [rt::Argument<'a>; A],
) -> Arguments<'a> {
const { assert!(P >= A && P <= A + 1, "invalid args") }
Arguments { pieces, fmt: None, args }
}
/// Specifies nonstandard formatting parameters.
///
/// An `rt::UnsafeArg` is required because the following invariants must be held
/// in order for this function to be safe:
/// 1. The `pieces` slice must be at least as long as `fmt`.
/// 2. Every `rt::Placeholder::position` value within `fmt` must be a valid index of `args`.
/// 3. Every `rt::Count::Param` within `fmt` must contain a valid index of `args`.
#[inline]
pub const fn new_v1_formatted(
pieces: &'a [&'static str],
args: &'a [rt::Argument<'a>],
fmt: &'a [rt::Placeholder],
_unsafe_arg: rt::UnsafeArg,
) -> Arguments<'a> {
Arguments { pieces, fmt: Some(fmt), args }
}
/// Estimates the length of the formatted text. /// Estimates the length of the formatted text.
/// ///
/// This is intended to be used for setting initial `String` capacity /// This is intended to be used for setting initial `String` capacity

View File

@@ -1,7 +1,10 @@
#![allow(missing_debug_implementations)] #![allow(missing_debug_implementations)]
#![unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")] #![unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")]
//! These are the lang items used by format_args!(). //! All types and methods in this file are used by the compiler in
//! the expansion/lowering of format_args!().
//!
//! Do not modify them without understanding the consequences for the format_args!() macro.
use super::*; use super::*;
use crate::hint::unreachable_unchecked; use crate::hint::unreachable_unchecked;
@@ -110,46 +113,45 @@ macro_rules! argument_new {
}; };
} }
#[rustc_diagnostic_item = "ArgumentMethods"]
impl Argument<'_> { impl Argument<'_> {
#[inline] #[inline]
pub fn new_display<T: Display>(x: &T) -> Argument<'_> { pub const fn new_display<T: Display>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as Display>::fmt) argument_new!(T, x, <T as Display>::fmt)
} }
#[inline] #[inline]
pub fn new_debug<T: Debug>(x: &T) -> Argument<'_> { pub const fn new_debug<T: Debug>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as Debug>::fmt) argument_new!(T, x, <T as Debug>::fmt)
} }
#[inline] #[inline]
pub fn new_debug_noop<T: Debug>(x: &T) -> Argument<'_> { pub const fn new_debug_noop<T: Debug>(x: &T) -> Argument<'_> {
argument_new!(T, x, |_: &T, _| Ok(())) argument_new!(T, x, |_: &T, _| Ok(()))
} }
#[inline] #[inline]
pub fn new_octal<T: Octal>(x: &T) -> Argument<'_> { pub const fn new_octal<T: Octal>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as Octal>::fmt) argument_new!(T, x, <T as Octal>::fmt)
} }
#[inline] #[inline]
pub fn new_lower_hex<T: LowerHex>(x: &T) -> Argument<'_> { pub const fn new_lower_hex<T: LowerHex>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as LowerHex>::fmt) argument_new!(T, x, <T as LowerHex>::fmt)
} }
#[inline] #[inline]
pub fn new_upper_hex<T: UpperHex>(x: &T) -> Argument<'_> { pub const fn new_upper_hex<T: UpperHex>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as UpperHex>::fmt) argument_new!(T, x, <T as UpperHex>::fmt)
} }
#[inline] #[inline]
pub fn new_pointer<T: Pointer>(x: &T) -> Argument<'_> { pub const fn new_pointer<T: Pointer>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as Pointer>::fmt) argument_new!(T, x, <T as Pointer>::fmt)
} }
#[inline] #[inline]
pub fn new_binary<T: Binary>(x: &T) -> Argument<'_> { pub const fn new_binary<T: Binary>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as Binary>::fmt) argument_new!(T, x, <T as Binary>::fmt)
} }
#[inline] #[inline]
pub fn new_lower_exp<T: LowerExp>(x: &T) -> Argument<'_> { pub const fn new_lower_exp<T: LowerExp>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as LowerExp>::fmt) argument_new!(T, x, <T as LowerExp>::fmt)
} }
#[inline] #[inline]
pub fn new_upper_exp<T: UpperExp>(x: &T) -> Argument<'_> { pub const fn new_upper_exp<T: UpperExp>(x: &T) -> Argument<'_> {
argument_new!(T, x, <T as UpperExp>::fmt) argument_new!(T, x, <T as UpperExp>::fmt)
} }
#[inline] #[inline]
@@ -200,15 +202,8 @@ impl Argument<'_> {
/// let f = format_args!("{}", "a"); /// let f = format_args!("{}", "a");
/// println!("{f}"); /// println!("{f}");
/// ``` /// ```
///
/// This function should _not_ be const, to make sure we don't accept
/// format_args!() and panic!() with arguments in const, even when not evaluated:
///
/// ```compile_fail,E0015
/// const _: () = if false { panic!("a {}", "a") };
/// ```
#[inline] #[inline]
pub fn none() -> [Self; 0] { pub const fn none() -> [Self; 0] {
[] []
} }
} }
@@ -229,3 +224,57 @@ impl UnsafeArg {
Self { _private: () } Self { _private: () }
} }
} }
/// Used by the format_args!() macro to create a fmt::Arguments object.
#[doc(hidden)]
#[unstable(feature = "fmt_internals", issue = "none")]
#[rustc_diagnostic_item = "FmtArgumentsNew"]
impl<'a> Arguments<'a> {
#[inline]
pub const fn new_const<const N: usize>(pieces: &'a [&'static str; N]) -> Self {
const { assert!(N <= 1) };
Arguments { pieces, fmt: None, args: &[] }
}
/// When using the format_args!() macro, this function is used to generate the
/// Arguments structure.
///
/// This function should _not_ be const, to make sure we don't accept
/// format_args!() and panic!() with arguments in const, even when not evaluated:
///
/// ```compile_fail,E0015
/// const _: () = if false { panic!("a {}", "a") };
/// ```
#[inline]
pub fn new_v1<const P: usize, const A: usize>(
pieces: &'a [&'static str; P],
args: &'a [rt::Argument<'a>; A],
) -> Arguments<'a> {
const { assert!(P >= A && P <= A + 1, "invalid args") }
Arguments { pieces, fmt: None, args }
}
/// Specifies nonstandard formatting parameters.
///
/// An `rt::UnsafeArg` is required because the following invariants must be held
/// in order for this function to be safe:
/// 1. The `pieces` slice must be at least as long as `fmt`.
/// 2. Every `rt::Placeholder::position` value within `fmt` must be a valid index of `args`.
/// 3. Every `rt::Count::Param` within `fmt` must contain a valid index of `args`.
///
/// This function should _not_ be const, to make sure we don't accept
/// format_args!() and panic!() with arguments in const, even when not evaluated:
///
/// ```compile_fail,E0015
/// const _: () = if false { panic!("a {:1}", "a") };
/// ```
#[inline]
pub fn new_v1_formatted(
pieces: &'a [&'static str],
args: &'a [rt::Argument<'a>],
fmt: &'a [rt::Placeholder],
_unsafe_arg: rt::UnsafeArg,
) -> Arguments<'a> {
Arguments { pieces, fmt: Some(fmt), args }
}
}

View File

@@ -121,7 +121,7 @@
StorageDead(_18); StorageDead(_18);
_16 = &_17; _16 = &_17;
_15 = &(*_16); _15 = &(*_16);
_11 = Arguments::<'_>::new_v1::<3, 2>(move _12, move _15) -> [return: bb5, unwind unreachable]; _11 = core::fmt::rt::<impl Arguments<'_>>::new_v1::<3, 2>(move _12, move _15) -> [return: bb5, unwind unreachable];
} }
bb5: { bb5: {

View File

@@ -34,7 +34,7 @@ fn bar() ({
((::alloc::fmt::format as ((::alloc::fmt::format as
for<'a> fn(Arguments<'a>) -> String {format})(((format_arguments::new_const for<'a> fn(Arguments<'a>) -> String {format})(((format_arguments::new_const
as as
fn(&[&'static str; 1]) -> Arguments<'_> {Arguments::<'_>::new_const::<1>})((&([("test" fn(&[&'static str; 1]) -> Arguments<'_> {core::fmt::rt::<impl Arguments<'_>>::new_const::<1>})((&([("test"
as &str)] as [&str; 1]) as &[&str; 1])) as Arguments<'_>)) as &str)] as [&str; 1]) as &[&str; 1])) as Arguments<'_>))
as String) as String)
} as String)) as String); } as String)) as String);

View File

@@ -1,16 +1,16 @@
error[E0015]: cannot call non-const formatting macro in constant functions error[E0015]: cannot call non-const formatting macro in constant functions
--> $DIR/format.rs:2:13 --> $DIR/format.rs:2:5
| |
LL | panic!("{:?}", 0); LL | panic!("{:?}", 0);
| ^^^^ | ^^^^^^^^^^^^^^^^^
| |
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
error[E0015]: cannot call non-const formatting macro in constant functions error[E0015]: cannot call non-const formatting macro in constant functions
--> $DIR/format.rs:7:15 --> $DIR/format.rs:7:5
| |
LL | println!("{:?}", 0); LL | println!("{:?}", 0);
| ^^^^ | ^^^^^^^^^^^^^^^^^^^
| |
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)