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#![warn(unreachable_pub)]
17// tidy-alphabetical-end
18
19pub use Alignment::*;
20pub use Count::*;
21pub use Position::*;
22use rustc_lexer::unescape;
23
24// Note: copied from rustc_span
25/// Range inside of a `Span` used for diagnostics when we only have access to relative positions.
26#[derive(Copy, Clone, PartialEq, Eq, Debug)]
27pub struct InnerSpan {
28    pub start: usize,
29    pub end: usize,
30}
31
32impl InnerSpan {
33    pub fn new(start: usize, end: usize) -> InnerSpan {
34        InnerSpan { start, end }
35    }
36}
37
38/// The location and before/after width of a character whose width has changed from its source code
39/// representation
40#[derive(Copy, Clone, PartialEq, Eq)]
41pub struct InnerWidthMapping {
42    /// Index of the character in the source
43    pub position: usize,
44    /// The inner width in characters
45    pub before: usize,
46    /// The transformed width in characters
47    pub after: usize,
48}
49
50impl InnerWidthMapping {
51    pub fn new(position: usize, before: usize, after: usize) -> InnerWidthMapping {
52        InnerWidthMapping { position, before, after }
53    }
54}
55
56/// Whether the input string is a literal. If yes, it contains the inner width mappings.
57#[derive(Clone, PartialEq, Eq)]
58enum InputStringKind {
59    NotALiteral,
60    Literal { width_mappings: Vec<InnerWidthMapping> },
61}
62
63/// The type of format string that we are parsing.
64#[derive(Copy, Clone, Debug, Eq, PartialEq)]
65pub enum ParseMode {
66    /// A normal format string as per `format_args!`.
67    Format,
68    /// An inline assembly template string for `asm!`.
69    InlineAsm,
70}
71
72#[derive(Copy, Clone)]
73struct InnerOffset(usize);
74
75impl InnerOffset {
76    fn to(self, end: InnerOffset) -> InnerSpan {
77        InnerSpan::new(self.0, end.0)
78    }
79}
80
81/// A piece is a portion of the format string which represents the next part
82/// to emit. These are emitted as a stream by the `Parser` class.
83#[derive(Clone, Debug, PartialEq)]
84pub enum Piece<'a> {
85    /// A literal string which should directly be emitted
86    Lit(&'a str),
87    /// This describes that formatting should process the next argument (as
88    /// specified inside) for emission.
89    NextArgument(Box<Argument<'a>>),
90}
91
92/// Representation of an argument specification.
93#[derive(Copy, Clone, Debug, PartialEq)]
94pub struct Argument<'a> {
95    /// Where to find this argument
96    pub position: Position<'a>,
97    /// The span of the position indicator. Includes any whitespace in implicit
98    /// positions (`{  }`).
99    pub position_span: InnerSpan,
100    /// How to format the argument
101    pub format: FormatSpec<'a>,
102}
103
104/// Specification for the formatting of an argument in the format string.
105#[derive(Copy, Clone, Debug, PartialEq)]
106pub struct FormatSpec<'a> {
107    /// Optionally specified character to fill alignment with.
108    pub fill: Option<char>,
109    /// Span of the optionally specified fill character.
110    pub fill_span: Option<InnerSpan>,
111    /// Optionally specified alignment.
112    pub align: Alignment,
113    /// The `+` or `-` flag.
114    pub sign: Option<Sign>,
115    /// The `#` flag.
116    pub alternate: bool,
117    /// The `0` flag.
118    pub zero_pad: bool,
119    /// The `x` or `X` flag. (Only for `Debug`.)
120    pub debug_hex: Option<DebugHex>,
121    /// The integer precision to use.
122    pub precision: Count<'a>,
123    /// The span of the precision formatting flag (for diagnostics).
124    pub precision_span: Option<InnerSpan>,
125    /// The string width requested for the resulting format.
126    pub width: Count<'a>,
127    /// The span of the width formatting flag (for diagnostics).
128    pub width_span: Option<InnerSpan>,
129    /// The descriptor string representing the name of the format desired for
130    /// this argument, this can be empty or any number of characters, although
131    /// it is required to be one word.
132    pub ty: &'a str,
133    /// The span of the descriptor string (for diagnostics).
134    pub ty_span: Option<InnerSpan>,
135}
136
137/// Enum describing where an argument for a format can be located.
138#[derive(Copy, Clone, Debug, PartialEq)]
139pub enum Position<'a> {
140    /// The argument is implied to be located at an index
141    ArgumentImplicitlyIs(usize),
142    /// The argument is located at a specific index given in the format,
143    ArgumentIs(usize),
144    /// The argument has a name.
145    ArgumentNamed(&'a str),
146}
147
148impl Position<'_> {
149    pub fn index(&self) -> Option<usize> {
150        match self {
151            ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
152            _ => None,
153        }
154    }
155}
156
157/// Enum of alignments which are supported.
158#[derive(Copy, Clone, Debug, PartialEq)]
159pub enum Alignment {
160    /// The value will be aligned to the left.
161    AlignLeft,
162    /// The value will be aligned to the right.
163    AlignRight,
164    /// The value will be aligned in the center.
165    AlignCenter,
166    /// The value will take on a default alignment.
167    AlignUnknown,
168}
169
170/// Enum for the sign flags.
171#[derive(Copy, Clone, Debug, PartialEq)]
172pub enum Sign {
173    /// The `+` flag.
174    Plus,
175    /// The `-` flag.
176    Minus,
177}
178
179/// Enum for the debug hex flags.
180#[derive(Copy, Clone, Debug, PartialEq)]
181pub enum DebugHex {
182    /// The `x` flag in `{:x?}`.
183    Lower,
184    /// The `X` flag in `{:X?}`.
185    Upper,
186}
187
188/// A count is used for the precision and width parameters of an integer, and
189/// can reference either an argument or a literal integer.
190#[derive(Copy, Clone, Debug, PartialEq)]
191pub enum Count<'a> {
192    /// The count is specified explicitly.
193    CountIs(usize),
194    /// The count is specified by the argument with the given name.
195    CountIsName(&'a str, InnerSpan),
196    /// The count is specified by the argument at the given index.
197    CountIsParam(usize),
198    /// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index.
199    CountIsStar(usize),
200    /// The count is implied and cannot be explicitly specified.
201    CountImplied,
202}
203
204pub struct ParseError {
205    pub description: String,
206    pub note: Option<String>,
207    pub label: String,
208    pub span: InnerSpan,
209    pub secondary_label: Option<(String, InnerSpan)>,
210    pub suggestion: Suggestion,
211}
212
213pub enum Suggestion {
214    None,
215    /// Replace inline argument with positional argument:
216    /// `format!("{foo.bar}")` -> `format!("{}", foo.bar)`
217    UsePositional,
218    /// Remove `r#` from identifier:
219    /// `format!("{r#foo}")` -> `format!("{foo}")`
220    RemoveRawIdent(InnerSpan),
221    /// Reorder format parameter:
222    /// `format!("{foo:?#}")` -> `format!("{foo:#?}")`
223    /// `format!("{foo:?x}")` -> `format!("{foo:x?}")`
224    /// `format!("{foo:?X}")` -> `format!("{foo:X?}")`
225    ReorderFormatParameter(InnerSpan, String),
226}
227
228/// The parser structure for interpreting the input format string. This is
229/// modeled as an iterator over `Piece` structures to form a stream of tokens
230/// being output.
231///
232/// This is a recursive-descent parser for the sake of simplicity, and if
233/// necessary there's probably lots of room for improvement performance-wise.
234pub struct Parser<'a> {
235    mode: ParseMode,
236    input: &'a str,
237    cur: std::iter::Peekable<std::str::CharIndices<'a>>,
238    /// Error messages accumulated during parsing
239    pub errors: Vec<ParseError>,
240    /// Current position of implicit positional argument pointer
241    pub curarg: usize,
242    /// `Some(raw count)` when the string is "raw", used to position spans correctly
243    style: Option<usize>,
244    /// Start and end byte offset of every successfully parsed argument
245    pub arg_places: Vec<InnerSpan>,
246    /// Characters whose length has been changed from their in-code representation
247    width_map: Vec<InnerWidthMapping>,
248    /// Span of the last opening brace seen, used for error reporting
249    last_opening_brace: Option<InnerSpan>,
250    /// Whether the source string is comes from `println!` as opposed to `format!` or `print!`
251    append_newline: bool,
252    /// Whether this formatting string was written directly in the source. This controls whether we
253    /// can use spans to refer into it and give better error messages.
254    /// N.B: This does _not_ control whether implicit argument captures can be used.
255    pub is_source_literal: bool,
256    /// Start position of the current line.
257    cur_line_start: usize,
258    /// Start and end byte offset of every line of the format string. Excludes
259    /// newline characters and leading whitespace.
260    pub line_spans: Vec<InnerSpan>,
261}
262
263impl<'a> Iterator for Parser<'a> {
264    type Item = Piece<'a>;
265
266    fn next(&mut self) -> Option<Piece<'a>> {
267        if let Some(&(pos, c)) = self.cur.peek() {
268            match c {
269                '{' => {
270                    let curr_last_brace = self.last_opening_brace;
271                    let byte_pos = self.to_span_index(pos);
272                    let lbrace_end = InnerOffset(byte_pos.0 + self.to_span_width(pos));
273                    self.last_opening_brace = Some(byte_pos.to(lbrace_end));
274                    self.cur.next();
275                    if self.consume('{') {
276                        self.last_opening_brace = curr_last_brace;
277
278                        Some(Piece::Lit(self.string(pos + 1)))
279                    } else {
280                        let arg = self.argument(lbrace_end);
281                        if let Some(rbrace_pos) = self.consume_closing_brace(&arg) {
282                            if self.is_source_literal {
283                                let lbrace_byte_pos = self.to_span_index(pos);
284                                let rbrace_byte_pos = self.to_span_index(rbrace_pos);
285
286                                let width = self.to_span_width(rbrace_pos);
287
288                                self.arg_places.push(
289                                    lbrace_byte_pos.to(InnerOffset(rbrace_byte_pos.0 + width)),
290                                );
291                            }
292                        } else if let Some(&(_, maybe)) = self.cur.peek() {
293                            match maybe {
294                                '?' => self.suggest_format_debug(),
295                                '<' | '^' | '>' => self.suggest_format_align(maybe),
296                                _ => self.suggest_positional_arg_instead_of_captured_arg(arg),
297                            }
298                        }
299                        Some(Piece::NextArgument(Box::new(arg)))
300                    }
301                }
302                '}' => {
303                    self.cur.next();
304                    if self.consume('}') {
305                        Some(Piece::Lit(self.string(pos + 1)))
306                    } else {
307                        let err_pos = self.to_span_index(pos);
308                        self.err_with_note(
309                            "unmatched `}` found",
310                            "unmatched `}`",
311                            "if you intended to print `}`, you can escape it using `}}`",
312                            err_pos.to(err_pos),
313                        );
314                        None
315                    }
316                }
317                _ => Some(Piece::Lit(self.string(pos))),
318            }
319        } else {
320            if self.is_source_literal {
321                let span = self.span(self.cur_line_start, self.input.len());
322                if self.line_spans.last() != Some(&span) {
323                    self.line_spans.push(span);
324                }
325            }
326            None
327        }
328    }
329}
330
331impl<'a> Parser<'a> {
332    /// Creates a new parser for the given format string
333    pub fn new(
334        s: &'a str,
335        style: Option<usize>,
336        snippet: Option<String>,
337        append_newline: bool,
338        mode: ParseMode,
339    ) -> Parser<'a> {
340        let input_string_kind = find_width_map_from_snippet(s, snippet, style);
341        let (width_map, is_source_literal) = match input_string_kind {
342            InputStringKind::Literal { width_mappings } => (width_mappings, true),
343            InputStringKind::NotALiteral => (Vec::new(), false),
344        };
345
346        Parser {
347            mode,
348            input: s,
349            cur: s.char_indices().peekable(),
350            errors: vec![],
351            curarg: 0,
352            style,
353            arg_places: vec![],
354            width_map,
355            last_opening_brace: None,
356            append_newline,
357            is_source_literal,
358            cur_line_start: 0,
359            line_spans: vec![],
360        }
361    }
362
363    /// Notifies of an error. The message doesn't actually need to be of type
364    /// String, but I think it does when this eventually uses conditions so it
365    /// might as well start using it now.
366    fn err(&mut self, description: impl Into<String>, label: impl Into<String>, span: InnerSpan) {
367        self.errors.push(ParseError {
368            description: description.into(),
369            note: None,
370            label: label.into(),
371            span,
372            secondary_label: None,
373            suggestion: Suggestion::None,
374        });
375    }
376
377    /// Notifies of an error. The message doesn't actually need to be of type
378    /// String, but I think it does when this eventually uses conditions so it
379    /// might as well start using it now.
380    fn err_with_note(
381        &mut self,
382        description: impl Into<String>,
383        label: impl Into<String>,
384        note: impl Into<String>,
385        span: InnerSpan,
386    ) {
387        self.errors.push(ParseError {
388            description: description.into(),
389            note: Some(note.into()),
390            label: label.into(),
391            span,
392            secondary_label: None,
393            suggestion: Suggestion::None,
394        });
395    }
396
397    /// Optionally consumes the specified character. If the character is not at
398    /// the current position, then the current iterator isn't moved and `false` is
399    /// returned, otherwise the character is consumed and `true` is returned.
400    fn consume(&mut self, c: char) -> bool {
401        self.consume_pos(c).is_some()
402    }
403
404    /// Optionally consumes the specified character. If the character is not at
405    /// the current position, then the current iterator isn't moved and `None` is
406    /// returned, otherwise the character is consumed and the current position is
407    /// returned.
408    fn consume_pos(&mut self, c: char) -> Option<usize> {
409        if let Some(&(pos, maybe)) = self.cur.peek() {
410            if c == maybe {
411                self.cur.next();
412                return Some(pos);
413            }
414        }
415        None
416    }
417
418    fn remap_pos(&self, mut pos: usize) -> InnerOffset {
419        for width in &self.width_map {
420            if pos > width.position {
421                pos += width.before - width.after;
422            } else if pos == width.position && width.after == 0 {
423                pos += width.before;
424            } else {
425                break;
426            }
427        }
428
429        InnerOffset(pos)
430    }
431
432    fn to_span_index(&self, pos: usize) -> InnerOffset {
433        // This handles the raw string case, the raw argument is the number of #
434        // in r###"..."### (we need to add one because of the `r`).
435        let raw = self.style.map_or(0, |raw| raw + 1);
436        let pos = self.remap_pos(pos);
437        InnerOffset(raw + pos.0 + 1)
438    }
439
440    fn to_span_width(&self, pos: usize) -> usize {
441        let pos = self.remap_pos(pos);
442        match self.width_map.iter().find(|w| w.position == pos.0) {
443            Some(w) => w.before,
444            None => 1,
445        }
446    }
447
448    fn span(&self, start_pos: usize, end_pos: usize) -> InnerSpan {
449        let start = self.to_span_index(start_pos);
450        let end = self.to_span_index(end_pos);
451        start.to(end)
452    }
453
454    /// Forces consumption of the specified character. If the character is not
455    /// found, an error is emitted.
456    fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<usize> {
457        self.ws();
458
459        let pos;
460        let description;
461
462        if let Some(&(peek_pos, maybe)) = self.cur.peek() {
463            if maybe == '}' {
464                self.cur.next();
465                return Some(peek_pos);
466            }
467
468            pos = peek_pos;
469            description = format!("expected `}}`, found `{}`", maybe.escape_debug());
470        } else {
471            description = "expected `}` but string was terminated".to_owned();
472            // point at closing `"`
473            pos = self.input.len() - if self.append_newline { 1 } else { 0 };
474        }
475
476        let pos = self.to_span_index(pos);
477
478        let label = "expected `}`".to_owned();
479        let (note, secondary_label) = if arg.format.fill == Some('}') {
480            (
481                Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
482                arg.format.fill_span.map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
483            )
484        } else {
485            (
486                Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
487                self.last_opening_brace.map(|sp| ("because of this opening brace".to_owned(), sp)),
488            )
489        };
490
491        self.errors.push(ParseError {
492            description,
493            note,
494            label,
495            span: pos.to(pos),
496            secondary_label,
497            suggestion: Suggestion::None,
498        });
499
500        None
501    }
502
503    /// Consumes all whitespace characters until the first non-whitespace character
504    fn ws(&mut self) {
505        while let Some(_) = self.cur.next_if(|&(_, c)| c.is_whitespace()) {}
506    }
507
508    /// Parses all of a string which is to be considered a "raw literal" in a
509    /// format string. This is everything outside of the braces.
510    fn string(&mut self, start: usize) -> &'a str {
511        // we may not consume the character, peek the iterator
512        while let Some(&(pos, c)) = self.cur.peek() {
513            match c {
514                '{' | '}' => {
515                    return &self.input[start..pos];
516                }
517                '\n' if self.is_source_literal => {
518                    self.line_spans.push(self.span(self.cur_line_start, pos));
519                    self.cur_line_start = pos + 1;
520                    self.cur.next();
521                }
522                _ => {
523                    if self.is_source_literal && pos == self.cur_line_start && c.is_whitespace() {
524                        self.cur_line_start = pos + c.len_utf8();
525                    }
526                    self.cur.next();
527                }
528            }
529        }
530        &self.input[start..]
531    }
532
533    /// Parses an `Argument` structure, or what's contained within braces inside the format string.
534    fn argument(&mut self, start: InnerOffset) -> Argument<'a> {
535        let pos = self.position();
536
537        let end = self
538            .cur
539            .clone()
540            .find(|(_, ch)| !ch.is_whitespace())
541            .map_or(start, |(end, _)| self.to_span_index(end));
542        let position_span = start.to(end);
543
544        let format = match self.mode {
545            ParseMode::Format => self.format(),
546            ParseMode::InlineAsm => self.inline_asm(),
547        };
548
549        // Resolve position after parsing format spec.
550        let pos = match pos {
551            Some(position) => position,
552            None => {
553                let i = self.curarg;
554                self.curarg += 1;
555                ArgumentImplicitlyIs(i)
556            }
557        };
558
559        Argument { position: pos, position_span, format }
560    }
561
562    /// Parses a positional argument for a format. This could either be an
563    /// integer index of an argument, a named argument, or a blank string.
564    /// Returns `Some(parsed_position)` if the position is not implicitly
565    /// consuming a macro argument, `None` if it's the case.
566    fn position(&mut self) -> Option<Position<'a>> {
567        if let Some(i) = self.integer() {
568            Some(ArgumentIs(i))
569        } else {
570            match self.cur.peek() {
571                Some(&(lo, c)) if rustc_lexer::is_id_start(c) => {
572                    let word = self.word();
573
574                    // Recover from `r#ident` in format strings.
575                    // FIXME: use a let chain
576                    if word == "r" {
577                        if let Some((pos, '#')) = self.cur.peek() {
578                            if self.input[pos + 1..]
579                                .chars()
580                                .next()
581                                .is_some_and(rustc_lexer::is_id_start)
582                            {
583                                self.cur.next();
584                                let word = self.word();
585                                let prefix_span = self.span(lo, lo + 2);
586                                let full_span = self.span(lo, lo + 2 + word.len());
587                                self.errors.insert(0, ParseError {
588                                    description: "raw identifiers are not supported".to_owned(),
589                                    note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
590                                    label: "raw identifier used here".to_owned(),
591                                    span: full_span,
592                                    secondary_label: None,
593                                    suggestion: Suggestion::RemoveRawIdent(prefix_span),
594                                });
595                                return Some(ArgumentNamed(word));
596                            }
597                        }
598                    }
599
600                    Some(ArgumentNamed(word))
601                }
602
603                // This is an `ArgumentNext`.
604                // Record the fact and do the resolution after parsing the
605                // format spec, to make things like `{:.*}` work.
606                _ => None,
607            }
608        }
609    }
610
611    fn current_pos(&mut self) -> usize {
612        if let Some(&(pos, _)) = self.cur.peek() { pos } else { self.input.len() }
613    }
614
615    /// Parses a format specifier at the current position, returning all of the
616    /// relevant information in the `FormatSpec` struct.
617    fn format(&mut self) -> FormatSpec<'a> {
618        let mut spec = FormatSpec {
619            fill: None,
620            fill_span: None,
621            align: AlignUnknown,
622            sign: None,
623            alternate: false,
624            zero_pad: false,
625            debug_hex: None,
626            precision: CountImplied,
627            precision_span: None,
628            width: CountImplied,
629            width_span: None,
630            ty: &self.input[..0],
631            ty_span: None,
632        };
633        if !self.consume(':') {
634            return spec;
635        }
636
637        // fill character
638        if let Some(&(idx, c)) = self.cur.peek() {
639            if let Some((_, '>' | '<' | '^')) = self.cur.clone().nth(1) {
640                spec.fill = Some(c);
641                spec.fill_span = Some(self.span(idx, idx + 1));
642                self.cur.next();
643            }
644        }
645        // Alignment
646        if self.consume('<') {
647            spec.align = AlignLeft;
648        } else if self.consume('>') {
649            spec.align = AlignRight;
650        } else if self.consume('^') {
651            spec.align = AlignCenter;
652        }
653        // Sign flags
654        if self.consume('+') {
655            spec.sign = Some(Sign::Plus);
656        } else if self.consume('-') {
657            spec.sign = Some(Sign::Minus);
658        }
659        // Alternate marker
660        if self.consume('#') {
661            spec.alternate = true;
662        }
663        // Width and precision
664        let mut havewidth = false;
665
666        if self.consume('0') {
667            // small ambiguity with '0$' as a format string. In theory this is a
668            // '0' flag and then an ill-formatted format string with just a '$'
669            // and no count, but this is better if we instead interpret this as
670            // no '0' flag and '0$' as the width instead.
671            if let Some(end) = self.consume_pos('$') {
672                spec.width = CountIsParam(0);
673                spec.width_span = Some(self.span(end - 1, end + 1));
674                havewidth = true;
675            } else {
676                spec.zero_pad = true;
677            }
678        }
679
680        if !havewidth {
681            let start = self.current_pos();
682            spec.width = self.count(start);
683            if spec.width != CountImplied {
684                let end = self.current_pos();
685                spec.width_span = Some(self.span(start, end));
686            }
687        }
688
689        if let Some(start) = self.consume_pos('.') {
690            if self.consume('*') {
691                // Resolve `CountIsNextParam`.
692                // We can do this immediately as `position` is resolved later.
693                let i = self.curarg;
694                self.curarg += 1;
695                spec.precision = CountIsStar(i);
696            } else {
697                spec.precision = self.count(start + 1);
698            }
699            let end = self.current_pos();
700            spec.precision_span = Some(self.span(start, end));
701        }
702
703        let ty_span_start = self.current_pos();
704        // Optional radix followed by the actual format specifier
705        if self.consume('x') {
706            if self.consume('?') {
707                spec.debug_hex = Some(DebugHex::Lower);
708                spec.ty = "?";
709            } else {
710                spec.ty = "x";
711            }
712        } else if self.consume('X') {
713            if self.consume('?') {
714                spec.debug_hex = Some(DebugHex::Upper);
715                spec.ty = "?";
716            } else {
717                spec.ty = "X";
718            }
719        } else if self.consume('?') {
720            spec.ty = "?";
721            if let Some(&(_, maybe)) = self.cur.peek() {
722                match maybe {
723                    '#' | 'x' | 'X' => self.suggest_format_parameter(maybe),
724                    _ => (),
725                }
726            }
727        } else {
728            spec.ty = self.word();
729            if !spec.ty.is_empty() {
730                let ty_span_end = self.current_pos();
731                spec.ty_span = Some(self.span(ty_span_start, ty_span_end));
732            }
733        }
734        spec
735    }
736
737    /// Parses an inline assembly template modifier at the current position, returning the modifier
738    /// in the `ty` field of the `FormatSpec` struct.
739    fn inline_asm(&mut self) -> FormatSpec<'a> {
740        let mut spec = FormatSpec {
741            fill: None,
742            fill_span: None,
743            align: AlignUnknown,
744            sign: None,
745            alternate: false,
746            zero_pad: false,
747            debug_hex: None,
748            precision: CountImplied,
749            precision_span: None,
750            width: CountImplied,
751            width_span: None,
752            ty: &self.input[..0],
753            ty_span: None,
754        };
755        if !self.consume(':') {
756            return spec;
757        }
758
759        let ty_span_start = self.current_pos();
760        spec.ty = self.word();
761        if !spec.ty.is_empty() {
762            let ty_span_end = self.current_pos();
763            spec.ty_span = Some(self.span(ty_span_start, ty_span_end));
764        }
765
766        spec
767    }
768
769    /// Parses a `Count` parameter at the current position. This does not check
770    /// for 'CountIsNextParam' because that is only used in precision, not
771    /// width.
772    fn count(&mut self, start: usize) -> Count<'a> {
773        if let Some(i) = self.integer() {
774            if self.consume('$') { CountIsParam(i) } else { CountIs(i) }
775        } else {
776            let tmp = self.cur.clone();
777            let word = self.word();
778            if word.is_empty() {
779                self.cur = tmp;
780                CountImplied
781            } else if let Some(end) = self.consume_pos('$') {
782                let name_span = self.span(start, end);
783                CountIsName(word, name_span)
784            } else {
785                self.cur = tmp;
786                CountImplied
787            }
788        }
789    }
790
791    /// Parses a word starting at the current position. A word is the same as
792    /// Rust identifier, except that it can't start with `_` character.
793    fn word(&mut self) -> &'a str {
794        let start = match self.cur.peek() {
795            Some(&(pos, c)) if rustc_lexer::is_id_start(c) => {
796                self.cur.next();
797                pos
798            }
799            _ => {
800                return "";
801            }
802        };
803        let mut end = None;
804        while let Some(&(pos, c)) = self.cur.peek() {
805            if rustc_lexer::is_id_continue(c) {
806                self.cur.next();
807            } else {
808                end = Some(pos);
809                break;
810            }
811        }
812        let end = end.unwrap_or(self.input.len());
813        let word = &self.input[start..end];
814        if word == "_" {
815            self.err_with_note(
816                "invalid argument name `_`",
817                "invalid argument name",
818                "argument name cannot be a single underscore",
819                self.span(start, end),
820            );
821        }
822        word
823    }
824
825    fn integer(&mut self) -> Option<usize> {
826        let mut cur: usize = 0;
827        let mut found = false;
828        let mut overflow = false;
829        let start = self.current_pos();
830        while let Some(&(_, c)) = self.cur.peek() {
831            if let Some(i) = c.to_digit(10) {
832                let (tmp, mul_overflow) = cur.overflowing_mul(10);
833                let (tmp, add_overflow) = tmp.overflowing_add(i as usize);
834                if mul_overflow || add_overflow {
835                    overflow = true;
836                }
837                cur = tmp;
838                found = true;
839                self.cur.next();
840            } else {
841                break;
842            }
843        }
844
845        if overflow {
846            let end = self.current_pos();
847            let overflowed_int = &self.input[start..end];
848            self.err(
849                format!(
850                    "integer `{}` does not fit into the type `usize` whose range is `0..={}`",
851                    overflowed_int,
852                    usize::MAX
853                ),
854                "integer out of range for `usize`",
855                self.span(start, end),
856            );
857        }
858
859        found.then_some(cur)
860    }
861
862    fn suggest_format_debug(&mut self) {
863        if let (Some(pos), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
864            let word = self.word();
865            let pos = self.to_span_index(pos);
866            self.errors.insert(
867                0,
868                ParseError {
869                    description: "expected format parameter to occur after `:`".to_owned(),
870                    note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
871                    label: "expected `?` to occur after `:`".to_owned(),
872                    span: pos.to(pos),
873                    secondary_label: None,
874                    suggestion: Suggestion::None,
875                },
876            );
877        }
878    }
879
880    fn suggest_format_align(&mut self, alignment: char) {
881        if let Some(pos) = self.consume_pos(alignment) {
882            let pos = self.to_span_index(pos);
883            self.errors.insert(
884                0,
885                ParseError {
886                    description: "expected format parameter to occur after `:`".to_owned(),
887                    note: None,
888                    label: format!("expected `{}` to occur after `:`", alignment),
889                    span: pos.to(pos),
890                    secondary_label: None,
891                    suggestion: Suggestion::None,
892                },
893            );
894        }
895    }
896
897    fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) {
898        if let Some(end) = self.consume_pos('.') {
899            let byte_pos = self.to_span_index(end);
900            let start = InnerOffset(byte_pos.0 + 1);
901            let field = self.argument(start);
902            // We can only parse simple `foo.bar` field access or `foo.0` tuple index access, any
903            // deeper nesting, or another type of expression, like method calls, are not supported
904            if !self.consume('}') {
905                return;
906            }
907            if let ArgumentNamed(_) = arg.position {
908                match field.position {
909                    ArgumentNamed(_) => {
910                        self.errors.insert(
911                            0,
912                            ParseError {
913                                description: "field access isn't supported".to_string(),
914                                note: None,
915                                label: "not supported".to_string(),
916                                span: InnerSpan::new(
917                                    arg.position_span.start,
918                                    field.position_span.end,
919                                ),
920                                secondary_label: None,
921                                suggestion: Suggestion::UsePositional,
922                            },
923                        );
924                    }
925                    ArgumentIs(_) => {
926                        self.errors.insert(
927                            0,
928                            ParseError {
929                                description: "tuple index access isn't supported".to_string(),
930                                note: None,
931                                label: "not supported".to_string(),
932                                span: InnerSpan::new(
933                                    arg.position_span.start,
934                                    field.position_span.end,
935                                ),
936                                secondary_label: None,
937                                suggestion: Suggestion::UsePositional,
938                            },
939                        );
940                    }
941                    _ => {}
942                };
943            }
944        }
945    }
946
947    fn suggest_format_parameter(&mut self, c: char) {
948        let replacement = match c {
949            '#' => "#?",
950            'x' => "x?",
951            'X' => "X?",
952            _ => return,
953        };
954        let Some(pos) = self.consume_pos(c) else {
955            return;
956        };
957
958        let span = self.span(pos - 1, pos + 1);
959        let pos = self.to_span_index(pos);
960
961        self.errors.insert(
962            0,
963            ParseError {
964                description: format!("expected `}}`, found `{c}`"),
965                note: None,
966                label: "expected `'}'`".into(),
967                span: pos.to(pos),
968                secondary_label: None,
969                suggestion: Suggestion::ReorderFormatParameter(span, format!("{replacement}")),
970            },
971        )
972    }
973}
974
975/// Finds the indices of all characters that have been processed and differ between the actual
976/// written code (code snippet) and the `InternedString` that gets processed in the `Parser`
977/// in order to properly synthesise the intra-string `Span`s for error diagnostics.
978fn find_width_map_from_snippet(
979    input: &str,
980    snippet: Option<String>,
981    str_style: Option<usize>,
982) -> InputStringKind {
983    let snippet = match snippet {
984        Some(ref s) if s.starts_with('"') || s.starts_with("r\"") || s.starts_with("r#") => s,
985        _ => return InputStringKind::NotALiteral,
986    };
987
988    if str_style.is_some() {
989        return InputStringKind::Literal { width_mappings: Vec::new() };
990    }
991
992    // Strip quotes.
993    let snippet = &snippet[1..snippet.len() - 1];
994
995    // Macros like `println` add a newline at the end. That technically doesn't make them "literals" anymore, but it's fine
996    // since we will never need to point our spans there, so we lie about it here by ignoring it.
997    // Since there might actually be newlines in the source code, we need to normalize away all trailing newlines.
998    // If we only trimmed it off the input, `format!("\n")` would cause a mismatch as here we they actually match up.
999    // Alternatively, we could just count the trailing newlines and only trim one from the input if they don't match up.
1000    let input_no_nl = input.trim_end_matches('\n');
1001    let Some(unescaped) = unescape_string(snippet) else {
1002        return InputStringKind::NotALiteral;
1003    };
1004
1005    let unescaped_no_nl = unescaped.trim_end_matches('\n');
1006
1007    if unescaped_no_nl != input_no_nl {
1008        // The source string that we're pointing at isn't our input, so spans pointing at it will be incorrect.
1009        // This can for example happen with proc macros that respan generated literals.
1010        return InputStringKind::NotALiteral;
1011    }
1012
1013    let mut s = snippet.char_indices();
1014    let mut width_mappings = vec![];
1015    while let Some((pos, c)) = s.next() {
1016        match (c, s.clone().next()) {
1017            // skip whitespace and empty lines ending in '\\'
1018            ('\\', Some((_, '\n'))) => {
1019                let _ = s.next();
1020                let mut width = 2;
1021
1022                while let Some((_, c)) = s.clone().next() {
1023                    if matches!(c, ' ' | '\n' | '\t') {
1024                        width += 1;
1025                        let _ = s.next();
1026                    } else {
1027                        break;
1028                    }
1029                }
1030
1031                width_mappings.push(InnerWidthMapping::new(pos, width, 0));
1032            }
1033            ('\\', Some((_, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
1034                width_mappings.push(InnerWidthMapping::new(pos, 2, 1));
1035                let _ = s.next();
1036            }
1037            ('\\', Some((_, 'x'))) => {
1038                // consume `\xAB` literal
1039                s.nth(2);
1040                width_mappings.push(InnerWidthMapping::new(pos, 4, 1));
1041            }
1042            ('\\', Some((_, 'u'))) => {
1043                let mut width = 2;
1044                let _ = s.next();
1045
1046                if let Some((_, next_c)) = s.next() {
1047                    if next_c == '{' {
1048                        // consume up to 6 hexanumeric chars
1049                        let digits_len =
1050                            s.clone().take(6).take_while(|(_, c)| c.is_ascii_hexdigit()).count();
1051
1052                        let len_utf8 = s
1053                            .as_str()
1054                            .get(..digits_len)
1055                            .and_then(|digits| u32::from_str_radix(digits, 16).ok())
1056                            .and_then(char::from_u32)
1057                            .map_or(1, char::len_utf8);
1058
1059                        // Skip the digits, for chars that encode to more than 1 utf-8 byte
1060                        // exclude as many digits as it is greater than 1 byte
1061                        //
1062                        // So for a 3 byte character, exclude 2 digits
1063                        let required_skips = digits_len.saturating_sub(len_utf8.saturating_sub(1));
1064
1065                        // skip '{' and '}' also
1066                        width += required_skips + 2;
1067
1068                        s.nth(digits_len);
1069                    } else if next_c.is_ascii_hexdigit() {
1070                        width += 1;
1071
1072                        // We suggest adding `{` and `}` when appropriate, accept it here as if
1073                        // it were correct
1074                        let mut i = 0; // consume up to 6 hexanumeric chars
1075                        while let (Some((_, c)), _) = (s.next(), i < 6) {
1076                            if c.is_ascii_hexdigit() {
1077                                width += 1;
1078                            } else {
1079                                break;
1080                            }
1081                            i += 1;
1082                        }
1083                    }
1084                }
1085
1086                width_mappings.push(InnerWidthMapping::new(pos, width, 1));
1087            }
1088            _ => {}
1089        }
1090    }
1091
1092    InputStringKind::Literal { width_mappings }
1093}
1094
1095fn unescape_string(string: &str) -> Option<String> {
1096    let mut buf = String::new();
1097    let mut ok = true;
1098    unescape::unescape_unicode(string, unescape::Mode::Str, &mut |_, unescaped_char| {
1099        match unescaped_char {
1100            Ok(c) => buf.push(c),
1101            Err(_) => ok = false,
1102        }
1103    });
1104
1105    ok.then_some(buf)
1106}
1107
1108// Assert a reasonable size for `Piece`
1109#[cfg(target_pointer_width = "64")]
1110rustc_index::static_assert_size!(Piece<'_>, 16);
1111
1112#[cfg(test)]
1113mod tests;