Skip to main content

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