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