Skip to main content

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::{ByteSymbol, DesugaringKind, Ident, Span, Symbol, sym};
8
9use super::LoweringContext;
10use crate::ResolverAstLoweringExt;
11
12impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
13    pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> {
14        // Never call the const constructor of `fmt::Arguments` if the
15        // format_args!() had any arguments _before_ flattening/inlining.
16        let allow_const = fmt.arguments.all_args().is_empty();
17        let mut fmt = Cow::Borrowed(fmt);
18
19        let sp = self.mark_span_with_reason(
20            DesugaringKind::FormatLiteral { source: fmt.is_source_literal },
21            sp,
22            sp.ctxt().outer_expn_data().allow_internal_unstable,
23        );
24
25        if self.tcx.sess.opts.unstable_opts.flatten_format_args {
26            fmt = flatten_format_args(fmt);
27            fmt = self.inline_literals(fmt);
28        }
29        expand_format_args(self, sp, &fmt, allow_const)
30    }
31
32    /// Try to convert a literal into an interned string
33    fn try_inline_lit(&self, lit: token::Lit) -> Option<Symbol> {
34        match LitKind::from_token_lit(lit) {
35            Ok(LitKind::Str(s, _)) => Some(s),
36            Ok(LitKind::Int(n, ty)) => {
37                match ty {
38                    // unsuffixed integer literals are assumed to be i32's
39                    LitIntType::Unsuffixed => {
40                        (n <= i32::MAX as u128).then_some(Symbol::intern(&n.to_string()))
41                    }
42                    LitIntType::Signed(int_ty) => {
43                        let max_literal = self.int_ty_max(int_ty);
44                        (n <= max_literal).then_some(Symbol::intern(&n.to_string()))
45                    }
46                    LitIntType::Unsigned(uint_ty) => {
47                        let max_literal = self.uint_ty_max(uint_ty);
48                        (n <= max_literal).then_some(Symbol::intern(&n.to_string()))
49                    }
50                }
51            }
52            _ => None,
53        }
54    }
55
56    /// Get the maximum value of int_ty. It is platform-dependent due to the byte size of isize
57    fn int_ty_max(&self, int_ty: IntTy) -> u128 {
58        match int_ty {
59            IntTy::Isize => self.tcx.data_layout.pointer_size().signed_int_max() as u128,
60            IntTy::I8 => i8::MAX as u128,
61            IntTy::I16 => i16::MAX as u128,
62            IntTy::I32 => i32::MAX as u128,
63            IntTy::I64 => i64::MAX as u128,
64            IntTy::I128 => i128::MAX as u128,
65        }
66    }
67
68    /// Get the maximum value of uint_ty. It is platform-dependent due to the byte size of usize
69    fn uint_ty_max(&self, uint_ty: UintTy) -> u128 {
70        match uint_ty {
71            UintTy::Usize => self.tcx.data_layout.pointer_size().unsigned_int_max(),
72            UintTy::U8 => u8::MAX as u128,
73            UintTy::U16 => u16::MAX as u128,
74            UintTy::U32 => u32::MAX as u128,
75            UintTy::U64 => u64::MAX as u128,
76            UintTy::U128 => u128::MAX as u128,
77        }
78    }
79
80    /// Inline literals into the format string.
81    ///
82    /// Turns
83    ///
84    /// `format_args!("Hello, {}! {} {}", "World", 123, x)`
85    ///
86    /// into
87    ///
88    /// `format_args!("Hello, World! 123 {}", x)`.
89    fn inline_literals<'fmt>(&self, mut fmt: Cow<'fmt, FormatArgs>) -> Cow<'fmt, FormatArgs> {
90        let mut was_inlined = ::alloc::vec::from_elem(false, fmt.arguments.all_args().len())vec![false; fmt.arguments.all_args().len()];
91        let mut inlined_anything = false;
92
93        for i in 0..fmt.template.len() {
94            if let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i]
95                && let Ok(arg_index) = placeholder.argument.index
96                && let FormatTrait::Display = placeholder.format_trait
97                && placeholder.format_options == Default::default()
98                && let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs()
99                && let ExprKind::Lit(lit) = arg.kind
100                && let Some(literal) = self.try_inline_lit(lit)
101            {
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                !#[allow(non_exhaustive_omitted_patterns)] match p {
    FormatArgsPiece::Placeholder(placeholder) if
        placeholder.argument.index == Ok(arg_index) => true,
    _ => false,
}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                    _ => ::core::panicking::panic("internal error: entered unreachable code")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(#[automatically_derived]
impl ::core::marker::Copy for ArgumentType { }Copy, #[automatically_derived]
impl ::core::clone::Clone for ArgumentType {
    #[inline]
    fn clone(&self) -> ArgumentType {
        let _: ::core::clone::AssertParamIsClone<FormatTrait>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for ArgumentType {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            ArgumentType::Format(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Format",
                    &__self_0),
            ArgumentType::Usize =>
                ::core::fmt::Formatter::write_str(f, "Usize"),
        }
    }
}Debug, #[automatically_derived]
impl ::core::hash::Hash for ArgumentType {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        ::core::hash::Hash::hash(&__self_discr, state);
        match self {
            ArgumentType::Format(__self_0) =>
                ::core::hash::Hash::hash(__self_0, state),
            _ => {}
        }
    }
}Hash, #[automatically_derived]
impl ::core::cmp::PartialEq for ArgumentType {
    #[inline]
    fn eq(&self, other: &ArgumentType) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (ArgumentType::Format(__self_0),
                    ArgumentType::Format(__arg1_0)) => __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ArgumentType {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<FormatTrait>;
    }
}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, impl ResolverAstLoweringExt<'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/// Get the value for a `width` or `precision` field.
264///
265/// Returns the value and whether it is indirect (an indexed argument) or not.
266fn make_count(
267    count: &FormatCount,
268    argmap: &mut FxIndexMap<(usize, ArgumentType), Option<Span>>,
269) -> (bool, u16) {
270    match count {
271        FormatCount::Literal(n) => (false, *n),
272        FormatCount::Argument(arg) => (
273            true,
274            argmap.insert_full((arg.index.unwrap_or(usize::MAX), ArgumentType::Usize), arg.span).0
275                as u16,
276        ),
277    }
278}
279
280fn expand_format_args<'hir>(
281    ctx: &mut LoweringContext<'_, 'hir, impl ResolverAstLoweringExt<'hir>>,
282    macsp: Span,
283    fmt: &FormatArgs,
284    allow_const: bool,
285) -> hir::ExprKind<'hir> {
286    let macsp = ctx.lower_span(macsp);
287
288    // Create a list of all _unique_ (argument, format trait) combinations.
289    // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
290    //
291    // We use usize::MAX for arguments that don't exist, because that can never be a valid index
292    // into the arguments array.
293    let mut argmap = FxIndexMap::default();
294
295    let mut incomplete_lit = String::new();
296
297    let mut implicit_arg_index = 0;
298
299    let mut bytecode = Vec::new();
300
301    let template = if fmt.template.is_empty() {
302        // Treat empty templates as a single literal piece (with an empty string),
303        // so we produce `from_str("")` for those.
304        &[FormatArgsPiece::Literal(sym::empty)][..]
305    } else {
306        &fmt.template[..]
307    };
308
309    // See library/core/src/fmt/mod.rs for the format string encoding format.
310
311    for (i, piece) in template.iter().enumerate() {
312        match piece {
313            &FormatArgsPiece::Literal(sym) => {
314                // Coalesce adjacent literal pieces.
315                if let Some(FormatArgsPiece::Literal(_)) = template.get(i + 1) {
316                    incomplete_lit.push_str(sym.as_str());
317                    continue;
318                }
319                let mut s = if incomplete_lit.is_empty() {
320                    sym.as_str()
321                } else {
322                    incomplete_lit.push_str(sym.as_str());
323                    &incomplete_lit
324                };
325
326                // If this is the last piece and was the only piece, that means
327                // there are no placeholders and the entire format string is just a literal.
328                //
329                // In that case, we can just use `from_str`.
330                if i + 1 == template.len() && bytecode.is_empty() {
331                    // Generate:
332                    //     <core::fmt::Arguments>::from_str("meow")
333                    let from_str = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
334                        macsp,
335                        hir::LangItem::FormatArguments,
336                        if allow_const { sym::from_str } else { sym::from_str_nonconst },
337                    ));
338                    let sym = if incomplete_lit.is_empty() { sym } else { Symbol::intern(s) };
339                    let s = ctx.expr_str(fmt.span, sym);
340                    let args = ctx.arena.alloc_from_iter([s]);
341                    return hir::ExprKind::Call(from_str, args);
342                }
343
344                // Encode the literal in chunks of up to u16::MAX bytes, split at utf-8 boundaries.
345                while !s.is_empty() {
346                    let len = s.floor_char_boundary(usize::from(u16::MAX));
347                    if len < 0x80 {
348                        bytecode.push(len as u8);
349                    } else {
350                        bytecode.push(0x80);
351                        bytecode.extend_from_slice(&(len as u16).to_le_bytes());
352                    }
353                    bytecode.extend(&s.as_bytes()[..len]);
354                    s = &s[len..];
355                }
356
357                incomplete_lit.clear();
358            }
359            FormatArgsPiece::Placeholder(p) => {
360                // Push the start byte and remember its index so we can set the option bits later.
361                let i = bytecode.len();
362                bytecode.push(0xC0);
363
364                let position = argmap
365                    .insert_full(
366                        (
367                            p.argument.index.unwrap_or(usize::MAX),
368                            ArgumentType::Format(p.format_trait),
369                        ),
370                        p.span,
371                    )
372                    .0 as u64;
373
374                // This needs to match the constants in library/core/src/fmt/mod.rs.
375                let o = &p.format_options;
376                let align = match o.alignment {
377                    Some(FormatAlignment::Left) => 0,
378                    Some(FormatAlignment::Right) => 1,
379                    Some(FormatAlignment::Center) => 2,
380                    None => 3,
381                };
382                let default_flags = 0x6000_0020;
383                let flags: u32 = o.fill.unwrap_or(' ') as u32
384                    | ((o.sign == Some(FormatSign::Plus)) as u32) << 21
385                    | ((o.sign == Some(FormatSign::Minus)) as u32) << 22
386                    | (o.alternate as u32) << 23
387                    | (o.zero_pad as u32) << 24
388                    | ((o.debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25
389                    | ((o.debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26
390                    | (o.width.is_some() as u32) << 27
391                    | (o.precision.is_some() as u32) << 28
392                    | align << 29;
393                if flags != default_flags {
394                    bytecode[i] |= 1;
395                    bytecode.extend_from_slice(&flags.to_le_bytes());
396                    if let Some(val) = &o.width {
397                        let (indirect, val) = make_count(val, &mut argmap);
398                        // Only encode if nonzero; zero is the default.
399                        if indirect || val != 0 {
400                            bytecode[i] |= 1 << 1 | (indirect as u8) << 4;
401                            bytecode.extend_from_slice(&val.to_le_bytes());
402                        }
403                    }
404                    if let Some(val) = &o.precision {
405                        let (indirect, val) = make_count(val, &mut argmap);
406                        // Only encode if nonzero; zero is the default.
407                        if indirect || val != 0 {
408                            bytecode[i] |= 1 << 2 | (indirect as u8) << 5;
409                            bytecode.extend_from_slice(&val.to_le_bytes());
410                        }
411                    }
412                }
413                if implicit_arg_index != position {
414                    bytecode[i] |= 1 << 3;
415                    bytecode.extend_from_slice(&(position as u16).to_le_bytes());
416                }
417                implicit_arg_index = position + 1;
418            }
419        }
420    }
421
422    if !incomplete_lit.is_empty() {
    ::core::panicking::panic("assertion failed: incomplete_lit.is_empty()")
};assert!(incomplete_lit.is_empty());
423
424    // Zero terminator.
425    bytecode.push(0);
426
427    // Ensure all argument indexes actually fit in 16 bits, as we truncated them to 16 bits before.
428    if argmap.len() > u16::MAX as usize {
429        ctx.dcx().span_err(macsp, "too many format arguments");
430    }
431
432    let arguments = fmt.arguments.all_args();
433
434    let (let_statements, args) = if arguments.is_empty() {
435        // Generate:
436        //     []
437        (::alloc::vec::Vec::new()vec![], ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(&[]))))
438    } else {
439        // Generate:
440        //     super let args = (&arg0, &arg1, &…);
441        let args_ident = Ident::new(sym::args, macsp);
442        let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
443        let elements = ctx.arena.alloc_from_iter(arguments.iter().map(|arg| {
444            let arg_expr = ctx.lower_expr(&arg.expr);
445            ctx.expr(
446                arg.expr.span.with_ctxt(macsp.ctxt()),
447                hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg_expr),
448            )
449        }));
450        let args_tuple = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Tup(elements)));
451        let let_statement_1 = ctx.stmt_super_let_pat(macsp, args_pat, Some(args_tuple));
452
453        // Generate:
454        //     super let args = [
455        //         <core::fmt::Argument>::new_display(args.0),
456        //         <core::fmt::Argument>::new_lower_hex(args.1),
457        //         <core::fmt::Argument>::new_debug(args.0),
458        //         …
459        //     ];
460        let args = ctx.arena.alloc_from_iter(argmap.iter().map(
461            |(&(arg_index, ty), &placeholder_span)| {
462                if let Some(arg) = arguments.get(arg_index) {
463                    let placeholder_span =
464                        placeholder_span.unwrap_or(arg.expr.span).with_ctxt(macsp.ctxt());
465                    let arg_span = match arg.kind {
466                        FormatArgumentKind::Captured(_) => placeholder_span,
467                        _ => arg.expr.span.with_ctxt(macsp.ctxt()),
468                    };
469                    let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id);
470                    let arg = ctx.arena.alloc(ctx.expr(
471                        arg_span,
472                        hir::ExprKind::Field(
473                            args_ident_expr,
474                            Ident::new(sym::integer(arg_index), macsp),
475                        ),
476                    ));
477                    make_argument(ctx, placeholder_span, arg, ty)
478                } else {
479                    ctx.expr(
480                        macsp,
481                        hir::ExprKind::Err(
482                            ctx.dcx().span_delayed_bug(macsp, "missing format_args argument"),
483                        ),
484                    )
485                }
486            },
487        ));
488        let args = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
489        let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
490        let let_statement_2 = ctx.stmt_super_let_pat(macsp, args_pat, Some(args));
491        (
492            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [let_statement_1, let_statement_2]))vec![let_statement_1, let_statement_2],
493            ctx.arena.alloc(ctx.expr_ident_mut(macsp, args_ident, args_hir_id)),
494        )
495    };
496
497    // Generate:
498    //     unsafe {
499    //         <core::fmt::Arguments>::new(b"…", &args)
500    //     }
501    let template = ctx.expr_byte_str(macsp, ByteSymbol::intern(&bytecode));
502    let call = {
503        let new = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
504            macsp,
505            hir::LangItem::FormatArguments,
506            sym::new,
507        ));
508        let args = ctx.expr_ref(macsp, args);
509        let new_args = ctx.arena.alloc_from_iter([template, args]);
510        ctx.expr_call(macsp, new, new_args)
511    };
512    let call = hir::ExprKind::Block(
513        ctx.arena.alloc(hir::Block {
514            stmts: &[],
515            expr: Some(call),
516            hir_id: ctx.next_id(),
517            rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated),
518            span: macsp,
519            targeted_by_break: false,
520        }),
521        None,
522    );
523
524    if !let_statements.is_empty() {
525        // Generate:
526        //     {
527        //         super let …
528        //         super let …
529        //         <core::fmt::Arguments>::new(…)
530        //     }
531        let call = ctx.arena.alloc(ctx.expr(macsp, call));
532        let block = ctx.block_all(macsp, ctx.arena.alloc_from_iter(let_statements), Some(call));
533        hir::ExprKind::Block(block, None)
534    } else {
535        call
536    }
537}
538
539fn for_all_argument_indexes(template: &mut [FormatArgsPiece], mut f: impl FnMut(&mut usize)) {
540    for piece in template {
541        let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
542        if let Ok(index) = &mut placeholder.argument.index {
543            f(index);
544        }
545        if let Some(FormatCount::Argument(FormatArgPosition { index: Ok(index), .. })) =
546            &mut placeholder.format_options.width
547        {
548            f(index);
549        }
550        if let Some(FormatCount::Argument(FormatArgPosition { index: Ok(index), .. })) =
551            &mut placeholder.format_options.precision
552        {
553            f(index);
554        }
555    }
556}