Thanks to visit codestin.com
Credit goes to doc.rust-lang.org

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