rust/compiler/rustc_parse_format/src/lib.rs
2025-05-06 10:00:22 +00:00

954 lines
36 KiB
Rust

//! 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(
html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/",
html_playground_url = "https://play.rust-lang.org/",
test(attr(deny(warnings)))
)]
// tidy-alphabetical-end
use std::ops::Range;
pub use Alignment::*;
pub use Count::*;
pub use Position::*;
use rustc_literal_escaper::{Mode, unescape_unicode};
/// 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 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<'a> {
/// A literal string which should directly be emitted
Lit(&'a str),
/// This describes that formatting should process the next argument (as
/// specified inside) for emission.
NextArgument(Box<Argument<'a>>),
}
/// Representation of an argument specification.
#[derive(Clone, Debug, PartialEq)]
pub struct Argument<'a> {
/// Where to find this argument
pub position: Position<'a>,
/// 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<'a>,
}
impl<'a> Argument<'a> {
pub fn is_identifier(&self) -> bool {
matches!(self.position, Position::ArgumentNamed(_))
&& matches!(
self.format,
FormatSpec {
fill: None,
fill_span: None,
align: AlignUnknown,
sign: None,
alternate: false,
zero_pad: false,
debug_hex: None,
precision: CountImplied,
precision_span: None,
width: CountImplied,
width_span: None,
ty: "",
ty_span: None,
},
)
}
}
/// Specification for the formatting of an argument in the format string.
#[derive(Clone, Debug, PartialEq)]
pub struct FormatSpec<'a> {
/// 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<'a>,
/// 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<'a>,
/// 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: &'a 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<'a> {
/// 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(&'a 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)]
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.
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)]
pub enum Count<'a> {
/// The count is specified explicitly.
CountIs(u16),
/// The count is specified by the argument with the given name.
CountIsName(&'a 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.
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),
}
/// 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<'a> {
mode: ParseMode,
/// Input to be parsed
input: &'a 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<'a> Iterator for Parser<'a> {
type Item = Piece<'a>;
fn next(&mut self) -> Option<Piece<'a>> {
if let Some(&(Range { start, end }, idx, ch)) = self.input_vec.get(self.input_vec_index) {
match ch {
'{' => {
self.input_vec_index += 1;
if let Some(&(_, i, '{')) = self.input_vec.get(self.input_vec_index) {
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();
if let Some(close_brace_range) = self.consume_closing_brace(&arg) {
if self.is_source_literal {
self.arg_places.push(start..close_brace_range.end);
}
} else if let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) {
match c {
'?' => self.suggest_format_debug(),
'<' | '^' | '>' => self.suggest_format_align(c),
_ => {
self.suggest_positional_arg_instead_of_captured_arg(arg.clone())
}
}
}
Some(Piece::NextArgument(Box::new(arg)))
}
}
'}' => {
self.input_vec_index += 1;
if let Some(&(_, i, '}')) = self.input_vec.get(self.input_vec_index) {
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<'a> Parser<'a> {
/// 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: &'a 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, which starts with 'r', a number of hashes, and a quote
// and ends with a quote and the same number of hashes
(true, snippet.len() - nr_hashes - 1, 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 without_quotes = &snippet[1..snippet.len() - 1];
let (mut ok, mut vec) = (true, vec![]);
let mut chars = input.chars();
unescape_unicode(without_quotes, Mode::Str, &mut |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![],
}
}
/// 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.input_vec.get(self.input_vec_index) {
if ch == *c {
self.input_vec_index += 1;
return Some((r.clone(), *i));
}
}
None
}
/// Forces consumption of the specified character. If the character is not
/// found, an error is emitted.
fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<Range<usize>> {
self.ws();
let (range, description) = if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index)
{
if *c == '}' {
self.input_vec_index += 1;
return Some(r.clone());
}
// or r.clone()?
(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,
});
None
}
/// 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) -> &'a str {
while let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) {
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<'a> {
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(),
};
// 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<'a>> {
if let Some(i) = self.integer() {
Some(ArgumentIs(i.into()))
} else {
match self.input_vec.get(self.input_vec_index) {
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.
// FIXME: use a let chain
if word == "r" {
if let Some((r, _, '#')) = self.input_vec.get(self.input_vec_index) {
if self
.input_vec
.get(self.input_vec_index + 1)
.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<'a> {
let mut spec = FormatSpec {
fill: None,
fill_span: None,
align: AlignUnknown,
sign: None,
alternate: false,
zero_pad: false,
debug_hex: None,
precision: CountImplied,
precision_span: None,
width: CountImplied,
width_span: None,
ty: &self.input[..0],
ty_span: None,
};
if !self.consume(':') {
return spec;
}
// fill character
if let Some(&(ref r, _, c)) = self.input_vec.get(self.input_vec_index) {
if let Some((_, _, '>' | '<' | '^')) = self.input_vec.get(self.input_vec_index + 1) {
self.input_vec_index += 1;
spec.fill = Some(c);
spec.fill_span = Some(r.clone());
}
}
// 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)) = self.input_vec.get(self.input_vec_index) {
match c {
'#' | 'x' | 'X' => 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<'a> {
let mut spec = FormatSpec {
fill: None,
fill_span: None,
align: AlignUnknown,
sign: None,
alternate: false,
zero_pad: false,
debug_hex: None,
precision: CountImplied,
precision_span: None,
width: CountImplied,
width_span: None,
ty: &self.input[..0],
ty_span: None,
};
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
}
/// 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<'a> {
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, except that it can't start with `_` character.
fn word(&mut self) -> &'a str {
let index = self.input_vec_index;
match self.input_vec.get(self.input_vec_index) {
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.input_vec.get(self.input_vec_index) {
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.input_vec.get(self.input_vec_index) {
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 suggest_format_align(&mut self, alignment: char) {
if let Some((range, _)) = self.consume_pos(alignment) {
self.errors.insert(
0,
ParseError {
description: "expected format parameter to occur after `:`".to_owned(),
note: None,
label: format!("expected `{}` to occur after `:`", alignment),
span: range,
secondary_label: None,
suggestion: Suggestion::None,
},
);
}
}
fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) {
// 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: None,
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: None,
label: "not supported".to_string(),
span: arg.position_span.start..field.position_span.end,
secondary_label: None,
suggestion: Suggestion::UsePositional,
},
);
}
_ => {}
};
}
}
}
}
// Assert a reasonable size for `Piece`
#[cfg(all(test, target_pointer_width = "64"))]
rustc_index::static_assert_size!(Piece<'_>, 16);
#[cfg(test)]
mod tests;