Allow storing format_args!() in let.

This uses `super let` to allow

    let f = format_args!("Hello {}", world);
    println!("{f}");

to work.
This commit is contained in:
Mara Bos
2025-05-07 17:40:30 +02:00
parent 27733d46d7
commit 8ff2c1a97e
4 changed files with 109 additions and 174 deletions

View File

@@ -2289,12 +2289,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
span: Span,
elements: &'hir [hir::Expr<'hir>],
) -> hir::Expr<'hir> {
let addrof = hir::ExprKind::AddrOf(
hir::BorrowKind::Ref,
hir::Mutability::Not,
self.arena.alloc(self.expr(span, hir::ExprKind::Array(elements))),
);
self.expr(span, addrof)
let array = self.arena.alloc(self.expr(span, hir::ExprKind::Array(elements)));
self.expr_ref(span, array)
}
pub(super) fn expr_ref(&mut self, span: Span, expr: &'hir hir::Expr<'hir>) -> hir::Expr<'hir> {
self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, expr))
}
pub(super) fn expr(&mut self, span: Span, kind: hir::ExprKind<'hir>) -> hir::Expr<'hir> {

View File

@@ -1,7 +1,5 @@
use core::ops::ControlFlow;
use std::borrow::Cow;
use rustc_ast::visit::Visitor;
use rustc_ast::*;
use rustc_data_structures::fx::FxIndexMap;
use rustc_hir as hir;
@@ -476,77 +474,52 @@ fn expand_format_args<'hir>(
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,
// we don't do this, because an Argument cannot be kept across yield points.
//
// This is an optimization, speeding up compilation about 1-2% in some cases.
// See https://github.com/rust-lang/rust/pull/106770#issuecomment-1380790609
let use_simple_array = argmap.len() == arguments.len()
&& argmap.iter().enumerate().all(|(i, (&(j, _), _))| i == j)
&& arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr));
let args = if arguments.is_empty() {
let (let_statements, args) = if arguments.is_empty() {
// Generate:
// &<core::fmt::Argument>::none()
// []
(vec![], ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(&[]))))
} else if argmap.len() == 1 && arguments.len() == 1 {
// Only one argument, so we don't need to make the `args` tuple.
//
// Note:
// `none()` just returns `[]`. We use `none()` rather than `[]` to limit the lifetime.
//
// This makes sure that this still fails to compile, even when the argument is inlined:
//
// ```
// let f = format_args!("{}", "a");
// println!("{f}"); // error E0716
// ```
//
// Cases where keeping the object around is allowed, such as `format_args!("a")`,
// are handled above by the `allow_const` case.
let none_fn = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
macsp,
hir::LangItem::FormatArgument,
sym::none,
));
let none = ctx.expr_call(macsp, none_fn, &[]);
ctx.expr(macsp, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, none))
} else if use_simple_array {
// Generate:
// &[
// <core::fmt::Argument>::new_display(&arg0),
// <core::fmt::Argument>::new_lower_hex(&arg1),
// <core::fmt::Argument>::new_debug(&arg2),
// …
// ]
let elements = ctx.arena.alloc_from_iter(arguments.iter().zip(argmap).map(
|(arg, ((_, ty), placeholder_span))| {
// super let args = [<core::fmt::Argument>::new_display(&arg)];
let args = ctx.arena.alloc_from_iter(argmap.iter().map(
|(&(arg_index, ty), &placeholder_span)| {
let arg = &arguments[arg_index];
let placeholder_span =
placeholder_span.unwrap_or(arg.expr.span).with_ctxt(macsp.ctxt());
let arg_span = match arg.kind {
FormatArgumentKind::Captured(_) => placeholder_span,
_ => arg.expr.span.with_ctxt(macsp.ctxt()),
};
let arg = ctx.lower_expr(&arg.expr);
let ref_arg = ctx.arena.alloc(ctx.expr(
arg_span,
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg),
));
let ref_arg = ctx.arena.alloc(ctx.expr_ref(arg.span, arg));
make_argument(ctx, placeholder_span, ref_arg, ty)
},
));
ctx.expr_array_ref(macsp, elements)
let args = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
let args_ident = Ident::new(sym::args, macsp);
let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
let let_statement = ctx.stmt_super_let_pat(macsp, args_pat, Some(args));
(vec![let_statement], ctx.arena.alloc(ctx.expr_ident_mut(macsp, args_ident, args_hir_id)))
} else {
// Generate:
// &match (&arg0, &arg1, &…) {
// args => [
// super let args = (&arg0, &arg1, &…);
let args_ident = Ident::new(sym::args, macsp);
let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
let elements = ctx.arena.alloc_from_iter(arguments.iter().map(|arg| {
let arg_expr = ctx.lower_expr(&arg.expr);
ctx.expr(
arg.expr.span.with_ctxt(macsp.ctxt()),
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg_expr),
)
}));
let args_tuple = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Tup(elements)));
let let_statement_1 = ctx.stmt_super_let_pat(macsp, args_pat, Some(args_tuple));
// Generate:
// super let args = [
// <core::fmt::Argument>::new_display(args.0),
// <core::fmt::Argument>::new_lower_hex(args.1),
// <core::fmt::Argument>::new_debug(args.0),
// …
// ]
// }
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), &placeholder_span)| {
let arg = &arguments[arg_index];
@@ -567,58 +540,48 @@ fn expand_format_args<'hir>(
make_argument(ctx, placeholder_span, arg, ty)
},
));
let elements = ctx.arena.alloc_from_iter(arguments.iter().map(|arg| {
let arg_expr = ctx.lower_expr(&arg.expr);
ctx.expr(
arg.expr.span.with_ctxt(macsp.ctxt()),
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg_expr),
)
}));
let args_tuple = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Tup(elements)));
let array = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
let match_arms = ctx.arena.alloc_from_iter([ctx.arm(args_pat, array)]);
let match_expr = ctx.arena.alloc(ctx.expr_match(
macsp,
args_tuple,
match_arms,
hir::MatchSource::FormatArgs,
));
ctx.expr(
macsp,
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, match_expr),
let args = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
let let_statement_2 = ctx.stmt_super_let_pat(macsp, args_pat, Some(args));
(
vec![let_statement_1, let_statement_2],
ctx.arena.alloc(ctx.expr_ident_mut(macsp, args_ident, args_hir_id)),
)
};
if let Some(format_options) = format_options {
// Generate:
// &args
let args =
ctx.expr(macsp, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, args));
let call = if let Some(format_options) = format_options {
// Generate:
// unsafe {
// <core::fmt::Arguments>::new_v1_formatted(
// lit_pieces,
// args,
// format_options,
// unsafe { ::core::fmt::UnsafeArg::new() }
// )
// }
let new_v1_formatted = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
macsp,
hir::LangItem::FormatArguments,
sym::new_v1_formatted,
));
let unsafe_arg_new = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
macsp,
hir::LangItem::FormatUnsafeArg,
sym::new,
));
let unsafe_arg_new_call = ctx.expr_call(macsp, unsafe_arg_new, &[]);
let args = ctx.arena.alloc_from_iter([lit_pieces, args, format_options]);
let call = ctx.expr_call(macsp, new_v1_formatted, args);
let hir_id = ctx.next_id();
let unsafe_arg = ctx.expr_block(ctx.arena.alloc(hir::Block {
hir::ExprKind::Block(
ctx.arena.alloc(hir::Block {
stmts: &[],
expr: Some(unsafe_arg_new_call),
expr: Some(call),
hir_id,
rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated),
span: macsp,
targeted_by_break: false,
}));
let args = ctx.arena.alloc_from_iter([lit_pieces, args, format_options, unsafe_arg]);
hir::ExprKind::Call(new_v1_formatted, args)
}),
None,
)
} else {
// Generate:
// <core::fmt::Arguments>::new_v1(
@@ -632,37 +595,23 @@ fn expand_format_args<'hir>(
));
let new_args = ctx.arena.alloc_from_iter([lit_pieces, args]);
hir::ExprKind::Call(new_v1, new_args)
}
}
};
fn may_contain_yield_point(e: &ast::Expr) -> bool {
struct MayContainYieldPoint;
impl Visitor<'_> for MayContainYieldPoint {
type Result = ControlFlow<()>;
fn visit_expr(&mut self, e: &ast::Expr) -> ControlFlow<()> {
if let ast::ExprKind::Await(_, _) | ast::ExprKind::Yield(_) = e.kind {
ControlFlow::Break(())
if !let_statements.is_empty() {
// Generate:
// {
// super let …
// super let …
// <core::fmt::Arguments>::new_…(…)
// }
let call = ctx.arena.alloc(ctx.expr(macsp, call));
let block = ctx.block_all(macsp, ctx.arena.alloc_from_iter(let_statements), Some(call));
hir::ExprKind::Block(block, None)
} else {
visit::walk_expr(self, e)
call
}
}
fn visit_mac_call(&mut self, _: &ast::MacCall) -> ControlFlow<()> {
// Macros should be expanded at this point.
unreachable!("unexpanded macro in ast lowering");
}
fn visit_item(&mut self, _: &ast::Item) -> ControlFlow<()> {
// Do not recurse into nested items.
ControlFlow::Continue(())
}
}
MayContainYieldPoint.visit_expr(e).is_break()
}
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 };

View File

@@ -2301,6 +2301,26 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
self.stmt(span, hir::StmtKind::Let(self.arena.alloc(local)))
}
fn stmt_super_let_pat(
&mut self,
span: Span,
pat: &'hir hir::Pat<'hir>,
init: Option<&'hir hir::Expr<'hir>>,
) -> hir::Stmt<'hir> {
let hir_id = self.next_id();
let local = hir::LetStmt {
super_: Some(span),
hir_id,
init,
pat,
els: None,
source: hir::LocalSource::Normal,
span: self.lower_span(span),
ty: None,
};
self.stmt(span, hir::StmtKind::Let(self.arena.alloc(local)))
}
fn block_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> &'hir hir::Block<'hir> {
self.block_all(expr.span, &[], Some(expr))
}

View File

@@ -183,38 +183,6 @@ impl Argument<'_> {
ArgumentType::Placeholder { .. } => None,
}
}
/// Used by `format_args` when all arguments are gone after inlining,
/// when using `&[]` would incorrectly allow for a bigger lifetime.
///
/// This fails without format argument inlining, and that shouldn't be different
/// when the argument is inlined:
///
/// ```compile_fail,E0716
/// let f = format_args!("{}", "a");
/// println!("{f}");
/// ```
#[inline]
pub const fn none() -> [Self; 0] {
[]
}
}
/// This struct represents the unsafety of constructing an `Arguments`.
/// It exists, rather than an unsafe function, in order to simplify the expansion
/// of `format_args!(..)` and reduce the scope of the `unsafe` block.
#[lang = "format_unsafe_arg"]
pub struct UnsafeArg {
_private: (),
}
impl UnsafeArg {
/// See documentation where `UnsafeArg` is required to know when it is safe to
/// create and use `UnsafeArg`.
#[inline]
pub const unsafe fn new() -> Self {
Self { _private: () }
}
}
/// Used by the format_args!() macro to create a fmt::Arguments object.
@@ -248,8 +216,7 @@ impl<'a> Arguments<'a> {
/// Specifies nonstandard formatting parameters.
///
/// An `rt::UnsafeArg` is required because the following invariants must be held
/// in order for this function to be safe:
/// SAFETY: the following invariants must be held:
/// 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`.
@@ -261,11 +228,10 @@ impl<'a> Arguments<'a> {
/// const _: () = if false { panic!("a {:1}", "a") };
/// ```
#[inline]
pub fn new_v1_formatted(
pub unsafe 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 }
}