Skip to content

Commit beb8d4e

Browse files
committed
Auto merge of rust-lang#140748 - m-ou-se:super-format-args3, r=<try>
Allow storing format_args!() in variable Fixes rust-lang#92698 Tracking issue for super let: rust-lang#139076 This change allows: ```rust let name = "world"; let f = format_args!("hello {name}!"); println!("{f}"); ``` This will need an FCP. This implementation makes use of `super let`, which is unstable and might not exist in the future in its current form. However, it is entirely reasonable to assume future Rust will always have _a_ way of expressing temporary lifetimes like this, since the (stable) `pin!()` macro needs this. (This was also the motivation for merging rust-lang#139114.) > [!NOTE] > This PR causes many subtle changes in diagnostics output. Most of those are good. Some of those are bad. I've collected all the bad ones in the last commit. Those still need fixing. Marking this PR as draft. (This is a second version of rust-lang#139135)
2 parents 3ef8e64 + d261de6 commit beb8d4e

File tree

72 files changed

+426
-445
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+426
-445
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -2289,12 +2289,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
22892289
span: Span,
22902290
elements: &'hir [hir::Expr<'hir>],
22912291
) -> hir::Expr<'hir> {
2292-
let addrof = hir::ExprKind::AddrOf(
2293-
hir::BorrowKind::Ref,
2294-
hir::Mutability::Not,
2295-
self.arena.alloc(self.expr(span, hir::ExprKind::Array(elements))),
2296-
);
2297-
self.expr(span, addrof)
2292+
let array = self.arena.alloc(self.expr(span, hir::ExprKind::Array(elements)));
2293+
self.expr_ref(span, array)
2294+
}
2295+
2296+
pub(super) fn expr_ref(&mut self, span: Span, expr: &'hir hir::Expr<'hir>) -> hir::Expr<'hir> {
2297+
self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, expr))
22982298
}
22992299

23002300
pub(super) fn expr(&mut self, span: Span, kind: hir::ExprKind<'hir>) -> hir::Expr<'hir> {

compiler/rustc_ast_lowering/src/format.rs

+78-129
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
use core::ops::ControlFlow;
21
use std::borrow::Cow;
32

4-
use rustc_ast::visit::Visitor;
53
use rustc_ast::*;
64
use rustc_data_structures::fx::FxIndexMap;
75
use rustc_hir as hir;
@@ -476,77 +474,52 @@ fn expand_format_args<'hir>(
476474
return hir::ExprKind::Call(new, new_args);
477475
}
478476

479-
// If the args array contains exactly all the original arguments once,
480-
// in order, we can use a simple array instead of a `match` construction.
481-
// However, if there's a yield point in any argument except the first one,
482-
// we don't do this, because an Argument cannot be kept across yield points.
483-
//
484-
// This is an optimization, speeding up compilation about 1-2% in some cases.
485-
// See https://github.com/rust-lang/rust/pull/106770#issuecomment-1380790609
486-
let use_simple_array = argmap.len() == arguments.len()
487-
&& argmap.iter().enumerate().all(|(i, (&(j, _), _))| i == j)
488-
&& arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr));
489-
490-
let args = if arguments.is_empty() {
477+
let (let_statements, args) = if arguments.is_empty() {
491478
// Generate:
492-
// &<core::fmt::Argument>::none()
479+
// []
480+
(vec![], ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(&[]))))
481+
} else if argmap.len() == 1 && arguments.len() == 1 {
482+
// Only one argument, so we don't need to make the `args` tuple.
493483
//
494-
// Note:
495-
// `none()` just returns `[]`. We use `none()` rather than `[]` to limit the lifetime.
496-
//
497-
// This makes sure that this still fails to compile, even when the argument is inlined:
498-
//
499-
// ```
500-
// let f = format_args!("{}", "a");
501-
// println!("{f}"); // error E0716
502-
// ```
503-
//
504-
// Cases where keeping the object around is allowed, such as `format_args!("a")`,
505-
// are handled above by the `allow_const` case.
506-
let none_fn = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
507-
macsp,
508-
hir::LangItem::FormatArgument,
509-
sym::none,
510-
));
511-
let none = ctx.expr_call(macsp, none_fn, &[]);
512-
ctx.expr(macsp, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, none))
513-
} else if use_simple_array {
514484
// Generate:
515-
// &[
516-
// <core::fmt::Argument>::new_display(&arg0),
517-
// <core::fmt::Argument>::new_lower_hex(&arg1),
518-
// <core::fmt::Argument>::new_debug(&arg2),
519-
// …
520-
// ]
521-
let elements = ctx.arena.alloc_from_iter(arguments.iter().zip(argmap).map(
522-
|(arg, ((_, ty), placeholder_span))| {
485+
// super let args = [<core::fmt::Argument>::new_display(&arg)];
486+
let args = ctx.arena.alloc_from_iter(argmap.iter().map(
487+
|(&(arg_index, ty), &placeholder_span)| {
488+
let arg = &arguments[arg_index];
523489
let placeholder_span =
524490
placeholder_span.unwrap_or(arg.expr.span).with_ctxt(macsp.ctxt());
525-
let arg_span = match arg.kind {
526-
FormatArgumentKind::Captured(_) => placeholder_span,
527-
_ => arg.expr.span.with_ctxt(macsp.ctxt()),
528-
};
529491
let arg = ctx.lower_expr(&arg.expr);
530-
let ref_arg = ctx.arena.alloc(ctx.expr(
531-
arg_span,
532-
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg),
533-
));
492+
let ref_arg = ctx.arena.alloc(ctx.expr_ref(arg.span, arg));
534493
make_argument(ctx, placeholder_span, ref_arg, ty)
535494
},
536495
));
537-
ctx.expr_array_ref(macsp, elements)
496+
let args = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
497+
let args_ident = Ident::new(sym::args, macsp);
498+
let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
499+
let let_statement = ctx.stmt_super_let_pat(macsp, args_pat, Some(args));
500+
(vec![let_statement], ctx.arena.alloc(ctx.expr_ident_mut(macsp, args_ident, args_hir_id)))
538501
} else {
539502
// Generate:
540-
// &match (&arg0, &arg1, &…) {
541-
// args => [
542-
// <core::fmt::Argument>::new_display(args.0),
543-
// <core::fmt::Argument>::new_lower_hex(args.1),
544-
// <core::fmt::Argument>::new_debug(args.0),
545-
// …
546-
// ]
547-
// }
503+
// super let args = (&arg0, &arg1, &…);
548504
let args_ident = Ident::new(sym::args, macsp);
549505
let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
506+
let elements = ctx.arena.alloc_from_iter(arguments.iter().map(|arg| {
507+
let arg_expr = ctx.lower_expr(&arg.expr);
508+
ctx.expr(
509+
arg.expr.span.with_ctxt(macsp.ctxt()),
510+
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg_expr),
511+
)
512+
}));
513+
let args_tuple = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Tup(elements)));
514+
let let_statement_1 = ctx.stmt_super_let_pat(macsp, args_pat, Some(args_tuple));
515+
516+
// Generate:
517+
// super let args = [
518+
// <core::fmt::Argument>::new_display(args.0),
519+
// <core::fmt::Argument>::new_lower_hex(args.1),
520+
// <core::fmt::Argument>::new_debug(args.0),
521+
// …
522+
// ];
550523
let args = ctx.arena.alloc_from_iter(argmap.iter().map(
551524
|(&(arg_index, ty), &placeholder_span)| {
552525
let arg = &arguments[arg_index];
@@ -567,58 +540,48 @@ fn expand_format_args<'hir>(
567540
make_argument(ctx, placeholder_span, arg, ty)
568541
},
569542
));
570-
let elements = ctx.arena.alloc_from_iter(arguments.iter().map(|arg| {
571-
let arg_expr = ctx.lower_expr(&arg.expr);
572-
ctx.expr(
573-
arg.expr.span.with_ctxt(macsp.ctxt()),
574-
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg_expr),
575-
)
576-
}));
577-
let args_tuple = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Tup(elements)));
578-
let array = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
579-
let match_arms = ctx.arena.alloc_from_iter([ctx.arm(args_pat, array)]);
580-
let match_expr = ctx.arena.alloc(ctx.expr_match(
581-
macsp,
582-
args_tuple,
583-
match_arms,
584-
hir::MatchSource::FormatArgs,
585-
));
586-
ctx.expr(
587-
macsp,
588-
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, match_expr),
543+
let args = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
544+
let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
545+
let let_statement_2 = ctx.stmt_super_let_pat(macsp, args_pat, Some(args));
546+
(
547+
vec![let_statement_1, let_statement_2],
548+
ctx.arena.alloc(ctx.expr_ident_mut(macsp, args_ident, args_hir_id)),
589549
)
590550
};
591551

592-
if let Some(format_options) = format_options {
552+
// Generate:
553+
// &args
554+
let args =
555+
ctx.expr(macsp, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, args));
556+
557+
let call = if let Some(format_options) = format_options {
593558
// Generate:
594-
// <core::fmt::Arguments>::new_v1_formatted(
595-
// lit_pieces,
596-
// args,
597-
// format_options,
598-
// unsafe { ::core::fmt::UnsafeArg::new() }
599-
// )
559+
// unsafe {
560+
// <core::fmt::Arguments>::new_v1_formatted(
561+
// lit_pieces,
562+
// args,
563+
// format_options,
564+
// )
565+
// }
600566
let new_v1_formatted = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
601567
macsp,
602568
hir::LangItem::FormatArguments,
603569
sym::new_v1_formatted,
604570
));
605-
let unsafe_arg_new = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
606-
macsp,
607-
hir::LangItem::FormatUnsafeArg,
608-
sym::new,
609-
));
610-
let unsafe_arg_new_call = ctx.expr_call(macsp, unsafe_arg_new, &[]);
571+
let args = ctx.arena.alloc_from_iter([lit_pieces, args, format_options]);
572+
let call = ctx.expr_call(macsp, new_v1_formatted, args);
611573
let hir_id = ctx.next_id();
612-
let unsafe_arg = ctx.expr_block(ctx.arena.alloc(hir::Block {
613-
stmts: &[],
614-
expr: Some(unsafe_arg_new_call),
615-
hir_id,
616-
rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated),
617-
span: macsp,
618-
targeted_by_break: false,
619-
}));
620-
let args = ctx.arena.alloc_from_iter([lit_pieces, args, format_options, unsafe_arg]);
621-
hir::ExprKind::Call(new_v1_formatted, args)
574+
hir::ExprKind::Block(
575+
ctx.arena.alloc(hir::Block {
576+
stmts: &[],
577+
expr: Some(call),
578+
hir_id,
579+
rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated),
580+
span: macsp,
581+
targeted_by_break: false,
582+
}),
583+
None,
584+
)
622585
} else {
623586
// Generate:
624587
// <core::fmt::Arguments>::new_v1(
@@ -632,35 +595,21 @@ fn expand_format_args<'hir>(
632595
));
633596
let new_args = ctx.arena.alloc_from_iter([lit_pieces, args]);
634597
hir::ExprKind::Call(new_v1, new_args)
635-
}
636-
}
637-
638-
fn may_contain_yield_point(e: &ast::Expr) -> bool {
639-
struct MayContainYieldPoint;
640-
641-
impl Visitor<'_> for MayContainYieldPoint {
642-
type Result = ControlFlow<()>;
643-
644-
fn visit_expr(&mut self, e: &ast::Expr) -> ControlFlow<()> {
645-
if let ast::ExprKind::Await(_, _) | ast::ExprKind::Yield(_) = e.kind {
646-
ControlFlow::Break(())
647-
} else {
648-
visit::walk_expr(self, e)
649-
}
650-
}
651-
652-
fn visit_mac_call(&mut self, _: &ast::MacCall) -> ControlFlow<()> {
653-
// Macros should be expanded at this point.
654-
unreachable!("unexpanded macro in ast lowering");
655-
}
598+
};
656599

657-
fn visit_item(&mut self, _: &ast::Item) -> ControlFlow<()> {
658-
// Do not recurse into nested items.
659-
ControlFlow::Continue(())
660-
}
600+
if !let_statements.is_empty() {
601+
// Generate:
602+
// {
603+
// super let …
604+
// super let …
605+
// <core::fmt::Arguments>::new_…(…)
606+
// }
607+
let call = ctx.arena.alloc(ctx.expr(macsp, call));
608+
let block = ctx.block_all(macsp, ctx.arena.alloc_from_iter(let_statements), Some(call));
609+
hir::ExprKind::Block(block, None)
610+
} else {
611+
call
661612
}
662-
663-
MayContainYieldPoint.visit_expr(e).is_break()
664613
}
665614

666615
fn for_all_argument_indexes(template: &mut [FormatArgsPiece], mut f: impl FnMut(&mut usize)) {

compiler/rustc_ast_lowering/src/lib.rs

+20
Original file line numberDiff line numberDiff line change
@@ -2261,6 +2261,26 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
22612261
self.stmt(span, hir::StmtKind::Let(self.arena.alloc(local)))
22622262
}
22632263

2264+
fn stmt_super_let_pat(
2265+
&mut self,
2266+
span: Span,
2267+
pat: &'hir hir::Pat<'hir>,
2268+
init: Option<&'hir hir::Expr<'hir>>,
2269+
) -> hir::Stmt<'hir> {
2270+
let hir_id = self.next_id();
2271+
let local = hir::LetStmt {
2272+
super_: Some(span),
2273+
hir_id,
2274+
init,
2275+
pat,
2276+
els: None,
2277+
source: hir::LocalSource::Normal,
2278+
span: self.lower_span(span),
2279+
ty: None,
2280+
};
2281+
self.stmt(span, hir::StmtKind::Let(self.arena.alloc(local)))
2282+
}
2283+
22642284
fn block_expr(&mut self, expr: &'hir hir::Expr<'hir>) -> &'hir hir::Block<'hir> {
22652285
self.block_all(expr.span, &[], Some(expr))
22662286
}

library/core/src/fmt/rt.rs

+18-15
Original file line numberDiff line numberDiff line change
@@ -192,30 +192,22 @@ impl Argument<'_> {
192192
}
193193
}
194194

195-
/// Used by `format_args` when all arguments are gone after inlining,
196-
/// when using `&[]` would incorrectly allow for a bigger lifetime.
197-
///
198-
/// This fails without format argument inlining, and that shouldn't be different
199-
/// when the argument is inlined:
200-
///
201-
/// ```compile_fail,E0716
202-
/// let f = format_args!("{}", "a");
203-
/// println!("{f}");
204-
/// ```
195+
/// Bootstrap only.
205196
#[inline]
197+
#[cfg(bootstrap)]
206198
pub const fn none() -> [Self; 0] {
207199
[]
208200
}
209201
}
210202

211-
/// This struct represents the unsafety of constructing an `Arguments`.
212-
/// It exists, rather than an unsafe function, in order to simplify the expansion
213-
/// of `format_args!(..)` and reduce the scope of the `unsafe` block.
203+
/// Bootstrap only.
204+
#[cfg(bootstrap)]
214205
#[lang = "format_unsafe_arg"]
215206
pub struct UnsafeArg {
216207
_private: (),
217208
}
218209

210+
#[cfg(bootstrap)]
219211
impl UnsafeArg {
220212
/// See documentation where `UnsafeArg` is required to know when it is safe to
221213
/// create and use `UnsafeArg`.
@@ -256,8 +248,7 @@ impl<'a> Arguments<'a> {
256248

257249
/// Specifies nonstandard formatting parameters.
258250
///
259-
/// An `rt::UnsafeArg` is required because the following invariants must be held
260-
/// in order for this function to be safe:
251+
/// SAFETY: the following invariants must be held:
261252
/// 1. The `pieces` slice must be at least as long as `fmt`.
262253
/// 2. Every `rt::Placeholder::position` value within `fmt` must be a valid index of `args`.
263254
/// 3. Every `rt::Count::Param` within `fmt` must contain a valid index of `args`.
@@ -269,6 +260,18 @@ impl<'a> Arguments<'a> {
269260
/// const _: () = if false { panic!("a {:1}", "a") };
270261
/// ```
271262
#[inline]
263+
#[cfg(not(bootstrap))]
264+
pub unsafe fn new_v1_formatted(
265+
pieces: &'a [&'static str],
266+
args: &'a [rt::Argument<'a>],
267+
fmt: &'a [rt::Placeholder],
268+
) -> Arguments<'a> {
269+
Arguments { pieces, fmt: Some(fmt), args }
270+
}
271+
272+
/// Bootstrap only.
273+
#[inline]
274+
#[cfg(bootstrap)]
272275
pub fn new_v1_formatted(
273276
pieces: &'a [&'static str],
274277
args: &'a [rt::Argument<'a>],

library/coretests/tests/fmt/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@ mod builders;
22
mod float;
33
mod num;
44

5+
#[test]
6+
#[cfg(not(bootstrap))]
7+
fn test_lifetime() {
8+
// Trigger all different forms of expansion,
9+
// and check that each of them can be stored as a variable.
10+
let a = format_args!("hello");
11+
let a = format_args!("hello {a}");
12+
let a = format_args!("hello {a:1}");
13+
let a = format_args!("hello {a} {a:?}");
14+
assert_eq!(a.to_string(), "hello hello hello hello hello hello hello");
15+
16+
// Without arguments, it should also work in consts.
17+
const A: std::fmt::Arguments<'static> = format_args!("hello");
18+
assert_eq!(A.to_string(), "hello");
19+
}
20+
521
#[test]
622
fn test_format_flags() {
723
// No residual flags left by pointer formatting

0 commit comments

Comments
 (0)