rustc_ast_lowering/
format.rs

1use std::borrow::Cow;
2
3use rustc_ast::*;
4use rustc_data_structures::fx::FxIndexMap;
5use rustc_hir as hir;
6use rustc_session::config::FmtDebug;
7use rustc_span::{DesugaringKind, Ident, Span, Symbol, sym};
8
9use super::LoweringContext;
10
11impl<'hir> LoweringContext<'_, 'hir> {
12    pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> {
13        // Never call the const constructor of `fmt::Arguments` if the
14        // format_args!() had any arguments _before_ flattening/inlining.
15        let allow_const = fmt.arguments.all_args().is_empty();
16        let mut fmt = Cow::Borrowed(fmt);
17
18        let sp = self.mark_span_with_reason(
19            DesugaringKind::FormatLiteral { source: fmt.is_source_literal },
20            sp,
21            sp.ctxt().outer_expn_data().allow_internal_unstable,
22        );
23
24        if self.tcx.sess.opts.unstable_opts.flatten_format_args {
25            fmt = flatten_format_args(fmt);
26            fmt = self.inline_literals(fmt);
27        }
28        expand_format_args(self, sp, &fmt, allow_const)
29    }
30
31    /// Try to convert a literal into an interned string
32    fn try_inline_lit(&self, lit: token::Lit) -> Option<Symbol> {
33        match LitKind::from_token_lit(lit) {
34            Ok(LitKind::Str(s, _)) => Some(s),
35            Ok(LitKind::Int(n, ty)) => {
36                match ty {
37                    // unsuffixed integer literals are assumed to be i32's
38                    LitIntType::Unsuffixed => {
39                        (n <= i32::MAX as u128).then_some(Symbol::intern(&n.to_string()))
40                    }
41                    LitIntType::Signed(int_ty) => {
42                        let max_literal = self.int_ty_max(int_ty);
43                        (n <= max_literal).then_some(Symbol::intern(&n.to_string()))
44                    }
45                    LitIntType::Unsigned(uint_ty) => {
46                        let max_literal = self.uint_ty_max(uint_ty);
47                        (n <= max_literal).then_some(Symbol::intern(&n.to_string()))
48                    }
49                }
50            }
51            _ => None,
52        }
53    }
54
55    /// Get the maximum value of int_ty. It is platform-dependent due to the byte size of isize
56    fn int_ty_max(&self, int_ty: IntTy) -> u128 {
57        match int_ty {
58            IntTy::Isize => self.tcx.data_layout.pointer_size.signed_int_max() as u128,
59            IntTy::I8 => i8::MAX as u128,
60            IntTy::I16 => i16::MAX as u128,
61            IntTy::I32 => i32::MAX as u128,
62            IntTy::I64 => i64::MAX as u128,
63            IntTy::I128 => i128::MAX as u128,
64        }
65    }
66
67    /// Get the maximum value of uint_ty. It is platform-dependent due to the byte size of usize
68    fn uint_ty_max(&self, uint_ty: UintTy) -> u128 {
69        match uint_ty {
70            UintTy::Usize => self.tcx.data_layout.pointer_size.unsigned_int_max(),
71            UintTy::U8 => u8::MAX as u128,
72            UintTy::U16 => u16::MAX as u128,
73            UintTy::U32 => u32::MAX as u128,
74            UintTy::U64 => u64::MAX as u128,
75            UintTy::U128 => u128::MAX as u128,
76        }
77    }
78
79    /// Inline literals into the format string.
80    ///
81    /// Turns
82    ///
83    /// `format_args!("Hello, {}! {} {}", "World", 123, x)`
84    ///
85    /// into
86    ///
87    /// `format_args!("Hello, World! 123 {}", x)`.
88    fn inline_literals<'fmt>(&self, mut fmt: Cow<'fmt, FormatArgs>) -> Cow<'fmt, FormatArgs> {
89        let mut was_inlined = vec![false; fmt.arguments.all_args().len()];
90        let mut inlined_anything = false;
91
92        for i in 0..fmt.template.len() {
93            let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue };
94            let Ok(arg_index) = placeholder.argument.index else { continue };
95
96            let mut literal = None;
97
98            if let FormatTrait::Display = placeholder.format_trait
99                && placeholder.format_options == Default::default()
100                && let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs()
101                && let ExprKind::Lit(lit) = arg.kind
102            {
103                literal = self.try_inline_lit(lit);
104            }
105
106            if let Some(literal) = literal {
107                // Now we need to mutate the outer FormatArgs.
108                // If this is the first time, this clones the outer FormatArgs.
109                let fmt = fmt.to_mut();
110                // Replace the placeholder with the literal.
111                fmt.template[i] = FormatArgsPiece::Literal(literal);
112                was_inlined[arg_index] = true;
113                inlined_anything = true;
114            }
115        }
116
117        // Remove the arguments that were inlined.
118        if inlined_anything {
119            let fmt = fmt.to_mut();
120
121            let mut remove = was_inlined;
122
123            // Don't remove anything that's still used.
124            for_all_argument_indexes(&mut fmt.template, |index| remove[*index] = false);
125
126            // Drop all the arguments that are marked for removal.
127            let mut remove_it = remove.iter();
128            fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&true));
129
130            // Calculate the mapping of old to new indexes for the remaining arguments.
131            let index_map: Vec<usize> = remove
132                .into_iter()
133                .scan(0, |i, remove| {
134                    let mapped = *i;
135                    *i += !remove as usize;
136                    Some(mapped)
137                })
138                .collect();
139
140            // Correct the indexes that refer to arguments that have shifted position.
141            for_all_argument_indexes(&mut fmt.template, |index| *index = index_map[*index]);
142        }
143
144        fmt
145    }
146}
147
148/// Flattens nested `format_args!()` into one.
149///
150/// Turns
151///
152/// `format_args!("a {} {} {}.", 1, format_args!("b{}!", 2), 3)`
153///
154/// into
155///
156/// `format_args!("a {} b{}! {}.", 1, 2, 3)`.
157fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> {
158    let mut i = 0;
159    while i < fmt.template.len() {
160        if let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i]
161            && let FormatTrait::Display | FormatTrait::Debug = &placeholder.format_trait
162            && let Ok(arg_index) = placeholder.argument.index
163            && let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs()
164            && let ExprKind::FormatArgs(_) = &arg.kind
165            // Check that this argument is not used by any other placeholders.
166            && fmt.template.iter().enumerate().all(|(j, p)|
167                i == j ||
168                !matches!(p, FormatArgsPiece::Placeholder(placeholder)
169                    if placeholder.argument.index == Ok(arg_index))
170            )
171        {
172            // Now we need to mutate the outer FormatArgs.
173            // If this is the first time, this clones the outer FormatArgs.
174            let fmt = fmt.to_mut();
175
176            // Take the inner FormatArgs out of the outer arguments, and
177            // replace it by the inner arguments. (We can't just put those at
178            // the end, because we need to preserve the order of evaluation.)
179
180            let args = fmt.arguments.all_args_mut();
181            let remaining_args = args.split_off(arg_index + 1);
182            let old_arg_offset = args.len();
183            let mut fmt2 = &mut args.pop().unwrap().expr; // The inner FormatArgs.
184            let fmt2 = loop {
185                // Unwrap the Expr to get to the FormatArgs.
186                match &mut fmt2.kind {
187                    ExprKind::Paren(inner) | ExprKind::AddrOf(BorrowKind::Ref, _, inner) => {
188                        fmt2 = inner
189                    }
190                    ExprKind::FormatArgs(fmt2) => break fmt2,
191                    _ => unreachable!(),
192                }
193            };
194
195            args.append(fmt2.arguments.all_args_mut());
196            let new_arg_offset = args.len();
197            args.extend(remaining_args);
198
199            // Correct the indexes that refer to the arguments after the newly inserted arguments.
200            for_all_argument_indexes(&mut fmt.template, |index| {
201                if *index >= old_arg_offset {
202                    *index -= old_arg_offset;
203                    *index += new_arg_offset;
204                }
205            });
206
207            // Now merge the placeholders:
208
209            let rest = fmt.template.split_off(i + 1);
210            fmt.template.pop(); // remove the placeholder for the nested fmt args.
211            // Insert the pieces from the nested format args, but correct any
212            // placeholders to point to the correct argument index.
213            for_all_argument_indexes(&mut fmt2.template, |index| *index += arg_index);
214            fmt.template.append(&mut fmt2.template);
215            fmt.template.extend(rest);
216
217            // Don't increment `i` here, so we recurse into the newly added pieces.
218        } else {
219            i += 1;
220        }
221    }
222    fmt
223}
224
225#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
226enum ArgumentType {
227    Format(FormatTrait),
228    Usize,
229}
230
231/// Generate a hir expression representing an argument to a format_args invocation.
232///
233/// Generates:
234///
235/// ```text
236///     <core::fmt::Argument>::new_…(arg)
237/// ```
238fn make_argument<'hir>(
239    ctx: &mut LoweringContext<'_, 'hir>,
240    sp: Span,
241    arg: &'hir hir::Expr<'hir>,
242    ty: ArgumentType,
243) -> hir::Expr<'hir> {
244    use ArgumentType::*;
245    use FormatTrait::*;
246    let new_fn = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
247        sp,
248        hir::LangItem::FormatArgument,
249        match ty {
250            Format(Display) => sym::new_display,
251            Format(Debug) => match ctx.tcx.sess.opts.unstable_opts.fmt_debug {
252                FmtDebug::Full | FmtDebug::Shallow => sym::new_debug,
253                FmtDebug::None => sym::new_debug_noop,
254            },
255            Format(LowerExp) => sym::new_lower_exp,
256            Format(UpperExp) => sym::new_upper_exp,
257            Format(Octal) => sym::new_octal,
258            Format(Pointer) => sym::new_pointer,
259            Format(Binary) => sym::new_binary,
260            Format(LowerHex) => sym::new_lower_hex,
261            Format(UpperHex) => sym::new_upper_hex,
262            Usize => sym::from_usize,
263        },
264    ));
265    ctx.expr_call_mut(sp, new_fn, std::slice::from_ref(arg))
266}
267
268/// Generate a hir expression for a format_args Count.
269///
270/// Generates:
271///
272/// ```text
273///     <core::fmt::rt::Count>::Is(…)
274/// ```
275///
276/// or
277///
278/// ```text
279///     <core::fmt::rt::Count>::Param(…)
280/// ```
281///
282/// or
283///
284/// ```text
285///     <core::fmt::rt::Count>::Implied
286/// ```
287fn make_count<'hir>(
288    ctx: &mut LoweringContext<'_, 'hir>,
289    sp: Span,
290    count: &Option<FormatCount>,
291    argmap: &mut FxIndexMap<(usize, ArgumentType), Option<Span>>,
292) -> hir::Expr<'hir> {
293    match count {
294        Some(FormatCount::Literal(n)) => {
295            let count_is = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
296                sp,
297                hir::LangItem::FormatCount,
298                sym::Is,
299            ));
300            let value = ctx.arena.alloc_from_iter([ctx.expr_u16(sp, *n)]);
301            ctx.expr_call_mut(sp, count_is, value)
302        }
303        Some(FormatCount::Argument(arg)) => {
304            if let Ok(arg_index) = arg.index {
305                let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize), arg.span);
306                let count_param = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
307                    sp,
308                    hir::LangItem::FormatCount,
309                    sym::Param,
310                ));
311                let value = ctx.arena.alloc_from_iter([ctx.expr_usize(sp, i)]);
312                ctx.expr_call_mut(sp, count_param, value)
313            } else {
314                ctx.expr(
315                    sp,
316                    hir::ExprKind::Err(
317                        ctx.dcx().span_delayed_bug(sp, "lowered bad format_args count"),
318                    ),
319                )
320            }
321        }
322        None => ctx.expr_lang_item_type_relative(sp, hir::LangItem::FormatCount, sym::Implied),
323    }
324}
325
326/// Generate a hir expression for a format_args placeholder specification.
327///
328/// Generates
329///
330/// ```text
331///     <core::fmt::rt::Placeholder {
332///         position: …usize,
333///         flags: …u32,
334///         precision: <core::fmt::rt::Count::…>,
335///         width: <core::fmt::rt::Count::…>,
336///     }
337/// ```
338fn make_format_spec<'hir>(
339    ctx: &mut LoweringContext<'_, 'hir>,
340    sp: Span,
341    placeholder: &FormatPlaceholder,
342    argmap: &mut FxIndexMap<(usize, ArgumentType), Option<Span>>,
343) -> hir::Expr<'hir> {
344    let position = match placeholder.argument.index {
345        Ok(arg_index) => {
346            let (i, _) = argmap.insert_full(
347                (arg_index, ArgumentType::Format(placeholder.format_trait)),
348                placeholder.span,
349            );
350            ctx.expr_usize(sp, i)
351        }
352        Err(_) => ctx.expr(
353            sp,
354            hir::ExprKind::Err(ctx.dcx().span_delayed_bug(sp, "lowered bad format_args count")),
355        ),
356    };
357    let &FormatOptions {
358        ref width,
359        ref precision,
360        alignment,
361        fill,
362        sign,
363        alternate,
364        zero_pad,
365        debug_hex,
366    } = &placeholder.format_options;
367    let fill = fill.unwrap_or(' ');
368    // These need to match the constants in library/core/src/fmt/rt.rs.
369    let align = match alignment {
370        Some(FormatAlignment::Left) => 0,
371        Some(FormatAlignment::Right) => 1,
372        Some(FormatAlignment::Center) => 2,
373        None => 3,
374    };
375    // This needs to match the constants in library/core/src/fmt/rt.rs.
376    let flags: u32 = fill as u32
377        | ((sign == Some(FormatSign::Plus)) as u32) << 21
378        | ((sign == Some(FormatSign::Minus)) as u32) << 22
379        | (alternate as u32) << 23
380        | (zero_pad as u32) << 24
381        | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25
382        | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26
383        | (width.is_some() as u32) << 27
384        | (precision.is_some() as u32) << 28
385        | align << 29
386        | 1 << 31; // Highest bit always set.
387    let flags = ctx.expr_u32(sp, flags);
388    let precision = make_count(ctx, sp, precision, argmap);
389    let width = make_count(ctx, sp, width, argmap);
390    let position = ctx.expr_field(Ident::new(sym::position, sp), ctx.arena.alloc(position), sp);
391    let flags = ctx.expr_field(Ident::new(sym::flags, sp), ctx.arena.alloc(flags), sp);
392    let precision = ctx.expr_field(Ident::new(sym::precision, sp), ctx.arena.alloc(precision), sp);
393    let width = ctx.expr_field(Ident::new(sym::width, sp), ctx.arena.alloc(width), sp);
394    let placeholder = ctx.arena.alloc(hir::QPath::LangItem(hir::LangItem::FormatPlaceholder, sp));
395    let fields = ctx.arena.alloc_from_iter([position, flags, precision, width]);
396    ctx.expr(sp, hir::ExprKind::Struct(placeholder, fields, hir::StructTailExpr::None))
397}
398
399fn expand_format_args<'hir>(
400    ctx: &mut LoweringContext<'_, 'hir>,
401    macsp: Span,
402    fmt: &FormatArgs,
403    allow_const: bool,
404) -> hir::ExprKind<'hir> {
405    let mut incomplete_lit = String::new();
406    let lit_pieces =
407        ctx.arena.alloc_from_iter(fmt.template.iter().enumerate().filter_map(|(i, piece)| {
408            match piece {
409                &FormatArgsPiece::Literal(s) => {
410                    // Coalesce adjacent literal pieces.
411                    if let Some(FormatArgsPiece::Literal(_)) = fmt.template.get(i + 1) {
412                        incomplete_lit.push_str(s.as_str());
413                        None
414                    } else if !incomplete_lit.is_empty() {
415                        incomplete_lit.push_str(s.as_str());
416                        let s = Symbol::intern(&incomplete_lit);
417                        incomplete_lit.clear();
418                        Some(ctx.expr_str(fmt.span, s))
419                    } else {
420                        Some(ctx.expr_str(fmt.span, s))
421                    }
422                }
423                &FormatArgsPiece::Placeholder(_) => {
424                    // Inject empty string before placeholders when not already preceded by a literal piece.
425                    if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) {
426                        Some(ctx.expr_str(fmt.span, sym::empty))
427                    } else {
428                        None
429                    }
430                }
431            }
432        }));
433    let lit_pieces = ctx.expr_array_ref(fmt.span, lit_pieces);
434
435    // Whether we'll use the `Arguments::new_v1_formatted` form (true),
436    // or the `Arguments::new_v1` form (false).
437    let mut use_format_options = false;
438
439    // Create a list of all _unique_ (argument, format trait) combinations.
440    // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
441    let mut argmap = FxIndexMap::default();
442    for piece in &fmt.template {
443        let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
444        if placeholder.format_options != Default::default() {
445            // Can't use basic form if there's any formatting options.
446            use_format_options = true;
447        }
448        if let Ok(index) = placeholder.argument.index {
449            if argmap
450                .insert((index, ArgumentType::Format(placeholder.format_trait)), placeholder.span)
451                .is_some()
452            {
453                // Duplicate (argument, format trait) combination,
454                // which we'll only put once in the args array.
455                use_format_options = true;
456            }
457        }
458    }
459
460    let format_options = use_format_options.then(|| {
461        // Generate:
462        //     &[format_spec_0, format_spec_1, format_spec_2]
463        let elements = ctx.arena.alloc_from_iter(fmt.template.iter().filter_map(|piece| {
464            let FormatArgsPiece::Placeholder(placeholder) = piece else { return None };
465            Some(make_format_spec(ctx, macsp, placeholder, &mut argmap))
466        }));
467        ctx.expr_array_ref(macsp, elements)
468    });
469
470    let arguments = fmt.arguments.all_args();
471
472    if allow_const && arguments.is_empty() && argmap.is_empty() {
473        // Generate:
474        //     <core::fmt::Arguments>::new_const(lit_pieces)
475        let new = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
476            macsp,
477            hir::LangItem::FormatArguments,
478            sym::new_const,
479        ));
480        let new_args = ctx.arena.alloc_from_iter([lit_pieces]);
481        return hir::ExprKind::Call(new, new_args);
482    }
483
484    let (let_statements, args) = if arguments.is_empty() {
485        // Generate:
486        //     []
487        (vec![], ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(&[]))))
488    } else if argmap.len() == 1 && arguments.len() == 1 {
489        // Only one argument, so we don't need to make the `args` tuple.
490        //
491        // Generate:
492        //     super let args = [<core::fmt::Argument>::new_display(&arg)];
493        let args = ctx.arena.alloc_from_iter(argmap.iter().map(
494            |(&(arg_index, ty), &placeholder_span)| {
495                let arg = &arguments[arg_index];
496                let placeholder_span =
497                    placeholder_span.unwrap_or(arg.expr.span).with_ctxt(macsp.ctxt());
498                let arg = ctx.lower_expr(&arg.expr);
499                let ref_arg = ctx.arena.alloc(ctx.expr_ref(arg.span.with_ctxt(macsp.ctxt()), arg));
500                make_argument(ctx, placeholder_span, ref_arg, ty)
501            },
502        ));
503        let args = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
504        let args_ident = Ident::new(sym::args, macsp);
505        let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
506        let let_statement = ctx.stmt_super_let_pat(macsp, args_pat, Some(args));
507        (vec![let_statement], ctx.arena.alloc(ctx.expr_ident_mut(macsp, args_ident, args_hir_id)))
508    } else {
509        // Generate:
510        //     super let args = (&arg0, &arg1, &…);
511        let args_ident = Ident::new(sym::args, macsp);
512        let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
513        let elements = ctx.arena.alloc_from_iter(arguments.iter().map(|arg| {
514            let arg_expr = ctx.lower_expr(&arg.expr);
515            ctx.expr(
516                arg.expr.span.with_ctxt(macsp.ctxt()),
517                hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg_expr),
518            )
519        }));
520        let args_tuple = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Tup(elements)));
521        let let_statement_1 = ctx.stmt_super_let_pat(macsp, args_pat, Some(args_tuple));
522
523        // Generate:
524        //     super let args = [
525        //         <core::fmt::Argument>::new_display(args.0),
526        //         <core::fmt::Argument>::new_lower_hex(args.1),
527        //         <core::fmt::Argument>::new_debug(args.0),
528        //         …
529        //     ];
530        let args = ctx.arena.alloc_from_iter(argmap.iter().map(
531            |(&(arg_index, ty), &placeholder_span)| {
532                let arg = &arguments[arg_index];
533                let placeholder_span =
534                    placeholder_span.unwrap_or(arg.expr.span).with_ctxt(macsp.ctxt());
535                let arg_span = match arg.kind {
536                    FormatArgumentKind::Captured(_) => placeholder_span,
537                    _ => arg.expr.span.with_ctxt(macsp.ctxt()),
538                };
539                let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id);
540                let arg = ctx.arena.alloc(ctx.expr(
541                    arg_span,
542                    hir::ExprKind::Field(
543                        args_ident_expr,
544                        Ident::new(sym::integer(arg_index), macsp),
545                    ),
546                ));
547                make_argument(ctx, placeholder_span, arg, ty)
548            },
549        ));
550        let args = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
551        let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
552        let let_statement_2 = ctx.stmt_super_let_pat(macsp, args_pat, Some(args));
553        (
554            vec![let_statement_1, let_statement_2],
555            ctx.arena.alloc(ctx.expr_ident_mut(macsp, args_ident, args_hir_id)),
556        )
557    };
558
559    // Generate:
560    //     &args
561    let args = ctx.expr_ref(macsp, args);
562
563    let call = if let Some(format_options) = format_options {
564        // Generate:
565        //     unsafe {
566        //         <core::fmt::Arguments>::new_v1_formatted(
567        //             lit_pieces,
568        //             args,
569        //             format_options,
570        //         )
571        //     }
572        let new_v1_formatted = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
573            macsp,
574            hir::LangItem::FormatArguments,
575            sym::new_v1_formatted,
576        ));
577        let args = ctx.arena.alloc_from_iter([lit_pieces, args, format_options]);
578        let call = ctx.expr_call(macsp, new_v1_formatted, args);
579        let hir_id = ctx.next_id();
580        hir::ExprKind::Block(
581            ctx.arena.alloc(hir::Block {
582                stmts: &[],
583                expr: Some(call),
584                hir_id,
585                rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated),
586                span: macsp,
587                targeted_by_break: false,
588            }),
589            None,
590        )
591    } else {
592        // Generate:
593        //     <core::fmt::Arguments>::new_v1(
594        //         lit_pieces,
595        //         args,
596        //     )
597        let new_v1 = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
598            macsp,
599            hir::LangItem::FormatArguments,
600            sym::new_v1,
601        ));
602        let new_args = ctx.arena.alloc_from_iter([lit_pieces, args]);
603        hir::ExprKind::Call(new_v1, new_args)
604    };
605
606    if !let_statements.is_empty() {
607        // Generate:
608        //     {
609        //         super let …
610        //         super let …
611        //         <core::fmt::Arguments>::new_…(…)
612        //     }
613        let call = ctx.arena.alloc(ctx.expr(macsp, call));
614        let block = ctx.block_all(macsp, ctx.arena.alloc_from_iter(let_statements), Some(call));
615        hir::ExprKind::Block(block, None)
616    } else {
617        call
618    }
619}
620
621fn for_all_argument_indexes(template: &mut [FormatArgsPiece], mut f: impl FnMut(&mut usize)) {
622    for piece in template {
623        let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
624        if let Ok(index) = &mut placeholder.argument.index {
625            f(index);
626        }
627        if let Some(FormatCount::Argument(FormatArgPosition { index: Ok(index), .. })) =
628            &mut placeholder.format_options.width
629        {
630            f(index);
631        }
632        if let Some(FormatCount::Argument(FormatArgPosition { index: Ok(index), .. })) =
633            &mut placeholder.format_options.precision
634        {
635            f(index);
636        }
637    }
638}