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