Allow storing `format_args!()` in `let`.
This uses `super let` to allow
let f = format_args!("Hello {}", world);
println!("{f}");
to work.
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 718edad..f297bf9 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -2289,12 +2289,12 @@ pub(super) fn expr_array_ref(
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> {
diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs
index 17b443b..4138cc4 100644
--- a/compiler/rustc_ast_lowering/src/format.rs
+++ b/compiler/rustc_ast_lowering/src/format.rs
@@ -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)
- } else {
- // Generate:
- // &match (&arg0, &arg1, &…) {
- // 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 = 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:
+ // 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 = 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:
- // <core::fmt::Arguments>::new_v1_formatted(
- // lit_pieces,
- // args,
- // format_options,
- // unsafe { ::core::fmt::UnsafeArg::new() }
- // )
+ // unsafe {
+ // <core::fmt::Arguments>::new_v1_formatted(
+ // lit_pieces,
+ // args,
+ // format_options,
+ // )
+ // }
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 {
- stmts: &[],
- expr: Some(unsafe_arg_new_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)
+ hir::ExprKind::Block(
+ ctx.arena.alloc(hir::Block {
+ stmts: &[],
+ expr: Some(call),
+ hir_id,
+ rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated),
+ span: macsp,
+ targeted_by_break: false,
+ }),
+ 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)
+ };
+
+ 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 {
+ call
}
}
-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(())
- } else {
- visit::walk_expr(self, e)
- }
- }
-
- 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 };
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 3b99a65..0484081 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -2301,6 +2301,26 @@ fn stmt_let_pat(
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))
}
diff --git a/library/core/src/fmt/rt.rs b/library/core/src/fmt/rt.rs
index 7fd8d3e..fb858a0 100644
--- a/library/core/src/fmt/rt.rs
+++ b/library/core/src/fmt/rt.rs
@@ -183,38 +183,6 @@ pub(super) const fn as_u16(&self) -> Option<u16> {
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 @@ pub fn new_v1<const P: usize, const A: usize>(
/// 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 @@ pub fn new_v1<const P: usize, const A: usize>(
/// 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 }
}