Auto merge of #106824 - m-ou-se:format-args-flatten, r=oli-obk

Flatten/inline format_args!() and (string and int) literal arguments into format_args!()

Implements https://github.com/rust-lang/rust/issues/78356

Gated behind `-Zflatten-format-args=yes`.

Part of #99012

This change inlines string literals, integer literals and nested format_args!() into format_args!() during ast lowering, making all of the following pairs result in equivalent hir:

```rust
println!("Hello, {}!", "World");
println!("Hello, World!");
```

```rust
println!("[info] {}", format_args!("error"));
println!("[info] error");
```

```rust
println!("[{}] {}", status, format_args!("error: {}", msg));
println!("[{}] error: {}", status, msg);
```

```rust
println!("{} + {} = {}", 1, 2, 1 + 2);
println!("1 + 2 = {}", 1 + 2);
```

And so on.

This is useful for macros. E.g. a `log::info!()` macro could just pass the tokens from the user directly into a `format_args!()` that gets efficiently flattened/inlined into a `format_args!("info: {}")`.

It also means that `dbg!(x)` will have its file, line, and expression name inlined:

```rust
eprintln!("[{}:{}] {} = {:#?}", file!(), line!(), stringify!(x), x); // before
eprintln!("[example.rs:1] x = {:#?}", x); // after
```

Which can be nice in some cases, but also means a lot more unique static strings than before if dbg!() is used a lot.
This commit is contained in:
bors 2023-03-16 13:46:52 +00:00
commit 1203e0866e
17 changed files with 330 additions and 54 deletions

View file

@ -1184,6 +1184,15 @@ pub fn peel_parens(&self) -> &Expr {
expr
}
pub fn peel_parens_and_refs(&self) -> &Expr {
let mut expr = self;
while let ExprKind::Paren(inner) | ExprKind::AddrOf(BorrowKind::Ref, _, inner) = &expr.kind
{
expr = inner;
}
expr
}
/// Attempts to reparse as `Ty` (for diagnostic purposes).
pub fn to_ty(&self) -> Option<P<Ty>> {
let kind = match &self.kind {

View file

@ -131,8 +131,8 @@ pub fn all_args(&self) -> &[FormatArgument] {
&self.arguments[..]
}
pub fn all_args_mut(&mut self) -> &mut [FormatArgument] {
&mut self.arguments[..]
pub fn all_args_mut(&mut self) -> &mut Vec<FormatArgument> {
&mut self.arguments
}
}

View file

@ -7,15 +7,174 @@
use rustc_span::{
sym,
symbol::{kw, Ident},
Span,
Span, Symbol,
};
use std::borrow::Cow;
impl<'hir> LoweringContext<'_, 'hir> {
pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> {
expand_format_args(self, sp, fmt)
// Never call the const constructor of `fmt::Arguments` if the
// format_args!() had any arguments _before_ flattening/inlining.
let allow_const = fmt.arguments.all_args().is_empty();
let mut fmt = Cow::Borrowed(fmt);
if self.tcx.sess.opts.unstable_opts.flatten_format_args {
fmt = flatten_format_args(fmt);
fmt = inline_literals(fmt);
}
expand_format_args(self, sp, &fmt, allow_const)
}
}
/// Flattens nested `format_args!()` into one.
///
/// Turns
///
/// `format_args!("a {} {} {}.", 1, format_args!("b{}!", 2), 3)`
///
/// into
///
/// `format_args!("a {} b{}! {}.", 1, 2, 3)`.
fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> {
let mut i = 0;
while i < fmt.template.len() {
if let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i]
&& let FormatTrait::Display | FormatTrait::Debug = &placeholder.format_trait
&& let Ok(arg_index) = placeholder.argument.index
&& let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs()
&& let ExprKind::FormatArgs(_) = &arg.kind
// Check that this argument is not used by any other placeholders.
&& fmt.template.iter().enumerate().all(|(j, p)|
i == j ||
!matches!(p, FormatArgsPiece::Placeholder(placeholder)
if placeholder.argument.index == Ok(arg_index))
)
{
// Now we need to mutate the outer FormatArgs.
// If this is the first time, this clones the outer FormatArgs.
let fmt = fmt.to_mut();
// Take the inner FormatArgs out of the outer arguments, and
// replace it by the inner arguments. (We can't just put those at
// the end, because we need to preserve the order of evaluation.)
let args = fmt.arguments.all_args_mut();
let remaining_args = args.split_off(arg_index + 1);
let old_arg_offset = args.len();
let mut fmt2 = &mut args.pop().unwrap().expr; // The inner FormatArgs.
let fmt2 = loop { // Unwrap the Expr to get to the FormatArgs.
match &mut fmt2.kind {
ExprKind::Paren(inner) | ExprKind::AddrOf(BorrowKind::Ref, _, inner) => fmt2 = inner,
ExprKind::FormatArgs(fmt2) => break fmt2,
_ => unreachable!(),
}
};
args.append(fmt2.arguments.all_args_mut());
let new_arg_offset = args.len();
args.extend(remaining_args);
// Correct the indexes that refer to the arguments after the newly inserted arguments.
for_all_argument_indexes(&mut fmt.template, |index| {
if *index >= old_arg_offset {
*index -= old_arg_offset;
*index += new_arg_offset;
}
});
// Now merge the placeholders:
let rest = fmt.template.split_off(i + 1);
fmt.template.pop(); // remove the placeholder for the nested fmt args.
// Insert the pieces from the nested format args, but correct any
// placeholders to point to the correct argument index.
for_all_argument_indexes(&mut fmt2.template, |index| *index += arg_index);
fmt.template.append(&mut fmt2.template);
fmt.template.extend(rest);
// Don't increment `i` here, so we recurse into the newly added pieces.
} else {
i += 1;
}
}
fmt
}
/// Inline literals into the format string.
///
/// Turns
///
/// `format_args!("Hello, {}! {} {}", "World", 123, x)`
///
/// into
///
/// `format_args!("Hello, World! 123 {}", x)`.
fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> {
let mut was_inlined = vec![false; fmt.arguments.all_args().len()];
let mut inlined_anything = false;
for i in 0..fmt.template.len() {
let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue };
let Ok(arg_index) = placeholder.argument.index else { continue };
let mut literal = None;
if let FormatTrait::Display = placeholder.format_trait
&& placeholder.format_options == Default::default()
&& let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs()
&& let ExprKind::Lit(lit) = arg.kind
{
if let token::LitKind::Str | token::LitKind::StrRaw(_) = lit.kind
&& let Ok(LitKind::Str(s, _)) = LitKind::from_token_lit(lit)
{
literal = Some(s);
} else if let token::LitKind::Integer = lit.kind
&& let Ok(LitKind::Int(n, _)) = LitKind::from_token_lit(lit)
{
literal = Some(Symbol::intern(&n.to_string()));
}
}
if let Some(literal) = literal {
// Now we need to mutate the outer FormatArgs.
// If this is the first time, this clones the outer FormatArgs.
let fmt = fmt.to_mut();
// Replace the placeholder with the literal.
fmt.template[i] = FormatArgsPiece::Literal(literal);
was_inlined[arg_index] = true;
inlined_anything = true;
}
}
// Remove the arguments that were inlined.
if inlined_anything {
let fmt = fmt.to_mut();
let mut remove = was_inlined;
// Don't remove anything that's still used.
for_all_argument_indexes(&mut fmt.template, |index| remove[*index] = false);
// Drop all the arguments that are marked for removal.
let mut remove_it = remove.iter();
fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&true));
// Calculate the mapping of old to new indexes for the remaining arguments.
let index_map: Vec<usize> = remove
.into_iter()
.scan(0, |i, remove| {
let mapped = *i;
*i += !remove as usize;
Some(mapped)
})
.collect();
// Correct the indexes that refer to arguments that have shifted position.
for_all_argument_indexes(&mut fmt.template, |index| *index = index_map[*index]);
}
fmt
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
enum ArgumentType {
Format(FormatTrait),
@ -189,11 +348,26 @@ fn expand_format_args<'hir>(
ctx: &mut LoweringContext<'_, 'hir>,
macsp: Span,
fmt: &FormatArgs,
allow_const: bool,
) -> hir::ExprKind<'hir> {
let mut incomplete_lit = String::new();
let lit_pieces =
ctx.arena.alloc_from_iter(fmt.template.iter().enumerate().filter_map(|(i, piece)| {
match piece {
&FormatArgsPiece::Literal(s) => Some(ctx.expr_str(fmt.span, s)),
&FormatArgsPiece::Literal(s) => {
// Coalesce adjacent literal pieces.
if let Some(FormatArgsPiece::Literal(_)) = fmt.template.get(i + 1) {
incomplete_lit.push_str(s.as_str());
None
} else if !incomplete_lit.is_empty() {
incomplete_lit.push_str(s.as_str());
let s = Symbol::intern(&incomplete_lit);
incomplete_lit.clear();
Some(ctx.expr_str(fmt.span, s))
} else {
Some(ctx.expr_str(fmt.span, s))
}
}
&FormatArgsPiece::Placeholder(_) => {
// Inject empty string before placeholders when not already preceded by a literal piece.
if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) {
@ -244,6 +418,18 @@ fn expand_format_args<'hir>(
let arguments = fmt.arguments.all_args();
if allow_const && arguments.is_empty() && argmap.is_empty() {
// Generate:
// <core::fmt::Arguments>::new_const(lit_pieces)
let new = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
macsp,
hir::LangItem::FormatArguments,
sym::new_const,
));
let new_args = ctx.arena.alloc_from_iter([lit_pieces]);
return hir::ExprKind::Call(new, new_args);
}
// If the args array contains exactly all the original arguments once,
// in order, we can use a simple array instead of a `match` construction.
// However, if there's a yield point in any argument except the first one,
@ -290,25 +476,14 @@ fn expand_format_args<'hir>(
let args_ident = Ident::new(sym::args, macsp);
let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
let args = ctx.arena.alloc_from_iter(argmap.iter().map(|&(arg_index, ty)| {
if let Some(arg) = arguments.get(arg_index) {
let sp = arg.expr.span.with_ctxt(macsp.ctxt());
let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id);
let arg = ctx.arena.alloc(ctx.expr(
sp,
hir::ExprKind::Field(
args_ident_expr,
Ident::new(sym::integer(arg_index), macsp),
),
));
make_argument(ctx, sp, arg, ty)
} else {
ctx.expr(
macsp,
hir::ExprKind::Err(
ctx.tcx.sess.delay_span_bug(macsp, format!("no arg at {arg_index}")),
),
)
}
let arg = &arguments[arg_index];
let sp = arg.expr.span.with_ctxt(macsp.ctxt());
let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id);
let arg = ctx.arena.alloc(ctx.expr(
sp,
hir::ExprKind::Field(args_ident_expr, Ident::new(sym::integer(arg_index), macsp)),
));
make_argument(ctx, sp, arg, ty)
}));
let elements: Vec<_> = arguments
.iter()
@ -409,3 +584,22 @@ fn visit_item(&mut self, _: &ast::Item) {
visitor.visit_expr(e);
visitor.0
}
fn for_all_argument_indexes(template: &mut [FormatArgsPiece], mut f: impl FnMut(&mut usize)) {
for piece in template {
let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
if let Ok(index) = &mut placeholder.argument.index {
f(index);
}
if let Some(FormatCount::Argument(FormatArgPosition { index: Ok(index), .. })) =
&mut placeholder.format_options.width
{
f(index);
}
if let Some(FormatCount::Argument(FormatArgPosition { index: Ok(index), .. })) =
&mut placeholder.format_options.precision
{
f(index);
}
}
}

View file

@ -744,6 +744,7 @@ macro_rules! tracked {
tracked!(emit_thin_lto, false);
tracked!(export_executable_symbols, true);
tracked!(fewer_names, Some(true));
tracked!(flatten_format_args, true);
tracked!(force_unstable_if_unmarked, true);
tracked!(fuel, Some(("abc".to_string(), 99)));
tracked!(function_sections, Some(false));

View file

@ -1422,6 +1422,9 @@ pub(crate) fn parse_proc_macro_execution_strategy(
fewer_names: Option<bool> = (None, parse_opt_bool, [TRACKED],
"reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \
(default: no)"),
flatten_format_args: bool = (false, parse_bool, [TRACKED],
"flatten nested format_args!() and literals into a simplified format_args!() call \
(default: no)"),
force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED],
"force all crates to be `rustc_private` unstable (default: no)"),
fuel: Option<(String, u64)> = (None, parse_optimization_fuel, [TRACKED],

View file

@ -984,6 +984,7 @@
never_type_fallback,
new,
new_binary,
new_const,
new_debug,
new_display,
new_lower_exp,

View file

@ -392,8 +392,31 @@ enum FlagV1 {
}
impl<'a> Arguments<'a> {
#[doc(hidden)]
#[inline]
#[unstable(feature = "fmt_internals", issue = "none")]
#[rustc_const_unstable(feature = "const_fmt_arguments_new", issue = "none")]
pub const fn new_const(pieces: &'a [&'static str]) -> Self {
if pieces.len() > 1 {
panic!("invalid args");
}
Arguments { pieces, fmt: None, args: &[] }
}
/// When using the format_args!() macro, this function is used to generate the
/// Arguments structure.
#[cfg(not(bootstrap))]
#[doc(hidden)]
#[inline]
#[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")]
pub fn new_v1(pieces: &'a [&'static str], args: &'a [ArgumentV1<'a>]) -> Arguments<'a> {
if pieces.len() < args.len() || pieces.len() > args.len() + 1 {
panic!("invalid args");
}
Arguments { pieces, fmt: None, args }
}
#[cfg(bootstrap)]
#[doc(hidden)]
#[inline]
#[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")]
@ -417,8 +440,7 @@ pub const fn new_v1(pieces: &'a [&'static str], args: &'a [ArgumentV1<'a>]) -> A
#[doc(hidden)]
#[inline]
#[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")]
#[rustc_const_unstable(feature = "const_fmt_arguments_new", issue = "none")]
pub const fn new_v1_formatted(
pub fn new_v1_formatted(
pieces: &'a [&'static str],
args: &'a [ArgumentV1<'a>],
fmt: &'a [rt::v1::Argument],

View file

@ -111,7 +111,7 @@ pub const fn panic(expr: &'static str) -> ! {
// truncation and padding (even though none is used here). Using
// Arguments::new_v1 may allow the compiler to omit Formatter::pad from the
// output binary, saving up to a few kilobytes.
panic_fmt(fmt::Arguments::new_v1(&[expr], &[]));
panic_fmt(fmt::Arguments::new_const(&[expr]));
}
/// Like `panic`, but without unwinding and track_caller to reduce the impact on codesize.
@ -120,7 +120,7 @@ pub const fn panic(expr: &'static str) -> ! {
#[lang = "panic_nounwind"] // needed by codegen for non-unwinding panics
#[rustc_nounwind]
pub fn panic_nounwind(expr: &'static str) -> ! {
panic_nounwind_fmt(fmt::Arguments::new_v1(&[expr], &[]));
panic_nounwind_fmt(fmt::Arguments::new_const(&[expr]));
}
#[inline]

View file

@ -22,11 +22,11 @@ fn test_pointer_formats_data_pointer() {
#[test]
fn test_estimated_capacity() {
assert_eq!(format_args!("").estimated_capacity(), 0);
assert_eq!(format_args!("{}", "").estimated_capacity(), 0);
assert_eq!(format_args!("{}", {""}).estimated_capacity(), 0);
assert_eq!(format_args!("Hello").estimated_capacity(), 5);
assert_eq!(format_args!("Hello, {}!", "").estimated_capacity(), 16);
assert_eq!(format_args!("{}, hello!", "World").estimated_capacity(), 0);
assert_eq!(format_args!("{}. 16-bytes piece", "World").estimated_capacity(), 32);
assert_eq!(format_args!("Hello, {}!", {""}).estimated_capacity(), 16);
assert_eq!(format_args!("{}, hello!", {"World"}).estimated_capacity(), 0);
assert_eq!(format_args!("{}. 16-bytes piece", {"World"}).estimated_capacity(), 32);
}
#[test]

View file

@ -533,6 +533,14 @@ struct FormatArgsValues<'tcx> {
}
impl<'tcx> FormatArgsValues<'tcx> {
fn new_empty(format_string_span: SpanData) -> Self {
Self {
value_args: Vec::new(),
pos_to_value_index: Vec::new(),
format_string_span,
}
}
fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
let mut pos_to_value_index = Vec::new();
let mut value_args = Vec::new();
@ -997,12 +1005,13 @@ pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
.find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
let newline = macro_name == sym::format_args_nl;
// ::core::fmt::Arguments::new_const(pieces)
// ::core::fmt::Arguments::new_v1(pieces, args)
// ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind
if let ExprKind::Call(callee, [pieces, rest @ ..]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
&& let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind
&& matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted")
&& matches!(seg.ident.as_str(), "new_const" | "new_v1" | "new_v1_formatted")
{
let format_string = FormatString::new(cx, pieces)?;
@ -1026,7 +1035,7 @@ pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
return None;
}
let positions = if let Some(fmt_arg) = rest.first() {
let positions = if let Some(fmt_arg) = rest.get(1) {
// If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
// them.
@ -1042,7 +1051,11 @@ pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
}))
};
let values = FormatArgsValues::new(args, format_string.span.data());
let values = if let Some(args) = rest.first() {
FormatArgsValues::new(args, format_string.span.data())
} else {
FormatArgsValues::new_empty(format_string.span.data())
};
let args = izip!(positions, parsed_args, parser.arg_places)
.map(|(position, parsed_arg, arg_span)| {

View file

@ -32,13 +32,11 @@ fn bar() ({
({
let res =
((::alloc::fmt::format as
for<'a> fn(Arguments<'a>) -> String {format})(((<#[lang = "format_arguments"]>::new_v1
for<'a> fn(Arguments<'a>) -> String {format})(((<#[lang = "format_arguments"]>::new_const
as
fn(&[&'static str], &[core::fmt::ArgumentV1<'_>]) -> Arguments<'_> {Arguments::<'_>::new_v1})((&([("test"
as &str)] as [&str; 1]) as &[&str; 1]),
(&([] as [core::fmt::ArgumentV1<'_>; 0]) as
&[core::fmt::ArgumentV1<'_>; 0])) as Arguments<'_>)) as
String);
fn(&[&'static str]) -> Arguments<'_> {Arguments::<'_>::new_const})((&([("test"
as &str)] as [&str; 1]) as &[&str; 1])) as Arguments<'_>))
as String);
(res as String)
} as String);
} as ())

View file

@ -44,6 +44,7 @@
-Z export-executable-symbols=val -- export symbols from executables, as if they were dynamic libraries
-Z extra-const-ub-checks=val -- turns on more checks to detect const UB, which can be slow (default: no)
-Z fewer-names=val -- reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) (default: no)
-Z flatten-format-args=val -- flatten nested format_args!() and literals into a simplified format_args!() call (default: no)
-Z force-unstable-if-unmarked=val -- force all crates to be `rustc_private` unstable (default: no)
-Z fuel=val -- set the optimization fuel quota for a crate
-Z function-sections=val -- whether each function should go in its own section

View file

@ -1,4 +1,4 @@
error: `Arguments::<'a>::new_v1` is not yet stable as a const fn
error: `Arguments::<'a>::new_const` is not yet stable as a const fn
--> $DIR/issue-64453.rs:4:31
|
LL | static settings_dir: String = format!("");

View file

@ -1,12 +1,13 @@
const fn failure() {
panic!("{:?}", 0);
//~^ ERROR cannot call non-const formatting macro in constant functions
//~| ERROR cannot call non-const fn `Arguments::<'_>::new_v1` in constant functions
}
const fn print() {
println!("{:?}", 0);
//~^ ERROR cannot call non-const formatting macro in constant functions
//~| ERROR `Arguments::<'a>::new_v1` is not yet stable as a const fn
//~| ERROR cannot call non-const fn `Arguments::<'_>::new_v1` in constant functions
//~| ERROR cannot call non-const fn `_print` in constant functions
}

View file

@ -7,8 +7,17 @@ LL | panic!("{:?}", 0);
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
= note: this error originates in the macro `$crate::const_format_args` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0015]: cannot call non-const fn `Arguments::<'_>::new_v1` in constant functions
--> $DIR/format.rs:2:5
|
LL | panic!("{:?}", 0);
| ^^^^^^^^^^^^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
= note: this error originates in the macro `$crate::const_format_args` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0015]: cannot call non-const formatting macro in constant functions
--> $DIR/format.rs:7:22
--> $DIR/format.rs:8:22
|
LL | println!("{:?}", 0);
| ^
@ -16,17 +25,17 @@ LL | println!("{:?}", 0);
= 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)
error: `Arguments::<'a>::new_v1` is not yet stable as a const fn
--> $DIR/format.rs:7:5
error[E0015]: cannot call non-const fn `Arguments::<'_>::new_v1` in constant functions
--> $DIR/format.rs:8:5
|
LL | println!("{:?}", 0);
| ^^^^^^^^^^^^^^^^^^^
|
= help: add `#![feature(const_fmt_arguments_new)]` to the crate attributes to enable
= 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)
error[E0015]: cannot call non-const fn `_print` in constant functions
--> $DIR/format.rs:7:5
--> $DIR/format.rs:8:5
|
LL | println!("{:?}", 0);
| ^^^^^^^^^^^^^^^^^^^
@ -63,19 +72,19 @@ LL | panic!("{:?}", 0);
= note: this note originates in the macro `$crate::const_format_args` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant used
--> $DIR/format.rs:7:14
--> $DIR/format.rs:8:14
|
LL | println!("{:?}", 0);
| ^^^^^^
note: erroneous constant used
--> $DIR/format.rs:7:14
--> $DIR/format.rs:8:14
|
LL | println!("{:?}", 0);
| ^^^^^^
note: erroneous constant used
--> $DIR/format.rs:7:22
--> $DIR/format.rs:8:22
|
LL | println!("{:?}", 0);
| ^
@ -83,13 +92,13 @@ LL | println!("{:?}", 0);
= note: this note 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: erroneous constant used
--> $DIR/format.rs:7:22
--> $DIR/format.rs:8:22
|
LL | println!("{:?}", 0);
| ^
|
= note: this note 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)
error: aborting due to 4 previous errors
error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0015`.

View file

@ -0,0 +1,8 @@
// compile-flags: -Zunpretty=hir -Zflatten-format-args=yes
// check-pass
fn main() {
let x = 1;
// Should flatten to println!("a 123 b {x} xyz\n"):
println!("a {} {}", format_args!("{} b {x}", 123), "xyz");
}

View file

@ -0,0 +1,16 @@
#[prelude_import]
use ::std::prelude::rust_2015::*;
#[macro_use]
extern crate std;
// compile-flags: -Zunpretty=hir -Zflatten-format-args=yes
// check-pass
fn main() {
let x = 1;
// Should flatten to println!("a 123 b {x} xyz\n"):
{
::std::io::_print(<#[lang = "format_arguments"]>::new_v1(&["a 123 b ",
" xyz\n"],
&[<#[lang = "format_argument"]>::new_display(&x)]));
};
}