//! Macro support for format strings
//!
//! These structures are used when parsing format strings for the compiler.
//! Parsing does not happen at runtime: structures of `std::fmt::rt` are
//! generated instead.

// tidy-alphabetical-start
// We want to be able to build this crate with a stable compiler,
// so no `#![feature]` attributes should be added.
#![deny(unstable_features)]
#![doc(test(attr(deny(warnings), allow(internal_features))))]
// tidy-alphabetical-end

use std::ops::Range;

pub use Alignment::*;
pub use Count::*;
pub use Position::*;

/// The type of format string that we are parsing.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ParseMode {
    /// A normal format string as per `format_args!`.
    Format,
    /// An inline assembly template string for `asm!`.
    InlineAsm,
    /// A format string for use in diagnostic attributes.
    ///
    /// Similar to `format_args!`, however only named ("captured") arguments
    /// are allowed, and no format modifiers are permitted.
    Diagnostic,
}

/// A piece is a portion of the format string which represents the next part
/// to emit. These are emitted as a stream by the `Parser` class.
#[derive(Clone, Debug, PartialEq)]
pub enum Piece<'input> {
    /// A literal string which should directly be emitted
    Lit(&'input str),
    /// This describes that formatting should process the next argument (as
    /// specified inside) for emission.
    NextArgument(Box<Argument<'input>>),
}

/// Representation of an argument specification.
#[derive(Clone, Debug, PartialEq)]
pub struct Argument<'input> {
    /// Where to find this argument
    pub position: Position<'input>,
    /// The span of the position indicator. Includes any whitespace in implicit
    /// positions (`{  }`).
    pub position_span: Range<usize>,
    /// How to format the argument
    pub format: FormatSpec<'input>,
}

impl<'input> Argument<'input> {
    pub fn is_identifier(&self) -> bool {
        matches!(self.position, Position::ArgumentNamed(_)) && self.format == FormatSpec::default()
    }
}

/// Specification for the formatting of an argument in the format string.
#[derive(Clone, Debug, PartialEq, Default)]
pub struct FormatSpec<'input> {
    /// Optionally specified character to fill alignment with.
    pub fill: Option<char>,
    /// Span of the optionally specified fill character.
    pub fill_span: Option<Range<usize>>,
    /// Optionally specified alignment.
    pub align: Alignment,
    /// The `+` or `-` flag.
    pub sign: Option<Sign>,
    /// The `#` flag.
    pub alternate: bool,
    /// The `0` flag.
    pub zero_pad: bool,
    /// The `x` or `X` flag. (Only for `Debug`.)
    pub debug_hex: Option<DebugHex>,
    /// The integer precision to use.
    pub precision: Count<'input>,
    /// The span of the precision formatting flag (for diagnostics).
    pub precision_span: Option<Range<usize>>,
    /// The string width requested for the resulting format.
    pub width: Count<'input>,
    /// The span of the width formatting flag (for diagnostics).
    pub width_span: Option<Range<usize>>,
    /// The descriptor string representing the name of the format desired for
    /// this argument, this can be empty or any number of characters, although
    /// it is required to be one word.
    pub ty: &'input str,
    /// The span of the descriptor string (for diagnostics).
    pub ty_span: Option<Range<usize>>,
}

/// Enum describing where an argument for a format can be located.
#[derive(Clone, Debug, PartialEq)]
pub enum Position<'input> {
    /// The argument is implied to be located at an index
    ArgumentImplicitlyIs(usize),
    /// The argument is located at a specific index given in the format,
    ArgumentIs(usize),
    /// The argument has a name.
    ArgumentNamed(&'input str),
}

impl Position<'_> {
    pub fn index(&self) -> Option<usize> {
        match self {
            ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
            _ => None,
        }
    }
}

/// Enum of alignments which are supported.
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum Alignment {
    /// The value will be aligned to the left.
    AlignLeft,
    /// The value will be aligned to the right.
    AlignRight,
    /// The value will be aligned in the center.
    AlignCenter,
    /// The value will take on a default alignment.
    #[default]
    AlignUnknown,
}

/// Enum for the sign flags.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Sign {
    /// The `+` flag.
    Plus,
    /// The `-` flag.
    Minus,
}

/// Enum for the debug hex flags.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum DebugHex {
    /// The `x` flag in `{:x?}`.
    Lower,
    /// The `X` flag in `{:X?}`.
    Upper,
}

/// A count is used for the precision and width parameters of an integer, and
/// can reference either an argument or a literal integer.
#[derive(Clone, Debug, PartialEq, Default)]
pub enum Count<'input> {
    /// The count is specified explicitly.
    CountIs(u16),
    /// The count is specified by the argument with the given name.
    CountIsName(&'input str, Range<usize>),
    /// The count is specified by the argument at the given index.
    CountIsParam(usize),
    /// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index.
    CountIsStar(usize),
    /// The count is implied and cannot be explicitly specified.
    #[default]
    CountImplied,
}

pub struct ParseError {
    pub description: String,
    pub note: Option<String>,
    pub label: String,
    pub span: Range<usize>,
    pub secondary_label: Option<(String, Range<usize>)>,
    pub suggestion: Suggestion,
}

pub enum Suggestion {
    None,
    /// Replace inline argument with positional argument:
    /// `format!("{foo.bar}")` -> `format!("{}", foo.bar)`
    UsePositional,
    /// Remove `r#` from identifier:
    /// `format!("{r#foo}")` -> `format!("{foo}")`
    RemoveRawIdent(Range<usize>),
    /// Reorder format parameter:
    /// `format!("{foo:?#}")` -> `format!("{foo:#?}")`
    /// `format!("{foo:?x}")` -> `format!("{foo:x?}")`
    /// `format!("{foo:?X}")` -> `format!("{foo:X?}")`
    ReorderFormatParameter(Range<usize>, String),
    /// Add missing colon:
    /// `format!("{foo?}")` -> `format!("{foo:?}")`
    AddMissingColon(Range<usize>),
    /// Use Rust format string:
    /// `format!("{x=}")` -> `dbg!(x)`
    UseRustDebugPrintingMacro,
}

/// The parser structure for interpreting the input format string. This is
/// modeled as an iterator over `Piece` structures to form a stream of tokens
/// being output.
///
/// This is a recursive-descent parser for the sake of simplicity, and if
/// necessary there's probably lots of room for improvement performance-wise.
pub struct Parser<'input> {
    mode: ParseMode,
    /// Input to be parsed
    input: &'input str,
    /// Tuples of the span in the code snippet (input as written before being unescaped), the pos in input, and the char in input
    input_vec: Vec<(Range<usize>, usize, char)>,
    /// Index into input_vec
    input_vec_index: usize,
    /// Error messages accumulated during parsing
    pub errors: Vec<ParseError>,
    /// Current position of implicit positional argument pointer
    pub curarg: usize,
    /// Start and end byte offset of every successfully parsed argument
    pub arg_places: Vec<Range<usize>>,
    /// Span of the last opening brace seen, used for error reporting
    last_open_brace: Option<Range<usize>>,
    /// Whether this formatting string was written directly in the source. This controls whether we
    /// can use spans to refer into it and give better error messages.
    /// N.B: This does _not_ control whether implicit argument captures can be used.
    pub is_source_literal: bool,
    /// Index to the end of the literal snippet
    end_of_snippet: usize,
    /// Start position of the current line.
    cur_line_start: usize,
    /// Start and end byte offset of every line of the format string. Excludes
    /// newline characters and leading whitespace.
    pub line_spans: Vec<Range<usize>>,
}

impl<'input> Iterator for Parser<'input> {
    type Item = Piece<'input>;

    fn next(&mut self) -> Option<Piece<'input>> {
        if let Some((Range { start, end }, idx, ch)) = self.peek() {
            match ch {
                '{' => {
                    self.input_vec_index += 1;
                    if let Some((_, i, '{')) = self.peek() {
                        self.input_vec_index += 1;
                        // double open brace escape: "{{"
                        // next state after this is either end-of-input or seen-a-brace
                        Some(Piece::Lit(self.string(i)))
                    } else {
                        // single open brace
                        self.last_open_brace = Some(start..end);
                        let arg = self.argument();
                        self.ws();
                        if let Some((close_brace_range, _)) = self.consume_pos('}') {
                            if self.is_source_literal {
                                self.arg_places.push(start..close_brace_range.end);
                            }
                        } else {
                            self.missing_closing_brace(&arg);
                        }

                        Some(Piece::NextArgument(Box::new(arg)))
                    }
                }
                '}' => {
                    self.input_vec_index += 1;
                    if let Some((_, i, '}')) = self.peek() {
                        self.input_vec_index += 1;
                        // double close brace escape: "}}"
                        // next state after this is either end-of-input or start
                        Some(Piece::Lit(self.string(i)))
                    } else {
                        // error: single close brace without corresponding open brace
                        self.errors.push(ParseError {
                            description: "unmatched `}` found".into(),
                            note: Some(
                                "if you intended to print `}`, you can escape it using `}}`".into(),
                            ),
                            label: "unmatched `}`".into(),
                            span: start..end,
                            secondary_label: None,
                            suggestion: Suggestion::None,
                        });
                        None
                    }
                }
                _ => Some(Piece::Lit(self.string(idx))),
            }
        } else {
            // end of input
            if self.is_source_literal {
                let span = self.cur_line_start..self.end_of_snippet;
                if self.line_spans.last() != Some(&span) {
                    self.line_spans.push(span);
                }
            }
            None
        }
    }
}

impl<'input> Parser<'input> {
    /// Creates a new parser for the given unescaped input string and
    /// optional code snippet (the input as written before being unescaped),
    /// where `style` is `Some(nr_hashes)` when the snippet is a raw string with that many hashes.
    /// If the input comes via `println` or `panic`, then it has a newline already appended,
    /// which is reflected in the `appended_newline` parameter.
    pub fn new(
        input: &'input str,
        style: Option<usize>,
        snippet: Option<String>,
        appended_newline: bool,
        mode: ParseMode,
    ) -> Self {
        let quote_offset = style.map_or(1, |nr_hashes| nr_hashes + 2);

        let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
            if let Some(nr_hashes) = style {
                // snippet is a raw string

                // validate snippet because a proc macro may have
                // respanned it to something completely different (fixes #114865)
                let prefix_len = nr_hashes + 2; // r + hashes + opening "
                let suffix_len = nr_hashes + 1; // closing " + hashes
                let snippet_bytes = snippet.as_bytes();
                let content_end = snippet.len() - suffix_len;
                if snippet.len() >= prefix_len + suffix_len // is sufficiently long
                    && snippet_bytes[0] == b'r'
                    && snippet_bytes[1..1 + nr_hashes].iter().all(|&c| c == b'#')
                    && snippet_bytes[1 + nr_hashes] == b'"'
                    && snippet_bytes[content_end] == b'"'
                    && snippet_bytes[content_end + 1..].iter().all(|&c| c == b'#')
                {
                    let snippet_without_quotes = &snippet[prefix_len..content_end];
                    let input_without_newline =
                        if appended_newline { &input[..input.len() - 1] } else { input };
                    if snippet_without_quotes == input_without_newline {
                        (true, snippet.len() - suffix_len, vec![])
                    } else {
                        (false, snippet.len(), vec![])
                    }
                } else {
                    (false, snippet.len(), vec![])
                }
            } else {
                // snippet is not a raw string
                if snippet.starts_with('"') {
                    // snippet looks like an ordinary string literal
                    // check whether it is the escaped version of input
                    let snippet_without_quotes = &snippet[1..snippet.len() - 1];
                    let (mut ok, mut vec) = (true, vec![]);
                    let mut chars = input.chars();
                    rustc_literal_escaper::unescape_str(snippet_without_quotes, |range, res| {
                        match res {
                            Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
                                vec.push((range, ch));
                            }
                            _ => {
                                ok = false;
                                vec = vec![];
                            }
                        }
                    });
                    let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
                    if ok {
                        if appended_newline {
                            if chars.as_str() == "\n" {
                                vec.push((end..end + 1, '\n'));
                                (true, 1 + end, vec)
                            } else {
                                (false, snippet.len(), vec![])
                            }
                        } else if chars.as_str() == "" {
                            (true, 1 + end, vec)
                        } else {
                            (false, snippet.len(), vec![])
                        }
                    } else {
                        (false, snippet.len(), vec![])
                    }
                } else {
                    // snippet is not a raw string and does not start with '"'
                    (false, snippet.len(), vec![])
                }
            }
        } else {
            // snippet is None
            (false, input.len() - if appended_newline { 1 } else { 0 }, vec![])
        };

        let input_vec: Vec<(Range<usize>, usize, char)> = if pre_input_vec.is_empty() {
            // Snippet is *not* input before unescaping, so spans pointing at it will be incorrect.
            // This can happen with proc macros that respan generated literals.
            input
                .char_indices()
                .map(|(idx, c)| {
                    let i = idx + quote_offset;
                    (i..i + c.len_utf8(), idx, c)
                })
                .collect()
        } else {
            // Snippet is input before unescaping
            input
                .char_indices()
                .zip(pre_input_vec)
                .map(|((i, c), (r, _))| (r.start + quote_offset..r.end + quote_offset, i, c))
                .collect()
        };

        Parser {
            mode,
            input,
            input_vec,
            input_vec_index: 0,
            errors: vec![],
            curarg: 0,
            arg_places: vec![],
            last_open_brace: None,
            is_source_literal,
            end_of_snippet,
            cur_line_start: quote_offset,
            line_spans: vec![],
        }
    }

    /// Peeks at the current position, without incrementing the pointer.
    pub fn peek(&self) -> Option<(Range<usize>, usize, char)> {
        self.input_vec.get(self.input_vec_index).cloned()
    }

    /// Peeks at the current position + 1, without incrementing the pointer.
    pub fn peek_ahead(&self) -> Option<(Range<usize>, usize, char)> {
        self.input_vec.get(self.input_vec_index + 1).cloned()
    }

    /// Optionally consumes the specified character. If the character is not at
    /// the current position, then the current iterator isn't moved and `false` is
    /// returned, otherwise the character is consumed and `true` is returned.
    fn consume(&mut self, c: char) -> bool {
        self.consume_pos(c).is_some()
    }

    /// Optionally consumes the specified character. If the character is not at
    /// the current position, then the current iterator isn't moved and `None` is
    /// returned, otherwise the character is consumed and the current position is
    /// returned.
    fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> {
        if let Some((r, i, c)) = self.peek()
            && ch == c
        {
            self.input_vec_index += 1;
            return Some((r, i));
        }

        None
    }

    /// Called if a closing brace was not found.
    fn missing_closing_brace(&mut self, arg: &Argument<'_>) {
        let (range, description) = if let Some((r, _, c)) = self.peek() {
            (r.start..r.start, format!("expected `}}`, found `{}`", c.escape_debug()))
        } else {
            (
                // point at closing `"`
                self.end_of_snippet..self.end_of_snippet,
                "expected `}` but string was terminated".to_owned(),
            )
        };

        let (note, secondary_label) = if arg.format.fill == Some('}') {
            (
                Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
                arg.format.fill_span.clone().map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
            )
        } else {
            (
                Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
                self.last_open_brace
                    .clone()
                    .map(|sp| ("because of this opening brace".to_owned(), sp)),
            )
        };

        self.errors.push(ParseError {
            description,
            note,
            label: "expected `}`".to_owned(),
            span: range.start..range.start,
            secondary_label,
            suggestion: Suggestion::None,
        });

        if let (Some((_, _, c)), Some((_, _, nc))) = (self.peek(), self.peek_ahead()) {
            match (c, nc) {
                ('?', '}') => self.missing_colon_before_debug_formatter(),
                ('?', _) => self.suggest_format_debug(),
                ('<' | '^' | '>', _) => self.suggest_format_align(c),
                (',', _) => self.suggest_unsupported_python_numeric_grouping(),
                ('=', '}') => self.suggest_rust_debug_printing_macro(),
                ('+', _) => self.suggest_format_missing_colon_for_sign(),
                _ => self.suggest_positional_arg_instead_of_captured_arg(arg),
            }
        }
    }

    /// Consumes all whitespace characters until the first non-whitespace character
    fn ws(&mut self) {
        let rest = &self.input_vec[self.input_vec_index..];
        let step = rest.iter().position(|&(_, _, c)| !c.is_whitespace()).unwrap_or(rest.len());
        self.input_vec_index += step;
    }

    /// Parses all of a string which is to be considered a "raw literal" in a
    /// format string. This is everything outside of the braces.
    fn string(&mut self, start: usize) -> &'input str {
        while let Some((r, i, c)) = self.peek() {
            match c {
                '{' | '}' => {
                    return &self.input[start..i];
                }
                '\n' if self.is_source_literal => {
                    self.input_vec_index += 1;
                    self.line_spans.push(self.cur_line_start..r.start);
                    self.cur_line_start = r.end;
                }
                _ => {
                    self.input_vec_index += 1;
                    if self.is_source_literal && r.start == self.cur_line_start && c.is_whitespace()
                    {
                        self.cur_line_start = r.end;
                    }
                }
            }
        }
        &self.input[start..]
    }

    /// Parses an `Argument` structure, or what's contained within braces inside the format string.
    fn argument(&mut self) -> Argument<'input> {
        let start_idx = self.input_vec_index;

        let position = self.position();
        self.ws();

        let end_idx = self.input_vec_index;

        let format = match self.mode {
            ParseMode::Format => self.format(),
            ParseMode::InlineAsm => self.inline_asm(),
            ParseMode::Diagnostic => self.diagnostic(),
        };

        // Resolve position after parsing format spec.
        let position = position.unwrap_or_else(|| {
            let i = self.curarg;
            self.curarg += 1;
            ArgumentImplicitlyIs(i)
        });

        let position_span =
            self.input_vec_index2range(start_idx).start..self.input_vec_index2range(end_idx).start;
        Argument { position, position_span, format }
    }

    /// Parses a positional argument for a format. This could either be an
    /// integer index of an argument, a named argument, or a blank string.
    /// Returns `Some(parsed_position)` if the position is not implicitly
    /// consuming a macro argument, `None` if it's the case.
    fn position(&mut self) -> Option<Position<'input>> {
        if let Some(i) = self.integer() {
            Some(ArgumentIs(i.into()))
        } else {
            match self.peek() {
                Some((range, _, c)) if rustc_lexer::is_id_start(c) => {
                    let start = range.start;
                    let word = self.word();

                    // Recover from `r#ident` in format strings.
                    if word == "r"
                        && let Some((r, _, '#')) = self.peek()
                        && self.peek_ahead().is_some_and(|(_, _, c)| rustc_lexer::is_id_start(c))
                    {
                        self.input_vec_index += 1;
                        let prefix_end = r.end;
                        let word = self.word();
                        let prefix_span = start..prefix_end;
                        let full_span =
                            start..self.input_vec_index2range(self.input_vec_index).start;
                        self.errors.insert(0, ParseError {
                                    description: "raw identifiers are not supported".to_owned(),
                                    note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
                                    label: "raw identifier used here".to_owned(),
                                    span: full_span,
                                    secondary_label: None,
                                    suggestion: Suggestion::RemoveRawIdent(prefix_span),
                                });
                        return Some(ArgumentNamed(word));
                    }

                    Some(ArgumentNamed(word))
                }
                // This is an `ArgumentNext`.
                // Record the fact and do the resolution after parsing the
                // format spec, to make things like `{:.*}` work.
                _ => None,
            }
        }
    }

    fn input_vec_index2pos(&self, index: usize) -> usize {
        if let Some((_, pos, _)) = self.input_vec.get(index) { *pos } else { self.input.len() }
    }

    fn input_vec_index2range(&self, index: usize) -> Range<usize> {
        if let Some((r, _, _)) = self.input_vec.get(index) {
            r.clone()
        } else {
            self.end_of_snippet..self.end_of_snippet
        }
    }

    /// Parses a format specifier at the current position, returning all of the
    /// relevant information in the `FormatSpec` struct.
    fn format(&mut self) -> FormatSpec<'input> {
        let mut spec = FormatSpec::default();

        if !self.consume(':') {
            return spec;
        }

        // fill character
        if let (Some((r, _, c)), Some((_, _, '>' | '<' | '^'))) = (self.peek(), self.peek_ahead()) {
            self.input_vec_index += 1;
            spec.fill = Some(c);
            spec.fill_span = Some(r);
        }
        // Alignment
        if self.consume('<') {
            spec.align = AlignLeft;
        } else if self.consume('>') {
            spec.align = AlignRight;
        } else if self.consume('^') {
            spec.align = AlignCenter;
        }
        // Sign flags
        if self.consume('+') {
            spec.sign = Some(Sign::Plus);
        } else if self.consume('-') {
            spec.sign = Some(Sign::Minus);
        }
        // Alternate marker
        if self.consume('#') {
            spec.alternate = true;
        }
        // Width and precision
        let mut havewidth = false;

        if let Some((range, _)) = self.consume_pos('0') {
            // small ambiguity with '0$' as a format string. In theory this is a
            // '0' flag and then an ill-formatted format string with just a '$'
            // and no count, but this is better if we instead interpret this as
            // no '0' flag and '0$' as the width instead.
            if let Some((r, _)) = self.consume_pos('$') {
                spec.width = CountIsParam(0);
                spec.width_span = Some(range.start..r.end);
                havewidth = true;
            } else {
                spec.zero_pad = true;
            }
        }

        if !havewidth {
            let start_idx = self.input_vec_index;
            spec.width = self.count();
            if spec.width != CountImplied {
                let end = self.input_vec_index2range(self.input_vec_index).start;
                spec.width_span = Some(self.input_vec_index2range(start_idx).start..end);
            }
        }

        if let Some((range, _)) = self.consume_pos('.') {
            if self.consume('*') {
                // Resolve `CountIsNextParam`.
                // We can do this immediately as `position` is resolved later.
                let i = self.curarg;
                self.curarg += 1;
                spec.precision = CountIsStar(i);
            } else {
                spec.precision = self.count();
            }
            spec.precision_span =
                Some(range.start..self.input_vec_index2range(self.input_vec_index).start);
        }

        let start_idx = self.input_vec_index;
        // Optional radix followed by the actual format specifier
        if self.consume('x') {
            if self.consume('?') {
                spec.debug_hex = Some(DebugHex::Lower);
                spec.ty = "?";
            } else {
                spec.ty = "x";
            }
        } else if self.consume('X') {
            if self.consume('?') {
                spec.debug_hex = Some(DebugHex::Upper);
                spec.ty = "?";
            } else {
                spec.ty = "X";
            }
        } else if let Some((range, _)) = self.consume_pos('?') {
            spec.ty = "?";
            if let Some((r, _, c @ ('#' | 'x' | 'X'))) = self.peek() {
                self.errors.insert(
                    0,
                    ParseError {
                        description: format!("expected `}}`, found `{c}`"),
                        note: None,
                        label: "expected `'}'`".into(),
                        span: r.clone(),
                        secondary_label: None,
                        suggestion: Suggestion::ReorderFormatParameter(
                            range.start..r.end,
                            format!("{c}?"),
                        ),
                    },
                );
            }
        } else {
            spec.ty = self.word();
            if !spec.ty.is_empty() {
                let start = self.input_vec_index2range(start_idx).start;
                let end = self.input_vec_index2range(self.input_vec_index).start;
                spec.ty_span = Some(start..end);
            }
        }
        spec
    }

    /// Parses an inline assembly template modifier at the current position, returning the modifier
    /// in the `ty` field of the `FormatSpec` struct.
    fn inline_asm(&mut self) -> FormatSpec<'input> {
        let mut spec = FormatSpec::default();

        if !self.consume(':') {
            return spec;
        }

        let start_idx = self.input_vec_index;
        spec.ty = self.word();
        if !spec.ty.is_empty() {
            let start = self.input_vec_index2range(start_idx).start;
            let end = self.input_vec_index2range(self.input_vec_index).start;
            spec.ty_span = Some(start..end);
        }

        spec
    }

    /// Always returns an empty `FormatSpec`, except for the `ty` and `ty_span` fields.
    fn diagnostic(&mut self) -> FormatSpec<'input> {
        let mut spec = FormatSpec::default();

        let Some((Range { start, .. }, _)) = self.consume_pos(':') else {
            return spec;
        };

        spec.ty = self.string(self.input_vec_index);
        spec.ty_span = {
            let end = self.input_vec_index2range(self.input_vec_index).start;
            Some(start..end)
        };
        spec
    }

    /// Parses a `Count` parameter at the current position. This does not check
    /// for 'CountIsNextParam' because that is only used in precision, not
    /// width.
    fn count(&mut self) -> Count<'input> {
        if let Some(i) = self.integer() {
            if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) }
        } else {
            let start_idx = self.input_vec_index;
            let word = self.word();
            if word.is_empty() {
                CountImplied
            } else if let Some((r, _)) = self.consume_pos('$') {
                CountIsName(word, self.input_vec_index2range(start_idx).start..r.start)
            } else {
                self.input_vec_index = start_idx;
                CountImplied
            }
        }
    }

    /// Parses a word starting at the current position. A word is the same as a
    /// Rust identifier or keyword, except that it can't be a bare `_` character.
    fn word(&mut self) -> &'input str {
        let index = self.input_vec_index;
        match self.peek() {
            Some((ref r, i, c)) if rustc_lexer::is_id_start(c) => {
                self.input_vec_index += 1;
                (r.start, i)
            }
            _ => {
                return "";
            }
        };
        let (err_end, end): (usize, usize) = loop {
            if let Some((ref r, i, c)) = self.peek() {
                if rustc_lexer::is_id_continue(c) {
                    self.input_vec_index += 1;
                } else {
                    break (r.start, i);
                }
            } else {
                break (self.end_of_snippet, self.input.len());
            }
        };

        let word = &self.input[self.input_vec_index2pos(index)..end];
        if word == "_" {
            self.errors.push(ParseError {
                description: "invalid argument name `_`".into(),
                note: Some("argument name cannot be a single underscore".into()),
                label: "invalid argument name".into(),
                span: self.input_vec_index2range(index).start..err_end,
                secondary_label: None,
                suggestion: Suggestion::None,
            });
        }
        word
    }

    fn integer(&mut self) -> Option<u16> {
        let mut cur: u16 = 0;
        let mut found = false;
        let mut overflow = false;
        let start_index = self.input_vec_index;
        while let Some((_, _, c)) = self.peek() {
            if let Some(i) = c.to_digit(10) {
                self.input_vec_index += 1;
                let (tmp, mul_overflow) = cur.overflowing_mul(10);
                let (tmp, add_overflow) = tmp.overflowing_add(i as u16);
                if mul_overflow || add_overflow {
                    overflow = true;
                }
                cur = tmp;
                found = true;
            } else {
                break;
            }
        }

        if overflow {
            let overflowed_int = &self.input[self.input_vec_index2pos(start_index)
                ..self.input_vec_index2pos(self.input_vec_index)];
            self.errors.push(ParseError {
                description: format!(
                    "integer `{}` does not fit into the type `u16` whose range is `0..={}`",
                    overflowed_int,
                    u16::MAX
                ),
                note: None,
                label: "integer out of range for `u16`".into(),
                span: self.input_vec_index2range(start_index).start
                    ..self.input_vec_index2range(self.input_vec_index).end,
                secondary_label: None,
                suggestion: Suggestion::None,
            });
        }

        found.then_some(cur)
    }

    fn suggest_format_debug(&mut self) {
        if let (Some((range, _)), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
            let word = self.word();
            self.errors.insert(
                0,
                ParseError {
                    description: "expected format parameter to occur after `:`".to_owned(),
                    note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
                    label: "expected `?` to occur after `:`".to_owned(),
                    span: range,
                    secondary_label: None,
                    suggestion: Suggestion::None,
                },
            );
        }
    }

    fn missing_colon_before_debug_formatter(&mut self) {
        if let Some((range, _)) = self.consume_pos('?') {
            let span = range.clone();
            self.errors.insert(
                0,
                ParseError {
                    description: "expected `}`, found `?`".to_owned(),
                    note: Some(format!("to print `{{`, you can escape it using `{{{{`",)),
                    label: "expected `:` before `?` to format with `Debug`".to_owned(),
                    span: range,
                    secondary_label: None,
                    suggestion: Suggestion::AddMissingColon(span),
                },
            );
        }
    }

    fn suggest_rust_debug_printing_macro(&mut self) {
        if let Some((range, _)) = self.consume_pos('=') {
            self.errors.insert(
                0,
                ParseError {
                    description:
                        "python's f-string debug `=` is not supported in rust, use `dbg(x)` instead"
                            .to_owned(),
                    note: Some(format!("to print `{{`, you can escape it using `{{{{`",)),
                    label: "expected `}`".to_owned(),
                    span: range,
                    secondary_label: self
                        .last_open_brace
                        .clone()
                        .map(|sp| ("because of this opening brace".to_owned(), sp)),
                    suggestion: Suggestion::UseRustDebugPrintingMacro,
                },
            );
        }
    }

    fn suggest_format_align(&mut self, alignment: char) {
        if let Some((range, _)) = self.consume_pos(alignment) {
            self.errors.insert(
                0,
                ParseError {
                    description:
                        "expected alignment specifier after `:` in format string; example: `{:>?}`"
                            .to_owned(),
                    note: None,
                    label: format!("expected `{}` to occur after `:`", alignment),
                    span: range,
                    secondary_label: None,
                    suggestion: Suggestion::None,
                },
            );
        }
    }

    fn suggest_format_missing_colon_for_sign(&mut self) {
        if let Some((range, _)) = self.consume_pos('+') {
            self.errors.insert(
                0,
                ParseError {
                    description: "the `+` sign flag must appear after `:` in a format string"
                        .to_owned(),
                    note: Some("`+` comes after `:`, try `{:+}` instead of `{+}`".to_owned()),
                    label: "expected `:` before `+` sign flag".to_owned(),
                    span: range,
                    secondary_label: None,
                    suggestion: Suggestion::None,
                },
            );
        }
    }

    fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: &Argument<'_>) {
        // If the argument is not an identifier, it is not a field access.
        if !arg.is_identifier() {
            return;
        }

        if let Some((_range, _pos)) = self.consume_pos('.') {
            let field = self.argument();
            // We can only parse simple `foo.bar` field access or `foo.0` tuple index access, any
            // deeper nesting, or another type of expression, like method calls, are not supported
            if !self.consume('}') {
                return;
            }
            if let ArgumentNamed(_) = arg.position {
                match field.position {
                    ArgumentNamed(_) => {
                        self.errors.insert(
                            0,
                            ParseError {
                                description: "field access isn't supported".to_string(),
                                note: Some(
                                    "consider moving this expression to a local variable and then \
                                     using the local here instead"
                                        .to_owned(),
                                ),
                                label: "not supported".to_string(),
                                span: arg.position_span.start..field.position_span.end,
                                secondary_label: None,
                                suggestion: Suggestion::UsePositional,
                            },
                        );
                    }
                    ArgumentIs(_) => {
                        self.errors.insert(
                            0,
                            ParseError {
                                description: "tuple index access isn't supported".to_string(),
                                note: Some(
                                    "consider moving this expression to a local variable and then \
                                     using the local here instead"
                                        .to_owned(),
                                ),
                                label: "not supported".to_string(),
                                span: arg.position_span.start..field.position_span.end,
                                secondary_label: None,
                                suggestion: Suggestion::UsePositional,
                            },
                        );
                    }
                    _ => {}
                };
            }
        }
    }

    fn suggest_unsupported_python_numeric_grouping(&mut self) {
        if let Some((range, _)) = self.consume_pos(',') {
            self.errors.insert(
                0,
                ParseError {
                    description:
                        "python's numeric grouping `,` is not supported in rust format strings"
                            .to_owned(),
                    note: Some(format!("to print `{{`, you can escape it using `{{{{`",)),
                    label: "expected `}`".to_owned(),
                    span: range,
                    secondary_label: self
                        .last_open_brace
                        .clone()
                        .map(|sp| ("because of this opening brace".to_owned(), sp)),
                    suggestion: Suggestion::None,
                },
            );
        }
    }
}

// Assert a reasonable size for `Piece`
#[cfg(all(test, target_pointer_width = "64"))]
rustc_index::static_assert_size!(Piece<'_>, 16);

#[cfg(test)]
mod tests;
