Skip to main content

rustc_parse_format/
lib.rs

1//! Macro support for format strings
2//!
3//! These structures are used when parsing format strings for the compiler.
4//! Parsing does not happen at runtime: structures of `std::fmt::rt` are
5//! generated instead.
6
7// tidy-alphabetical-start
8// We want to be able to build this crate with a stable compiler,
9// so no `#![feature]` attributes should be added.
10#![deny(unstable_features)]
11#![doc(test(attr(deny(warnings), allow(internal_features))))]
12// tidy-alphabetical-end
13
14use std::ops::Range;
15
16pub use Alignment::*;
17pub use Count::*;
18pub use Position::*;
19
20/// The type of format string that we are parsing.
21#[derive(#[automatically_derived]
impl ::core::marker::Copy for ParseMode { }Copy, #[automatically_derived]
impl ::core::clone::Clone for ParseMode {
    #[inline]
    fn clone(&self) -> ParseMode { *self }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for ParseMode {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ParseMode::Format => "Format",
                ParseMode::InlineAsm => "InlineAsm",
                ParseMode::Diagnostic => "Diagnostic",
            })
    }
}Debug, #[automatically_derived]
impl ::core::cmp::Eq for ParseMode {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) {}
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for ParseMode {
    #[inline]
    fn eq(&self, other: &ParseMode) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq)]
22pub enum ParseMode {
23    /// A normal format string as per `format_args!`.
24    Format,
25    /// An inline assembly template string for `asm!`.
26    InlineAsm,
27    /// A format string for use in diagnostic attributes.
28    ///
29    /// Similar to `format_args!`, however only named ("captured") arguments
30    /// are allowed, and no format modifiers are permitted.
31    Diagnostic,
32}
33
34/// A piece is a portion of the format string which represents the next part
35/// to emit. These are emitted as a stream by the `Parser` class.
36#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for Piece<'input> {
    #[inline]
    fn clone(&self) -> Piece<'input> {
        match self {
            Piece::Lit(__self_0) =>
                Piece::Lit(::core::clone::Clone::clone(__self_0)),
            Piece::NextArgument(__self_0) =>
                Piece::NextArgument(::core::clone::Clone::clone(__self_0)),
        }
    }
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for Piece<'input> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Piece::Lit(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Lit",
                    &__self_0),
            Piece::NextArgument(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "NextArgument", &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for Piece<'input> {
    #[inline]
    fn eq(&self, other: &Piece<'input>) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (Piece::Lit(__self_0), Piece::Lit(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Piece::NextArgument(__self_0), Piece::NextArgument(__arg1_0))
                    => __self_0 == __arg1_0,
                _ => unsafe { ::core::intrinsics::unreachable() }
            }
    }
}PartialEq)]
37pub enum Piece<'input> {
38    /// A literal string which should directly be emitted
39    Lit(&'input str),
40    /// This describes that formatting should process the next argument (as
41    /// specified inside) for emission.
42    NextArgument(Box<Argument<'input>>),
43}
44
45/// Representation of an argument specification.
46#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for Argument<'input> {
    #[inline]
    fn clone(&self) -> Argument<'input> {
        Argument {
            position: ::core::clone::Clone::clone(&self.position),
            position_span: ::core::clone::Clone::clone(&self.position_span),
            format: ::core::clone::Clone::clone(&self.format),
        }
    }
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for Argument<'input> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "Argument",
            "position", &self.position, "position_span", &self.position_span,
            "format", &&self.format)
    }
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for Argument<'input> {
    #[inline]
    fn eq(&self, other: &Argument<'input>) -> bool {
        self.position == other.position &&
                self.position_span == other.position_span &&
            self.format == other.format
    }
}PartialEq)]
47pub struct Argument<'input> {
48    /// Where to find this argument
49    pub position: Position<'input>,
50    /// The span of the position indicator. Includes any whitespace in implicit
51    /// positions (`{  }`).
52    pub position_span: Range<usize>,
53    /// How to format the argument
54    pub format: FormatSpec<'input>,
55}
56
57impl<'input> Argument<'input> {
58    pub fn is_identifier(&self) -> bool {
59        #[allow(non_exhaustive_omitted_patterns)] match self.position {
    Position::ArgumentNamed(_) => true,
    _ => false,
}matches!(self.position, Position::ArgumentNamed(_)) && self.format == FormatSpec::default()
60    }
61}
62
63/// Specification for the formatting of an argument in the format string.
64#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for FormatSpec<'input> {
    #[inline]
    fn clone(&self) -> FormatSpec<'input> {
        FormatSpec {
            fill: ::core::clone::Clone::clone(&self.fill),
            fill_span: ::core::clone::Clone::clone(&self.fill_span),
            align: ::core::clone::Clone::clone(&self.align),
            sign: ::core::clone::Clone::clone(&self.sign),
            alternate: ::core::clone::Clone::clone(&self.alternate),
            zero_pad: ::core::clone::Clone::clone(&self.zero_pad),
            debug_hex: ::core::clone::Clone::clone(&self.debug_hex),
            precision: ::core::clone::Clone::clone(&self.precision),
            precision_span: ::core::clone::Clone::clone(&self.precision_span),
            width: ::core::clone::Clone::clone(&self.width),
            width_span: ::core::clone::Clone::clone(&self.width_span),
            ty: ::core::clone::Clone::clone(&self.ty),
            ty_span: ::core::clone::Clone::clone(&self.ty_span),
        }
    }
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for FormatSpec<'input> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["fill", "fill_span", "align", "sign", "alternate", "zero_pad",
                        "debug_hex", "precision", "precision_span", "width",
                        "width_span", "ty", "ty_span"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.fill, &self.fill_span, &self.align, &self.sign,
                        &self.alternate, &self.zero_pad, &self.debug_hex,
                        &self.precision, &self.precision_span, &self.width,
                        &self.width_span, &self.ty, &&self.ty_span];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "FormatSpec",
            names, values)
    }
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for FormatSpec<'input> {
    #[inline]
    fn eq(&self, other: &FormatSpec<'input>) -> bool {
        self.alternate == other.alternate && self.zero_pad == other.zero_pad
                                                    && self.fill == other.fill &&
                                                self.fill_span == other.fill_span &&
                                            self.align == other.align && self.sign == other.sign &&
                                    self.debug_hex == other.debug_hex &&
                                self.precision == other.precision &&
                            self.precision_span == other.precision_span &&
                        self.width == other.width &&
                    self.width_span == other.width_span && self.ty == other.ty
            && self.ty_span == other.ty_span
    }
}PartialEq, #[automatically_derived]
impl<'input> ::core::default::Default for FormatSpec<'input> {
    #[inline]
    fn default() -> FormatSpec<'input> {
        FormatSpec {
            fill: ::core::default::Default::default(),
            fill_span: ::core::default::Default::default(),
            align: ::core::default::Default::default(),
            sign: ::core::default::Default::default(),
            alternate: ::core::default::Default::default(),
            zero_pad: ::core::default::Default::default(),
            debug_hex: ::core::default::Default::default(),
            precision: ::core::default::Default::default(),
            precision_span: ::core::default::Default::default(),
            width: ::core::default::Default::default(),
            width_span: ::core::default::Default::default(),
            ty: ::core::default::Default::default(),
            ty_span: ::core::default::Default::default(),
        }
    }
}Default)]
65pub struct FormatSpec<'input> {
66    /// Optionally specified character to fill alignment with.
67    pub fill: Option<char>,
68    /// Span of the optionally specified fill character.
69    pub fill_span: Option<Range<usize>>,
70    /// Optionally specified alignment.
71    pub align: Alignment,
72    /// The `+` or `-` flag.
73    pub sign: Option<Sign>,
74    /// The `#` flag.
75    pub alternate: bool,
76    /// The `0` flag.
77    pub zero_pad: bool,
78    /// The `x` or `X` flag. (Only for `Debug`.)
79    pub debug_hex: Option<DebugHex>,
80    /// The integer precision to use.
81    pub precision: Count<'input>,
82    /// The span of the precision formatting flag (for diagnostics).
83    pub precision_span: Option<Range<usize>>,
84    /// The string width requested for the resulting format.
85    pub width: Count<'input>,
86    /// The span of the width formatting flag (for diagnostics).
87    pub width_span: Option<Range<usize>>,
88    /// The descriptor string representing the name of the format desired for
89    /// this argument, this can be empty or any number of characters, although
90    /// it is required to be one word.
91    pub ty: &'input str,
92    /// The span of the descriptor string (for diagnostics).
93    pub ty_span: Option<Range<usize>>,
94}
95
96/// Enum describing where an argument for a format can be located.
97#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for Position<'input> {
    #[inline]
    fn clone(&self) -> Position<'input> {
        match self {
            Position::ArgumentImplicitlyIs(__self_0) =>
                Position::ArgumentImplicitlyIs(::core::clone::Clone::clone(__self_0)),
            Position::ArgumentIs(__self_0) =>
                Position::ArgumentIs(::core::clone::Clone::clone(__self_0)),
            Position::ArgumentNamed(__self_0) =>
                Position::ArgumentNamed(::core::clone::Clone::clone(__self_0)),
        }
    }
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for Position<'input> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Position::ArgumentImplicitlyIs(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "ArgumentImplicitlyIs", &__self_0),
            Position::ArgumentIs(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "ArgumentIs", &__self_0),
            Position::ArgumentNamed(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "ArgumentNamed", &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for Position<'input> {
    #[inline]
    fn eq(&self, other: &Position<'input>) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (Position::ArgumentImplicitlyIs(__self_0),
                    Position::ArgumentImplicitlyIs(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Position::ArgumentIs(__self_0),
                    Position::ArgumentIs(__arg1_0)) => __self_0 == __arg1_0,
                (Position::ArgumentNamed(__self_0),
                    Position::ArgumentNamed(__arg1_0)) => __self_0 == __arg1_0,
                _ => unsafe { ::core::intrinsics::unreachable() }
            }
    }
}PartialEq)]
98pub enum Position<'input> {
99    /// The argument is implied to be located at an index
100    ArgumentImplicitlyIs(usize),
101    /// The argument is located at a specific index given in the format,
102    ArgumentIs(usize),
103    /// The argument has a name.
104    ArgumentNamed(&'input str),
105}
106
107impl Position<'_> {
108    pub fn index(&self) -> Option<usize> {
109        match self {
110            ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
111            _ => None,
112        }
113    }
114}
115
116/// Enum of alignments which are supported.
117#[derive(#[automatically_derived]
impl ::core::marker::Copy for Alignment { }Copy, #[automatically_derived]
impl ::core::clone::Clone for Alignment {
    #[inline]
    fn clone(&self) -> Alignment { *self }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for Alignment {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                Alignment::AlignLeft => "AlignLeft",
                Alignment::AlignRight => "AlignRight",
                Alignment::AlignCenter => "AlignCenter",
                Alignment::AlignUnknown => "AlignUnknown",
            })
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Alignment {
    #[inline]
    fn eq(&self, other: &Alignment) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::default::Default for Alignment {
    #[inline]
    fn default() -> Alignment { Self::AlignUnknown }
}Default)]
118pub enum Alignment {
119    /// The value will be aligned to the left.
120    AlignLeft,
121    /// The value will be aligned to the right.
122    AlignRight,
123    /// The value will be aligned in the center.
124    AlignCenter,
125    /// The value will take on a default alignment.
126    #[default]
127    AlignUnknown,
128}
129
130/// Enum for the sign flags.
131#[derive(#[automatically_derived]
impl ::core::marker::Copy for Sign { }Copy, #[automatically_derived]
impl ::core::clone::Clone for Sign {
    #[inline]
    fn clone(&self) -> Sign { *self }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for Sign {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self { Sign::Plus => "Plus", Sign::Minus => "Minus", })
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Sign {
    #[inline]
    fn eq(&self, other: &Sign) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq)]
132pub enum Sign {
133    /// The `+` flag.
134    Plus,
135    /// The `-` flag.
136    Minus,
137}
138
139/// Enum for the debug hex flags.
140#[derive(#[automatically_derived]
impl ::core::marker::Copy for DebugHex { }Copy, #[automatically_derived]
impl ::core::clone::Clone for DebugHex {
    #[inline]
    fn clone(&self) -> DebugHex { *self }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for DebugHex {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                DebugHex::Lower => "Lower",
                DebugHex::Upper => "Upper",
            })
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for DebugHex {
    #[inline]
    fn eq(&self, other: &DebugHex) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq)]
141pub enum DebugHex {
142    /// The `x` flag in `{:x?}`.
143    Lower,
144    /// The `X` flag in `{:X?}`.
145    Upper,
146}
147
148/// A count is used for the precision and width parameters of an integer, and
149/// can reference either an argument or a literal integer.
150#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for Count<'input> {
    #[inline]
    fn clone(&self) -> Count<'input> {
        match self {
            Count::CountIs(__self_0) =>
                Count::CountIs(::core::clone::Clone::clone(__self_0)),
            Count::CountIsName(__self_0, __self_1) =>
                Count::CountIsName(::core::clone::Clone::clone(__self_0),
                    ::core::clone::Clone::clone(__self_1)),
            Count::CountIsParam(__self_0) =>
                Count::CountIsParam(::core::clone::Clone::clone(__self_0)),
            Count::CountIsStar(__self_0) =>
                Count::CountIsStar(::core::clone::Clone::clone(__self_0)),
            Count::CountImplied => Count::CountImplied,
        }
    }
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for Count<'input> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Count::CountIs(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "CountIs", &__self_0),
            Count::CountIsName(__self_0, __self_1) =>
                ::core::fmt::Formatter::debug_tuple_field2_finish(f,
                    "CountIsName", __self_0, &__self_1),
            Count::CountIsParam(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "CountIsParam", &__self_0),
            Count::CountIsStar(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "CountIsStar", &__self_0),
            Count::CountImplied =>
                ::core::fmt::Formatter::write_str(f, "CountImplied"),
        }
    }
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for Count<'input> {
    #[inline]
    fn eq(&self, other: &Count<'input>) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (Count::CountIs(__self_0), Count::CountIs(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Count::CountIsName(__self_0, __self_1),
                    Count::CountIsName(__arg1_0, __arg1_1)) =>
                    __self_0 == __arg1_0 && __self_1 == __arg1_1,
                (Count::CountIsParam(__self_0), Count::CountIsParam(__arg1_0))
                    => __self_0 == __arg1_0,
                (Count::CountIsStar(__self_0), Count::CountIsStar(__arg1_0))
                    => __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]
impl<'input> ::core::default::Default for Count<'input> {
    #[inline]
    fn default() -> Count<'input> { Self::CountImplied }
}Default)]
151pub enum Count<'input> {
152    /// The count is specified explicitly.
153    CountIs(u16),
154    /// The count is specified by the argument with the given name.
155    CountIsName(&'input str, Range<usize>),
156    /// The count is specified by the argument at the given index.
157    CountIsParam(usize),
158    /// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index.
159    CountIsStar(usize),
160    /// The count is implied and cannot be explicitly specified.
161    #[default]
162    CountImplied,
163}
164
165pub struct ParseError {
166    pub description: String,
167    pub note: Option<String>,
168    pub label: String,
169    pub span: Range<usize>,
170    pub secondary_label: Option<(String, Range<usize>)>,
171    pub suggestion: Suggestion,
172}
173
174pub enum Suggestion {
175    None,
176    /// Replace inline argument with positional argument:
177    /// `format!("{foo.bar}")` -> `format!("{}", foo.bar)`
178    UsePositional,
179    /// Remove `r#` from identifier:
180    /// `format!("{r#foo}")` -> `format!("{foo}")`
181    RemoveRawIdent(Range<usize>),
182    /// Reorder format parameter:
183    /// `format!("{foo:?#}")` -> `format!("{foo:#?}")`
184    /// `format!("{foo:?x}")` -> `format!("{foo:x?}")`
185    /// `format!("{foo:?X}")` -> `format!("{foo:X?}")`
186    ReorderFormatParameter(Range<usize>, String),
187    /// Add missing colon:
188    /// `format!("{foo?}")` -> `format!("{foo:?}")`
189    AddMissingColon(Range<usize>),
190    /// Use Rust format string:
191    /// `format!("{x=}")` -> `dbg!(x)`
192    UseRustDebugPrintingMacro,
193}
194
195/// The parser structure for interpreting the input format string. This is
196/// modeled as an iterator over `Piece` structures to form a stream of tokens
197/// being output.
198///
199/// This is a recursive-descent parser for the sake of simplicity, and if
200/// necessary there's probably lots of room for improvement performance-wise.
201pub struct Parser<'input> {
202    mode: ParseMode,
203    /// Input to be parsed
204    input: &'input str,
205    /// Tuples of the span in the code snippet (input as written before being unescaped), the pos in input, and the char in input
206    input_vec: Vec<(Range<usize>, usize, char)>,
207    /// Index into input_vec
208    input_vec_index: usize,
209    /// Error messages accumulated during parsing
210    pub errors: Vec<ParseError>,
211    /// Current position of implicit positional argument pointer
212    pub curarg: usize,
213    /// Start and end byte offset of every successfully parsed argument
214    pub arg_places: Vec<Range<usize>>,
215    /// Span of the last opening brace seen, used for error reporting
216    last_open_brace: Option<Range<usize>>,
217    /// Whether this formatting string was written directly in the source. This controls whether we
218    /// can use spans to refer into it and give better error messages.
219    /// N.B: This does _not_ control whether implicit argument captures can be used.
220    pub is_source_literal: bool,
221    /// Index to the end of the literal snippet
222    end_of_snippet: usize,
223    /// Start position of the current line.
224    cur_line_start: usize,
225    /// Start and end byte offset of every line of the format string. Excludes
226    /// newline characters and leading whitespace.
227    pub line_spans: Vec<Range<usize>>,
228}
229
230impl<'input> Iterator for Parser<'input> {
231    type Item = Piece<'input>;
232
233    fn next(&mut self) -> Option<Piece<'input>> {
234        if let Some((Range { start, end }, idx, ch)) = self.peek() {
235            match ch {
236                '{' => {
237                    self.input_vec_index += 1;
238                    if let Some((_, i, '{')) = self.peek() {
239                        self.input_vec_index += 1;
240                        // double open brace escape: "{{"
241                        // next state after this is either end-of-input or seen-a-brace
242                        Some(Piece::Lit(self.string(i)))
243                    } else {
244                        // single open brace
245                        self.last_open_brace = Some(start..end);
246                        let arg = self.argument();
247                        self.ws();
248                        if let Some((close_brace_range, _)) = self.consume_pos('}') {
249                            if self.is_source_literal {
250                                self.arg_places.push(start..close_brace_range.end);
251                            }
252                        } else {
253                            self.missing_closing_brace(&arg);
254                        }
255
256                        Some(Piece::NextArgument(Box::new(arg)))
257                    }
258                }
259                '}' => {
260                    self.input_vec_index += 1;
261                    if let Some((_, i, '}')) = self.peek() {
262                        self.input_vec_index += 1;
263                        // double close brace escape: "}}"
264                        // next state after this is either end-of-input or start
265                        Some(Piece::Lit(self.string(i)))
266                    } else {
267                        // error: single close brace without corresponding open brace
268                        self.errors.push(ParseError {
269                            description: "unmatched `}` found".into(),
270                            note: Some(
271                                "if you intended to print `}`, you can escape it using `}}`".into(),
272                            ),
273                            label: "unmatched `}`".into(),
274                            span: start..end,
275                            secondary_label: None,
276                            suggestion: Suggestion::None,
277                        });
278                        None
279                    }
280                }
281                _ => Some(Piece::Lit(self.string(idx))),
282            }
283        } else {
284            // end of input
285            if self.is_source_literal {
286                let span = self.cur_line_start..self.end_of_snippet;
287                if self.line_spans.last() != Some(&span) {
288                    self.line_spans.push(span);
289                }
290            }
291            None
292        }
293    }
294}
295
296impl<'input> Parser<'input> {
297    /// Creates a new parser for the given unescaped input string and
298    /// optional code snippet (the input as written before being unescaped),
299    /// where `style` is `Some(nr_hashes)` when the snippet is a raw string with that many hashes.
300    /// If the input comes via `println` or `panic`, then it has a newline already appended,
301    /// which is reflected in the `appended_newline` parameter.
302    pub fn new(
303        input: &'input str,
304        style: Option<usize>,
305        snippet: Option<String>,
306        appended_newline: bool,
307        mode: ParseMode,
308    ) -> Self {
309        let quote_offset = style.map_or(1, |nr_hashes| nr_hashes + 2);
310
311        let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
312            if let Some(nr_hashes) = style {
313                // snippet is a raw string, which starts with 'r', a number of hashes, and a quote
314                // and ends with a quote and the same number of hashes
315                (true, snippet.len() - nr_hashes - 1, ::alloc::vec::Vec::new()vec![])
316            } else {
317                // snippet is not a raw string
318                if snippet.starts_with('"') {
319                    // snippet looks like an ordinary string literal
320                    // check whether it is the escaped version of input
321                    let without_quotes = &snippet[1..snippet.len() - 1];
322                    let (mut ok, mut vec) = (true, ::alloc::vec::Vec::new()vec![]);
323                    let mut chars = input.chars();
324                    rustc_literal_escaper::unescape_str(without_quotes, |range, res| match res {
325                        Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
326                            vec.push((range, ch));
327                        }
328                        _ => {
329                            ok = false;
330                            vec = ::alloc::vec::Vec::new()vec![];
331                        }
332                    });
333                    let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
334                    if ok {
335                        if appended_newline {
336                            if chars.as_str() == "\n" {
337                                vec.push((end..end + 1, '\n'));
338                                (true, 1 + end, vec)
339                            } else {
340                                (false, snippet.len(), ::alloc::vec::Vec::new()vec![])
341                            }
342                        } else if chars.as_str() == "" {
343                            (true, 1 + end, vec)
344                        } else {
345                            (false, snippet.len(), ::alloc::vec::Vec::new()vec![])
346                        }
347                    } else {
348                        (false, snippet.len(), ::alloc::vec::Vec::new()vec![])
349                    }
350                } else {
351                    // snippet is not a raw string and does not start with '"'
352                    (false, snippet.len(), ::alloc::vec::Vec::new()vec![])
353                }
354            }
355        } else {
356            // snippet is None
357            (false, input.len() - if appended_newline { 1 } else { 0 }, ::alloc::vec::Vec::new()vec![])
358        };
359
360        let input_vec: Vec<(Range<usize>, usize, char)> = if pre_input_vec.is_empty() {
361            // Snippet is *not* input before unescaping, so spans pointing at it will be incorrect.
362            // This can happen with proc macros that respan generated literals.
363            input
364                .char_indices()
365                .map(|(idx, c)| {
366                    let i = idx + quote_offset;
367                    (i..i + c.len_utf8(), idx, c)
368                })
369                .collect()
370        } else {
371            // Snippet is input before unescaping
372            input
373                .char_indices()
374                .zip(pre_input_vec)
375                .map(|((i, c), (r, _))| (r.start + quote_offset..r.end + quote_offset, i, c))
376                .collect()
377        };
378
379        Parser {
380            mode,
381            input,
382            input_vec,
383            input_vec_index: 0,
384            errors: ::alloc::vec::Vec::new()vec![],
385            curarg: 0,
386            arg_places: ::alloc::vec::Vec::new()vec![],
387            last_open_brace: None,
388            is_source_literal,
389            end_of_snippet,
390            cur_line_start: quote_offset,
391            line_spans: ::alloc::vec::Vec::new()vec![],
392        }
393    }
394
395    /// Peeks at the current position, without incrementing the pointer.
396    pub fn peek(&self) -> Option<(Range<usize>, usize, char)> {
397        self.input_vec.get(self.input_vec_index).cloned()
398    }
399
400    /// Peeks at the current position + 1, without incrementing the pointer.
401    pub fn peek_ahead(&self) -> Option<(Range<usize>, usize, char)> {
402        self.input_vec.get(self.input_vec_index + 1).cloned()
403    }
404
405    /// Optionally consumes the specified character. If the character is not at
406    /// the current position, then the current iterator isn't moved and `false` is
407    /// returned, otherwise the character is consumed and `true` is returned.
408    fn consume(&mut self, c: char) -> bool {
409        self.consume_pos(c).is_some()
410    }
411
412    /// Optionally consumes the specified character. If the character is not at
413    /// the current position, then the current iterator isn't moved and `None` is
414    /// returned, otherwise the character is consumed and the current position is
415    /// returned.
416    fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> {
417        if let Some((r, i, c)) = self.peek()
418            && ch == c
419        {
420            self.input_vec_index += 1;
421            return Some((r, i));
422        }
423
424        None
425    }
426
427    /// Called if a closing brace was not found.
428    fn missing_closing_brace(&mut self, arg: &Argument<'_>) {
429        let (range, description) = if let Some((r, _, c)) = self.peek() {
430            (r.start..r.start, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected `}}`, found `{0}`",
                c.escape_debug()))
    })format!("expected `}}`, found `{}`", c.escape_debug()))
431        } else {
432            (
433                // point at closing `"`
434                self.end_of_snippet..self.end_of_snippet,
435                "expected `}` but string was terminated".to_owned(),
436            )
437        };
438
439        let (note, secondary_label) = if arg.format.fill == Some('}') {
440            (
441                Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
442                arg.format.fill_span.clone().map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
443            )
444        } else {
445            (
446                Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
447                self.last_open_brace
448                    .clone()
449                    .map(|sp| ("because of this opening brace".to_owned(), sp)),
450            )
451        };
452
453        self.errors.push(ParseError {
454            description,
455            note,
456            label: "expected `}`".to_owned(),
457            span: range.start..range.start,
458            secondary_label,
459            suggestion: Suggestion::None,
460        });
461
462        if let (Some((_, _, c)), Some((_, _, nc))) = (self.peek(), self.peek_ahead()) {
463            match (c, nc) {
464                ('?', '}') => self.missing_colon_before_debug_formatter(),
465                ('?', _) => self.suggest_format_debug(),
466                ('<' | '^' | '>', _) => self.suggest_format_align(c),
467                (',', _) => self.suggest_unsupported_python_numeric_grouping(),
468                ('=', '}') => self.suggest_rust_debug_printing_macro(),
469                _ => self.suggest_positional_arg_instead_of_captured_arg(arg),
470            }
471        }
472    }
473
474    /// Consumes all whitespace characters until the first non-whitespace character
475    fn ws(&mut self) {
476        let rest = &self.input_vec[self.input_vec_index..];
477        let step = rest.iter().position(|&(_, _, c)| !c.is_whitespace()).unwrap_or(rest.len());
478        self.input_vec_index += step;
479    }
480
481    /// Parses all of a string which is to be considered a "raw literal" in a
482    /// format string. This is everything outside of the braces.
483    fn string(&mut self, start: usize) -> &'input str {
484        while let Some((r, i, c)) = self.peek() {
485            match c {
486                '{' | '}' => {
487                    return &self.input[start..i];
488                }
489                '\n' if self.is_source_literal => {
490                    self.input_vec_index += 1;
491                    self.line_spans.push(self.cur_line_start..r.start);
492                    self.cur_line_start = r.end;
493                }
494                _ => {
495                    self.input_vec_index += 1;
496                    if self.is_source_literal && r.start == self.cur_line_start && c.is_whitespace()
497                    {
498                        self.cur_line_start = r.end;
499                    }
500                }
501            }
502        }
503        &self.input[start..]
504    }
505
506    /// Parses an `Argument` structure, or what's contained within braces inside the format string.
507    fn argument(&mut self) -> Argument<'input> {
508        let start_idx = self.input_vec_index;
509
510        let position = self.position();
511        self.ws();
512
513        let end_idx = self.input_vec_index;
514
515        let format = match self.mode {
516            ParseMode::Format => self.format(),
517            ParseMode::InlineAsm => self.inline_asm(),
518            ParseMode::Diagnostic => self.diagnostic(),
519        };
520
521        // Resolve position after parsing format spec.
522        let position = position.unwrap_or_else(|| {
523            let i = self.curarg;
524            self.curarg += 1;
525            ArgumentImplicitlyIs(i)
526        });
527
528        let position_span =
529            self.input_vec_index2range(start_idx).start..self.input_vec_index2range(end_idx).start;
530        Argument { position, position_span, format }
531    }
532
533    /// Parses a positional argument for a format. This could either be an
534    /// integer index of an argument, a named argument, or a blank string.
535    /// Returns `Some(parsed_position)` if the position is not implicitly
536    /// consuming a macro argument, `None` if it's the case.
537    fn position(&mut self) -> Option<Position<'input>> {
538        if let Some(i) = self.integer() {
539            Some(ArgumentIs(i.into()))
540        } else {
541            match self.peek() {
542                Some((range, _, c)) if rustc_lexer::is_id_start(c) => {
543                    let start = range.start;
544                    let word = self.word();
545
546                    // Recover from `r#ident` in format strings.
547                    if word == "r"
548                        && let Some((r, _, '#')) = self.peek()
549                        && self.peek_ahead().is_some_and(|(_, _, c)| rustc_lexer::is_id_start(c))
550                    {
551                        self.input_vec_index += 1;
552                        let prefix_end = r.end;
553                        let word = self.word();
554                        let prefix_span = start..prefix_end;
555                        let full_span =
556                            start..self.input_vec_index2range(self.input_vec_index).start;
557                        self.errors.insert(0, ParseError {
558                                    description: "raw identifiers are not supported".to_owned(),
559                                    note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
560                                    label: "raw identifier used here".to_owned(),
561                                    span: full_span,
562                                    secondary_label: None,
563                                    suggestion: Suggestion::RemoveRawIdent(prefix_span),
564                                });
565                        return Some(ArgumentNamed(word));
566                    }
567
568                    Some(ArgumentNamed(word))
569                }
570                // This is an `ArgumentNext`.
571                // Record the fact and do the resolution after parsing the
572                // format spec, to make things like `{:.*}` work.
573                _ => None,
574            }
575        }
576    }
577
578    fn input_vec_index2pos(&self, index: usize) -> usize {
579        if let Some((_, pos, _)) = self.input_vec.get(index) { *pos } else { self.input.len() }
580    }
581
582    fn input_vec_index2range(&self, index: usize) -> Range<usize> {
583        if let Some((r, _, _)) = self.input_vec.get(index) {
584            r.clone()
585        } else {
586            self.end_of_snippet..self.end_of_snippet
587        }
588    }
589
590    /// Parses a format specifier at the current position, returning all of the
591    /// relevant information in the `FormatSpec` struct.
592    fn format(&mut self) -> FormatSpec<'input> {
593        let mut spec = FormatSpec::default();
594
595        if !self.consume(':') {
596            return spec;
597        }
598
599        // fill character
600        if let (Some((r, _, c)), Some((_, _, '>' | '<' | '^'))) = (self.peek(), self.peek_ahead()) {
601            self.input_vec_index += 1;
602            spec.fill = Some(c);
603            spec.fill_span = Some(r);
604        }
605        // Alignment
606        if self.consume('<') {
607            spec.align = AlignLeft;
608        } else if self.consume('>') {
609            spec.align = AlignRight;
610        } else if self.consume('^') {
611            spec.align = AlignCenter;
612        }
613        // Sign flags
614        if self.consume('+') {
615            spec.sign = Some(Sign::Plus);
616        } else if self.consume('-') {
617            spec.sign = Some(Sign::Minus);
618        }
619        // Alternate marker
620        if self.consume('#') {
621            spec.alternate = true;
622        }
623        // Width and precision
624        let mut havewidth = false;
625
626        if let Some((range, _)) = self.consume_pos('0') {
627            // small ambiguity with '0$' as a format string. In theory this is a
628            // '0' flag and then an ill-formatted format string with just a '$'
629            // and no count, but this is better if we instead interpret this as
630            // no '0' flag and '0$' as the width instead.
631            if let Some((r, _)) = self.consume_pos('$') {
632                spec.width = CountIsParam(0);
633                spec.width_span = Some(range.start..r.end);
634                havewidth = true;
635            } else {
636                spec.zero_pad = true;
637            }
638        }
639
640        if !havewidth {
641            let start_idx = self.input_vec_index;
642            spec.width = self.count();
643            if spec.width != CountImplied {
644                let end = self.input_vec_index2range(self.input_vec_index).start;
645                spec.width_span = Some(self.input_vec_index2range(start_idx).start..end);
646            }
647        }
648
649        if let Some((range, _)) = self.consume_pos('.') {
650            if self.consume('*') {
651                // Resolve `CountIsNextParam`.
652                // We can do this immediately as `position` is resolved later.
653                let i = self.curarg;
654                self.curarg += 1;
655                spec.precision = CountIsStar(i);
656            } else {
657                spec.precision = self.count();
658            }
659            spec.precision_span =
660                Some(range.start..self.input_vec_index2range(self.input_vec_index).start);
661        }
662
663        let start_idx = self.input_vec_index;
664        // Optional radix followed by the actual format specifier
665        if self.consume('x') {
666            if self.consume('?') {
667                spec.debug_hex = Some(DebugHex::Lower);
668                spec.ty = "?";
669            } else {
670                spec.ty = "x";
671            }
672        } else if self.consume('X') {
673            if self.consume('?') {
674                spec.debug_hex = Some(DebugHex::Upper);
675                spec.ty = "?";
676            } else {
677                spec.ty = "X";
678            }
679        } else if let Some((range, _)) = self.consume_pos('?') {
680            spec.ty = "?";
681            if let Some((r, _, c @ ('#' | 'x' | 'X'))) = self.peek() {
682                self.errors.insert(
683                    0,
684                    ParseError {
685                        description: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected `}}`, found `{0}`", c))
    })format!("expected `}}`, found `{c}`"),
686                        note: None,
687                        label: "expected `'}'`".into(),
688                        span: r.clone(),
689                        secondary_label: None,
690                        suggestion: Suggestion::ReorderFormatParameter(
691                            range.start..r.end,
692                            ::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}?", c)) })format!("{c}?"),
693                        ),
694                    },
695                );
696            }
697        } else {
698            spec.ty = self.word();
699            if !spec.ty.is_empty() {
700                let start = self.input_vec_index2range(start_idx).start;
701                let end = self.input_vec_index2range(self.input_vec_index).start;
702                spec.ty_span = Some(start..end);
703            }
704        }
705        spec
706    }
707
708    /// Parses an inline assembly template modifier at the current position, returning the modifier
709    /// in the `ty` field of the `FormatSpec` struct.
710    fn inline_asm(&mut self) -> FormatSpec<'input> {
711        let mut spec = FormatSpec::default();
712
713        if !self.consume(':') {
714            return spec;
715        }
716
717        let start_idx = self.input_vec_index;
718        spec.ty = self.word();
719        if !spec.ty.is_empty() {
720            let start = self.input_vec_index2range(start_idx).start;
721            let end = self.input_vec_index2range(self.input_vec_index).start;
722            spec.ty_span = Some(start..end);
723        }
724
725        spec
726    }
727
728    /// Always returns an empty `FormatSpec`
729    fn diagnostic(&mut self) -> FormatSpec<'input> {
730        let mut spec = FormatSpec::default();
731
732        let Some((Range { start, .. }, start_idx)) = self.consume_pos(':') else {
733            return spec;
734        };
735
736        spec.ty = self.string(start_idx);
737        spec.ty_span = {
738            let end = self.input_vec_index2range(self.input_vec_index).start;
739            Some(start..end)
740        };
741        spec
742    }
743
744    /// Parses a `Count` parameter at the current position. This does not check
745    /// for 'CountIsNextParam' because that is only used in precision, not
746    /// width.
747    fn count(&mut self) -> Count<'input> {
748        if let Some(i) = self.integer() {
749            if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) }
750        } else {
751            let start_idx = self.input_vec_index;
752            let word = self.word();
753            if word.is_empty() {
754                CountImplied
755            } else if let Some((r, _)) = self.consume_pos('$') {
756                CountIsName(word, self.input_vec_index2range(start_idx).start..r.start)
757            } else {
758                self.input_vec_index = start_idx;
759                CountImplied
760            }
761        }
762    }
763
764    /// Parses a word starting at the current position. A word is the same as a
765    /// Rust identifier or keyword, except that it can't be a bare `_` character.
766    fn word(&mut self) -> &'input str {
767        let index = self.input_vec_index;
768        match self.peek() {
769            Some((ref r, i, c)) if rustc_lexer::is_id_start(c) => {
770                self.input_vec_index += 1;
771                (r.start, i)
772            }
773            _ => {
774                return "";
775            }
776        };
777        let (err_end, end): (usize, usize) = loop {
778            if let Some((ref r, i, c)) = self.peek() {
779                if rustc_lexer::is_id_continue(c) {
780                    self.input_vec_index += 1;
781                } else {
782                    break (r.start, i);
783                }
784            } else {
785                break (self.end_of_snippet, self.input.len());
786            }
787        };
788
789        let word = &self.input[self.input_vec_index2pos(index)..end];
790        if word == "_" {
791            self.errors.push(ParseError {
792                description: "invalid argument name `_`".into(),
793                note: Some("argument name cannot be a single underscore".into()),
794                label: "invalid argument name".into(),
795                span: self.input_vec_index2range(index).start..err_end,
796                secondary_label: None,
797                suggestion: Suggestion::None,
798            });
799        }
800        word
801    }
802
803    fn integer(&mut self) -> Option<u16> {
804        let mut cur: u16 = 0;
805        let mut found = false;
806        let mut overflow = false;
807        let start_index = self.input_vec_index;
808        while let Some((_, _, c)) = self.peek() {
809            if let Some(i) = c.to_digit(10) {
810                self.input_vec_index += 1;
811                let (tmp, mul_overflow) = cur.overflowing_mul(10);
812                let (tmp, add_overflow) = tmp.overflowing_add(i as u16);
813                if mul_overflow || add_overflow {
814                    overflow = true;
815                }
816                cur = tmp;
817                found = true;
818            } else {
819                break;
820            }
821        }
822
823        if overflow {
824            let overflowed_int = &self.input[self.input_vec_index2pos(start_index)
825                ..self.input_vec_index2pos(self.input_vec_index)];
826            self.errors.push(ParseError {
827                description: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("integer `{0}` does not fit into the type `u16` whose range is `0..={1}`",
                overflowed_int, u16::MAX))
    })format!(
828                    "integer `{}` does not fit into the type `u16` whose range is `0..={}`",
829                    overflowed_int,
830                    u16::MAX
831                ),
832                note: None,
833                label: "integer out of range for `u16`".into(),
834                span: self.input_vec_index2range(start_index).start
835                    ..self.input_vec_index2range(self.input_vec_index).end,
836                secondary_label: None,
837                suggestion: Suggestion::None,
838            });
839        }
840
841        found.then_some(cur)
842    }
843
844    fn suggest_format_debug(&mut self) {
845        if let (Some((range, _)), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
846            let word = self.word();
847            self.errors.insert(
848                0,
849                ParseError {
850                    description: "expected format parameter to occur after `:`".to_owned(),
851                    note: Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`?` comes after `:`, try `{0}:{1}` instead",
                word, "?"))
    })format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
852                    label: "expected `?` to occur after `:`".to_owned(),
853                    span: range,
854                    secondary_label: None,
855                    suggestion: Suggestion::None,
856                },
857            );
858        }
859    }
860
861    fn missing_colon_before_debug_formatter(&mut self) {
862        if let Some((range, _)) = self.consume_pos('?') {
863            let span = range.clone();
864            self.errors.insert(
865                0,
866                ParseError {
867                    description: "expected `}`, found `?`".to_owned(),
868                    note: Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("to print `{{`, you can escape it using `{{{{`"))
    })format!("to print `{{`, you can escape it using `{{{{`",)),
869                    label: "expected `:` before `?` to format with `Debug`".to_owned(),
870                    span: range,
871                    secondary_label: None,
872                    suggestion: Suggestion::AddMissingColon(span),
873                },
874            );
875        }
876    }
877
878    fn suggest_rust_debug_printing_macro(&mut self) {
879        if let Some((range, _)) = self.consume_pos('=') {
880            self.errors.insert(
881                0,
882                ParseError {
883                    description:
884                        "python's f-string debug `=` is not supported in rust, use `dbg(x)` instead"
885                            .to_owned(),
886                    note: Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("to print `{{`, you can escape it using `{{{{`"))
    })format!("to print `{{`, you can escape it using `{{{{`",)),
887                    label: "expected `}`".to_owned(),
888                    span: range,
889                    secondary_label: self
890                        .last_open_brace
891                        .clone()
892                        .map(|sp| ("because of this opening brace".to_owned(), sp)),
893                    suggestion: Suggestion::UseRustDebugPrintingMacro,
894                },
895            );
896        }
897    }
898
899    fn suggest_format_align(&mut self, alignment: char) {
900        if let Some((range, _)) = self.consume_pos(alignment) {
901            self.errors.insert(
902                0,
903                ParseError {
904                    description:
905                        "expected alignment specifier after `:` in format string; example: `{:>?}`"
906                            .to_owned(),
907                    note: None,
908                    label: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected `{0}` to occur after `:`",
                alignment))
    })format!("expected `{}` to occur after `:`", alignment),
909                    span: range,
910                    secondary_label: None,
911                    suggestion: Suggestion::None,
912                },
913            );
914        }
915    }
916
917    fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: &Argument<'_>) {
918        // If the argument is not an identifier, it is not a field access.
919        if !arg.is_identifier() {
920            return;
921        }
922
923        if let Some((_range, _pos)) = self.consume_pos('.') {
924            let field = self.argument();
925            // We can only parse simple `foo.bar` field access or `foo.0` tuple index access, any
926            // deeper nesting, or another type of expression, like method calls, are not supported
927            if !self.consume('}') {
928                return;
929            }
930            if let ArgumentNamed(_) = arg.position {
931                match field.position {
932                    ArgumentNamed(_) => {
933                        self.errors.insert(
934                            0,
935                            ParseError {
936                                description: "field access isn't supported".to_string(),
937                                note: None,
938                                label: "not supported".to_string(),
939                                span: arg.position_span.start..field.position_span.end,
940                                secondary_label: None,
941                                suggestion: Suggestion::UsePositional,
942                            },
943                        );
944                    }
945                    ArgumentIs(_) => {
946                        self.errors.insert(
947                            0,
948                            ParseError {
949                                description: "tuple index access isn't supported".to_string(),
950                                note: None,
951                                label: "not supported".to_string(),
952                                span: arg.position_span.start..field.position_span.end,
953                                secondary_label: None,
954                                suggestion: Suggestion::UsePositional,
955                            },
956                        );
957                    }
958                    _ => {}
959                };
960            }
961        }
962    }
963
964    fn suggest_unsupported_python_numeric_grouping(&mut self) {
965        if let Some((range, _)) = self.consume_pos(',') {
966            self.errors.insert(
967                0,
968                ParseError {
969                    description:
970                        "python's numeric grouping `,` is not supported in rust format strings"
971                            .to_owned(),
972                    note: Some(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("to print `{{`, you can escape it using `{{{{`"))
    })format!("to print `{{`, you can escape it using `{{{{`",)),
973                    label: "expected `}`".to_owned(),
974                    span: range,
975                    secondary_label: self
976                        .last_open_brace
977                        .clone()
978                        .map(|sp| ("because of this opening brace".to_owned(), sp)),
979                    suggestion: Suggestion::None,
980                },
981            );
982        }
983    }
984}
985
986// Assert a reasonable size for `Piece`
987#[cfg(all(test, target_pointer_width = "64"))]
988rustc_index::static_assert_size!(Piece<'_>, 16);
989
990#[cfg(test)]
991mod tests;