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