rustc_ast_lowering/
format.rs

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