1#![deny(unstable_features)]
11#![doc(test(attr(deny(warnings), allow(internal_features))))]
12use std::ops::Range;
15
16pub use Alignment::*;
17pub use Count::*;
18pub use Position::*;
19
20#[derive(#[automatically_derived]
impl ::core::marker::Copy for ParseMode { }Copy, #[automatically_derived]
impl ::core::clone::Clone for ParseMode {
#[inline]
fn clone(&self) -> ParseMode { *self }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for ParseMode {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
ParseMode::Format => "Format",
ParseMode::InlineAsm => "InlineAsm",
ParseMode::Diagnostic => "Diagnostic",
})
}
}Debug, #[automatically_derived]
impl ::core::cmp::Eq for ParseMode {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) {}
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for ParseMode {
#[inline]
fn eq(&self, other: &ParseMode) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq)]
22pub enum ParseMode {
23 Format,
25 InlineAsm,
27 Diagnostic,
32}
33
34#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for Piece<'input> {
#[inline]
fn clone(&self) -> Piece<'input> {
match self {
Piece::Lit(__self_0) =>
Piece::Lit(::core::clone::Clone::clone(__self_0)),
Piece::NextArgument(__self_0) =>
Piece::NextArgument(::core::clone::Clone::clone(__self_0)),
}
}
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for Piece<'input> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
Piece::Lit(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Lit",
&__self_0),
Piece::NextArgument(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"NextArgument", &__self_0),
}
}
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for Piece<'input> {
#[inline]
fn eq(&self, other: &Piece<'input>) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(Piece::Lit(__self_0), Piece::Lit(__arg1_0)) =>
__self_0 == __arg1_0,
(Piece::NextArgument(__self_0), Piece::NextArgument(__arg1_0))
=> __self_0 == __arg1_0,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq)]
37pub enum Piece<'input> {
38 Lit(&'input str),
40 NextArgument(Box<Argument<'input>>),
43}
44
45#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for Argument<'input> {
#[inline]
fn clone(&self) -> Argument<'input> {
Argument {
position: ::core::clone::Clone::clone(&self.position),
position_span: ::core::clone::Clone::clone(&self.position_span),
format: ::core::clone::Clone::clone(&self.format),
}
}
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for Argument<'input> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f, "Argument",
"position", &self.position, "position_span", &self.position_span,
"format", &&self.format)
}
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for Argument<'input> {
#[inline]
fn eq(&self, other: &Argument<'input>) -> bool {
self.position == other.position &&
self.position_span == other.position_span &&
self.format == other.format
}
}PartialEq)]
47pub struct Argument<'input> {
48 pub position: Position<'input>,
50 pub position_span: Range<usize>,
53 pub format: FormatSpec<'input>,
55}
56
57impl<'input> Argument<'input> {
58 pub fn is_identifier(&self) -> bool {
59 #[allow(non_exhaustive_omitted_patterns)] match self.position {
Position::ArgumentNamed(_) => true,
_ => false,
}matches!(self.position, Position::ArgumentNamed(_)) && self.format == FormatSpec::default()
60 }
61}
62
63#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for FormatSpec<'input> {
#[inline]
fn clone(&self) -> FormatSpec<'input> {
FormatSpec {
fill: ::core::clone::Clone::clone(&self.fill),
fill_span: ::core::clone::Clone::clone(&self.fill_span),
align: ::core::clone::Clone::clone(&self.align),
sign: ::core::clone::Clone::clone(&self.sign),
alternate: ::core::clone::Clone::clone(&self.alternate),
zero_pad: ::core::clone::Clone::clone(&self.zero_pad),
debug_hex: ::core::clone::Clone::clone(&self.debug_hex),
precision: ::core::clone::Clone::clone(&self.precision),
precision_span: ::core::clone::Clone::clone(&self.precision_span),
width: ::core::clone::Clone::clone(&self.width),
width_span: ::core::clone::Clone::clone(&self.width_span),
ty: ::core::clone::Clone::clone(&self.ty),
ty_span: ::core::clone::Clone::clone(&self.ty_span),
}
}
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for FormatSpec<'input> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["fill", "fill_span", "align", "sign", "alternate", "zero_pad",
"debug_hex", "precision", "precision_span", "width",
"width_span", "ty", "ty_span"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.fill, &self.fill_span, &self.align, &self.sign,
&self.alternate, &self.zero_pad, &self.debug_hex,
&self.precision, &self.precision_span, &self.width,
&self.width_span, &self.ty, &&self.ty_span];
::core::fmt::Formatter::debug_struct_fields_finish(f, "FormatSpec",
names, values)
}
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for FormatSpec<'input> {
#[inline]
fn eq(&self, other: &FormatSpec<'input>) -> bool {
self.alternate == other.alternate && self.zero_pad == other.zero_pad
&& self.fill == other.fill &&
self.fill_span == other.fill_span &&
self.align == other.align && self.sign == other.sign &&
self.debug_hex == other.debug_hex &&
self.precision == other.precision &&
self.precision_span == other.precision_span &&
self.width == other.width &&
self.width_span == other.width_span && self.ty == other.ty
&& self.ty_span == other.ty_span
}
}PartialEq, #[automatically_derived]
impl<'input> ::core::default::Default for FormatSpec<'input> {
#[inline]
fn default() -> FormatSpec<'input> {
FormatSpec {
fill: ::core::default::Default::default(),
fill_span: ::core::default::Default::default(),
align: ::core::default::Default::default(),
sign: ::core::default::Default::default(),
alternate: ::core::default::Default::default(),
zero_pad: ::core::default::Default::default(),
debug_hex: ::core::default::Default::default(),
precision: ::core::default::Default::default(),
precision_span: ::core::default::Default::default(),
width: ::core::default::Default::default(),
width_span: ::core::default::Default::default(),
ty: ::core::default::Default::default(),
ty_span: ::core::default::Default::default(),
}
}
}Default)]
65pub struct FormatSpec<'input> {
66 pub fill: Option<char>,
68 pub fill_span: Option<Range<usize>>,
70 pub align: Alignment,
72 pub sign: Option<Sign>,
74 pub alternate: bool,
76 pub zero_pad: bool,
78 pub debug_hex: Option<DebugHex>,
80 pub precision: Count<'input>,
82 pub precision_span: Option<Range<usize>>,
84 pub width: Count<'input>,
86 pub width_span: Option<Range<usize>>,
88 pub ty: &'input str,
92 pub ty_span: Option<Range<usize>>,
94}
95
96#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for Position<'input> {
#[inline]
fn clone(&self) -> Position<'input> {
match self {
Position::ArgumentImplicitlyIs(__self_0) =>
Position::ArgumentImplicitlyIs(::core::clone::Clone::clone(__self_0)),
Position::ArgumentIs(__self_0) =>
Position::ArgumentIs(::core::clone::Clone::clone(__self_0)),
Position::ArgumentNamed(__self_0) =>
Position::ArgumentNamed(::core::clone::Clone::clone(__self_0)),
}
}
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for Position<'input> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
Position::ArgumentImplicitlyIs(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"ArgumentImplicitlyIs", &__self_0),
Position::ArgumentIs(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"ArgumentIs", &__self_0),
Position::ArgumentNamed(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"ArgumentNamed", &__self_0),
}
}
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for Position<'input> {
#[inline]
fn eq(&self, other: &Position<'input>) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(Position::ArgumentImplicitlyIs(__self_0),
Position::ArgumentImplicitlyIs(__arg1_0)) =>
__self_0 == __arg1_0,
(Position::ArgumentIs(__self_0),
Position::ArgumentIs(__arg1_0)) => __self_0 == __arg1_0,
(Position::ArgumentNamed(__self_0),
Position::ArgumentNamed(__arg1_0)) => __self_0 == __arg1_0,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq)]
98pub enum Position<'input> {
99 ArgumentImplicitlyIs(usize),
101 ArgumentIs(usize),
103 ArgumentNamed(&'input str),
105}
106
107impl Position<'_> {
108 pub fn index(&self) -> Option<usize> {
109 match self {
110 ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
111 _ => None,
112 }
113 }
114}
115
116#[derive(#[automatically_derived]
impl ::core::marker::Copy for Alignment { }Copy, #[automatically_derived]
impl ::core::clone::Clone for Alignment {
#[inline]
fn clone(&self) -> Alignment { *self }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for Alignment {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
Alignment::AlignLeft => "AlignLeft",
Alignment::AlignRight => "AlignRight",
Alignment::AlignCenter => "AlignCenter",
Alignment::AlignUnknown => "AlignUnknown",
})
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Alignment {
#[inline]
fn eq(&self, other: &Alignment) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::default::Default for Alignment {
#[inline]
fn default() -> Alignment { Self::AlignUnknown }
}Default)]
118pub enum Alignment {
119 AlignLeft,
121 AlignRight,
123 AlignCenter,
125 #[default]
127 AlignUnknown,
128}
129
130#[derive(#[automatically_derived]
impl ::core::marker::Copy for Sign { }Copy, #[automatically_derived]
impl ::core::clone::Clone for Sign {
#[inline]
fn clone(&self) -> Sign { *self }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for Sign {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self { Sign::Plus => "Plus", Sign::Minus => "Minus", })
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Sign {
#[inline]
fn eq(&self, other: &Sign) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq)]
132pub enum Sign {
133 Plus,
135 Minus,
137}
138
139#[derive(#[automatically_derived]
impl ::core::marker::Copy for DebugHex { }Copy, #[automatically_derived]
impl ::core::clone::Clone for DebugHex {
#[inline]
fn clone(&self) -> DebugHex { *self }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for DebugHex {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
DebugHex::Lower => "Lower",
DebugHex::Upper => "Upper",
})
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for DebugHex {
#[inline]
fn eq(&self, other: &DebugHex) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq)]
141pub enum DebugHex {
142 Lower,
144 Upper,
146}
147
148#[derive(#[automatically_derived]
impl<'input> ::core::clone::Clone for Count<'input> {
#[inline]
fn clone(&self) -> Count<'input> {
match self {
Count::CountIs(__self_0) =>
Count::CountIs(::core::clone::Clone::clone(__self_0)),
Count::CountIsName(__self_0, __self_1) =>
Count::CountIsName(::core::clone::Clone::clone(__self_0),
::core::clone::Clone::clone(__self_1)),
Count::CountIsParam(__self_0) =>
Count::CountIsParam(::core::clone::Clone::clone(__self_0)),
Count::CountIsStar(__self_0) =>
Count::CountIsStar(::core::clone::Clone::clone(__self_0)),
Count::CountImplied => Count::CountImplied,
}
}
}Clone, #[automatically_derived]
impl<'input> ::core::fmt::Debug for Count<'input> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
Count::CountIs(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"CountIs", &__self_0),
Count::CountIsName(__self_0, __self_1) =>
::core::fmt::Formatter::debug_tuple_field2_finish(f,
"CountIsName", __self_0, &__self_1),
Count::CountIsParam(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"CountIsParam", &__self_0),
Count::CountIsStar(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"CountIsStar", &__self_0),
Count::CountImplied =>
::core::fmt::Formatter::write_str(f, "CountImplied"),
}
}
}Debug, #[automatically_derived]
impl<'input> ::core::cmp::PartialEq for Count<'input> {
#[inline]
fn eq(&self, other: &Count<'input>) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(Count::CountIs(__self_0), Count::CountIs(__arg1_0)) =>
__self_0 == __arg1_0,
(Count::CountIsName(__self_0, __self_1),
Count::CountIsName(__arg1_0, __arg1_1)) =>
__self_0 == __arg1_0 && __self_1 == __arg1_1,
(Count::CountIsParam(__self_0), Count::CountIsParam(__arg1_0))
=> __self_0 == __arg1_0,
(Count::CountIsStar(__self_0), Count::CountIsStar(__arg1_0))
=> __self_0 == __arg1_0,
_ => true,
}
}
}PartialEq, #[automatically_derived]
impl<'input> ::core::default::Default for Count<'input> {
#[inline]
fn default() -> Count<'input> { Self::CountImplied }
}Default)]
151pub enum Count<'input> {
152 CountIs(u16),
154 CountIsName(&'input str, Range<usize>),
156 CountIsParam(usize),
158 CountIsStar(usize),
160 #[default]
162 CountImplied,
163}
164
165pub struct ParseError {
166 pub description: String,
167 pub note: Option<String>,
168 pub label: String,
169 pub span: Range<usize>,
170 pub secondary_label: Option<(String, Range<usize>)>,
171 pub suggestion: Suggestion,
172}
173
174pub enum Suggestion {
175 None,
176 UsePositional,
179 RemoveRawIdent(Range<usize>),
182 ReorderFormatParameter(Range<usize>, String),
187 AddMissingColon(Range<usize>),
190 UseRustDebugPrintingMacro,
193}
194
195pub struct Parser<'input> {
202 mode: ParseMode,
203 input: &'input str,
205 input_vec: Vec<(Range<usize>, usize, char)>,
207 input_vec_index: usize,
209 pub errors: Vec<ParseError>,
211 pub curarg: usize,
213 pub arg_places: Vec<Range<usize>>,
215 last_open_brace: Option<Range<usize>>,
217 pub is_source_literal: bool,
221 end_of_snippet: usize,
223 cur_line_start: usize,
225 pub line_spans: Vec<Range<usize>>,
228}
229
230impl<'input> Iterator for Parser<'input> {
231 type Item = Piece<'input>;
232
233 fn next(&mut self) -> Option<Piece<'input>> {
234 if let Some((Range { start, end }, idx, ch)) = self.peek() {
235 match ch {
236 '{' => {
237 self.input_vec_index += 1;
238 if let Some((_, i, '{')) = self.peek() {
239 self.input_vec_index += 1;
240 Some(Piece::Lit(self.string(i)))
243 } else {
244 self.last_open_brace = Some(start..end);
246 let arg = self.argument();
247 self.ws();
248 if let Some((close_brace_range, _)) = self.consume_pos('}') {
249 if self.is_source_literal {
250 self.arg_places.push(start..close_brace_range.end);
251 }
252 } else {
253 self.missing_closing_brace(&arg);
254 }
255
256 Some(Piece::NextArgument(Box::new(arg)))
257 }
258 }
259 '}' => {
260 self.input_vec_index += 1;
261 if let Some((_, i, '}')) = self.peek() {
262 self.input_vec_index += 1;
263 Some(Piece::Lit(self.string(i)))
266 } else {
267 self.errors.push(ParseError {
269 description: "unmatched `}` found".into(),
270 note: Some(
271 "if you intended to print `}`, you can escape it using `}}`".into(),
272 ),
273 label: "unmatched `}`".into(),
274 span: start..end,
275 secondary_label: None,
276 suggestion: Suggestion::None,
277 });
278 None
279 }
280 }
281 _ => Some(Piece::Lit(self.string(idx))),
282 }
283 } else {
284 if self.is_source_literal {
286 let span = self.cur_line_start..self.end_of_snippet;
287 if self.line_spans.last() != Some(&span) {
288 self.line_spans.push(span);
289 }
290 }
291 None
292 }
293 }
294}
295
296impl<'input> Parser<'input> {
297 pub fn new(
303 input: &'input str,
304 style: Option<usize>,
305 snippet: Option<String>,
306 appended_newline: bool,
307 mode: ParseMode,
308 ) -> Self {
309 let quote_offset = style.map_or(1, |nr_hashes| nr_hashes + 2);
310
311 let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
312 if let Some(nr_hashes) = style {
313 (true, snippet.len() - nr_hashes - 1, ::alloc::vec::Vec::new()vec![])
316 } else {
317 if snippet.starts_with('"') {
319 let without_quotes = &snippet[1..snippet.len() - 1];
322 let (mut ok, mut vec) = (true, ::alloc::vec::Vec::new()vec![]);
323 let mut chars = input.chars();
324 rustc_literal_escaper::unescape_str(without_quotes, |range, res| match res {
325 Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
326 vec.push((range, ch));
327 }
328 _ => {
329 ok = false;
330 vec = ::alloc::vec::Vec::new()vec![];
331 }
332 });
333 let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
334 if ok {
335 if appended_newline {
336 if chars.as_str() == "\n" {
337 vec.push((end..end + 1, '\n'));
338 (true, 1 + end, vec)
339 } else {
340 (false, snippet.len(), ::alloc::vec::Vec::new()vec![])
341 }
342 } else if chars.as_str() == "" {
343 (true, 1 + end, vec)
344 } else {
345 (false, snippet.len(), ::alloc::vec::Vec::new()vec![])
346 }
347 } else {
348 (false, snippet.len(), ::alloc::vec::Vec::new()vec![])
349 }
350 } else {
351 (false, snippet.len(), ::alloc::vec::Vec::new()vec![])
353 }
354 }
355 } else {
356 (false, input.len() - if appended_newline { 1 } else { 0 }, ::alloc::vec::Vec::new()vec![])
358 };
359
360 let input_vec: Vec<(Range<usize>, usize, char)> = if pre_input_vec.is_empty() {
361 input
364 .char_indices()
365 .map(|(idx, c)| {
366 let i = idx + quote_offset;
367 (i..i + c.len_utf8(), idx, c)
368 })
369 .collect()
370 } else {
371 input
373 .char_indices()
374 .zip(pre_input_vec)
375 .map(|((i, c), (r, _))| (r.start + quote_offset..r.end + quote_offset, i, c))
376 .collect()
377 };
378
379 Parser {
380 mode,
381 input,
382 input_vec,
383 input_vec_index: 0,
384 errors: ::alloc::vec::Vec::new()vec![],
385 curarg: 0,
386 arg_places: ::alloc::vec::Vec::new()vec![],
387 last_open_brace: None,
388 is_source_literal,
389 end_of_snippet,
390 cur_line_start: quote_offset,
391 line_spans: ::alloc::vec::Vec::new()vec![],
392 }
393 }
394
395 pub fn peek(&self) -> Option<(Range<usize>, usize, char)> {
397 self.input_vec.get(self.input_vec_index).cloned()
398 }
399
400 pub fn peek_ahead(&self) -> Option<(Range<usize>, usize, char)> {
402 self.input_vec.get(self.input_vec_index + 1).cloned()
403 }
404
405 fn consume(&mut self, c: char) -> bool {
409 self.consume_pos(c).is_some()
410 }
411
412 fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> {
417 if let Some((r, i, c)) = self.peek()
418 && ch == c
419 {
420 self.input_vec_index += 1;
421 return Some((r, i));
422 }
423
424 None
425 }
426
427 fn missing_closing_brace(&mut self, arg: &Argument<'_>) {
429 let (range, description) = if let Some((r, _, c)) = self.peek() {
430 (r.start..r.start, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected `}}`, found `{0}`",
c.escape_debug()))
})format!("expected `}}`, found `{}`", c.escape_debug()))
431 } else {
432 (
433 self.end_of_snippet..self.end_of_snippet,
435 "expected `}` but string was terminated".to_owned(),
436 )
437 };
438
439 let (note, secondary_label) = if arg.format.fill == Some('}') {
440 (
441 Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
442 arg.format.fill_span.clone().map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
443 )
444 } else {
445 (
446 Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
447 self.last_open_brace
448 .clone()
449 .map(|sp| ("because of this opening brace".to_owned(), sp)),
450 )
451 };
452
453 self.errors.push(ParseError {
454 description,
455 note,
456 label: "expected `}`".to_owned(),
457 span: range.start..range.start,
458 secondary_label,
459 suggestion: Suggestion::None,
460 });
461
462 if let (Some((_, _, c)), Some((_, _, nc))) = (self.peek(), self.peek_ahead()) {
463 match (c, nc) {
464 ('?', '}') => self.missing_colon_before_debug_formatter(),
465 ('?', _) => self.suggest_format_debug(),
466 ('<' | '^' | '>', _) => self.suggest_format_align(c),
467 (',', _) => self.suggest_unsupported_python_numeric_grouping(),
468 ('=', '}') => self.suggest_rust_debug_printing_macro(),
469 _ => self.suggest_positional_arg_instead_of_captured_arg(arg),
470 }
471 }
472 }
473
474 fn ws(&mut self) {
476 let rest = &self.input_vec[self.input_vec_index..];
477 let step = rest.iter().position(|&(_, _, c)| !c.is_whitespace()).unwrap_or(rest.len());
478 self.input_vec_index += step;
479 }
480
481 fn string(&mut self, start: usize) -> &'input str {
484 while let Some((r, i, c)) = self.peek() {
485 match c {
486 '{' | '}' => {
487 return &self.input[start..i];
488 }
489 '\n' if self.is_source_literal => {
490 self.input_vec_index += 1;
491 self.line_spans.push(self.cur_line_start..r.start);
492 self.cur_line_start = r.end;
493 }
494 _ => {
495 self.input_vec_index += 1;
496 if self.is_source_literal && r.start == self.cur_line_start && c.is_whitespace()
497 {
498 self.cur_line_start = r.end;
499 }
500 }
501 }
502 }
503 &self.input[start..]
504 }
505
506 fn argument(&mut self) -> Argument<'input> {
508 let start_idx = self.input_vec_index;
509
510 let position = self.position();
511 self.ws();
512
513 let end_idx = self.input_vec_index;
514
515 let format = match self.mode {
516 ParseMode::Format => self.format(),
517 ParseMode::InlineAsm => self.inline_asm(),
518 ParseMode::Diagnostic => self.diagnostic(),
519 };
520
521 let position = position.unwrap_or_else(|| {
523 let i = self.curarg;
524 self.curarg += 1;
525 ArgumentImplicitlyIs(i)
526 });
527
528 let position_span =
529 self.input_vec_index2range(start_idx).start..self.input_vec_index2range(end_idx).start;
530 Argument { position, position_span, format }
531 }
532
533 fn position(&mut self) -> Option<Position<'input>> {
538 if let Some(i) = self.integer() {
539 Some(ArgumentIs(i.into()))
540 } else {
541 match self.peek() {
542 Some((range, _, c)) if rustc_lexer::is_id_start(c) => {
543 let start = range.start;
544 let word = self.word();
545
546 if word == "r"
548 && let Some((r, _, '#')) = self.peek()
549 && self.peek_ahead().is_some_and(|(_, _, c)| rustc_lexer::is_id_start(c))
550 {
551 self.input_vec_index += 1;
552 let prefix_end = r.end;
553 let word = self.word();
554 let prefix_span = start..prefix_end;
555 let full_span =
556 start..self.input_vec_index2range(self.input_vec_index).start;
557 self.errors.insert(0, ParseError {
558 description: "raw identifiers are not supported".to_owned(),
559 note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
560 label: "raw identifier used here".to_owned(),
561 span: full_span,
562 secondary_label: None,
563 suggestion: Suggestion::RemoveRawIdent(prefix_span),
564 });
565 return Some(ArgumentNamed(word));
566 }
567
568 Some(ArgumentNamed(word))
569 }
570 _ => None,
574 }
575 }
576 }
577
578 fn input_vec_index2pos(&self, index: usize) -> usize {
579 if let Some((_, pos, _)) = self.input_vec.get(index) { *pos } else { self.input.len() }
580 }
581
582 fn input_vec_index2range(&self, index: usize) -> Range<usize> {
583 if let Some((r, _, _)) = self.input_vec.get(index) {
584 r.clone()
585 } else {
586 self.end_of_snippet..self.end_of_snippet
587 }
588 }
589
590 fn format(&mut self) -> FormatSpec<'input> {
593 let mut spec = FormatSpec::default();
594
595 if !self.consume(':') {
596 return spec;
597 }
598
599 if let (Some((r, _, c)), Some((_, _, '>' | '<' | '^'))) = (self.peek(), self.peek_ahead()) {
601 self.input_vec_index += 1;
602 spec.fill = Some(c);
603 spec.fill_span = Some(r);
604 }
605 if self.consume('<') {
607 spec.align = AlignLeft;
608 } else if self.consume('>') {
609 spec.align = AlignRight;
610 } else if self.consume('^') {
611 spec.align = AlignCenter;
612 }
613 if self.consume('+') {
615 spec.sign = Some(Sign::Plus);
616 } else if self.consume('-') {
617 spec.sign = Some(Sign::Minus);
618 }
619 if self.consume('#') {
621 spec.alternate = true;
622 }
623 let mut havewidth = false;
625
626 if let Some((range, _)) = self.consume_pos('0') {
627 if let Some((r, _)) = self.consume_pos('$') {
632 spec.width = CountIsParam(0);
633 spec.width_span = Some(range.start..r.end);
634 havewidth = true;
635 } else {
636 spec.zero_pad = true;
637 }
638 }
639
640 if !havewidth {
641 let start_idx = self.input_vec_index;
642 spec.width = self.count();
643 if spec.width != CountImplied {
644 let end = self.input_vec_index2range(self.input_vec_index).start;
645 spec.width_span = Some(self.input_vec_index2range(start_idx).start..end);
646 }
647 }
648
649 if let Some((range, _)) = self.consume_pos('.') {
650 if self.consume('*') {
651 let i = self.curarg;
654 self.curarg += 1;
655 spec.precision = CountIsStar(i);
656 } else {
657 spec.precision = self.count();
658 }
659 spec.precision_span =
660 Some(range.start..self.input_vec_index2range(self.input_vec_index).start);
661 }
662
663 let start_idx = self.input_vec_index;
664 if self.consume('x') {
666 if self.consume('?') {
667 spec.debug_hex = Some(DebugHex::Lower);
668 spec.ty = "?";
669 } else {
670 spec.ty = "x";
671 }
672 } else if self.consume('X') {
673 if self.consume('?') {
674 spec.debug_hex = Some(DebugHex::Upper);
675 spec.ty = "?";
676 } else {
677 spec.ty = "X";
678 }
679 } else if let Some((range, _)) = self.consume_pos('?') {
680 spec.ty = "?";
681 if let Some((r, _, c @ ('#' | 'x' | 'X'))) = self.peek() {
682 self.errors.insert(
683 0,
684 ParseError {
685 description: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected `}}`, found `{0}`", c))
})format!("expected `}}`, found `{c}`"),
686 note: None,
687 label: "expected `'}'`".into(),
688 span: r.clone(),
689 secondary_label: None,
690 suggestion: Suggestion::ReorderFormatParameter(
691 range.start..r.end,
692 ::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}?", c)) })format!("{c}?"),
693 ),
694 },
695 );
696 }
697 } else {
698 spec.ty = self.word();
699 if !spec.ty.is_empty() {
700 let start = self.input_vec_index2range(start_idx).start;
701 let end = self.input_vec_index2range(self.input_vec_index).start;
702 spec.ty_span = Some(start..end);
703 }
704 }
705 spec
706 }
707
708 fn inline_asm(&mut self) -> FormatSpec<'input> {
711 let mut spec = FormatSpec::default();
712
713 if !self.consume(':') {
714 return spec;
715 }
716
717 let start_idx = self.input_vec_index;
718 spec.ty = self.word();
719 if !spec.ty.is_empty() {
720 let start = self.input_vec_index2range(start_idx).start;
721 let end = self.input_vec_index2range(self.input_vec_index).start;
722 spec.ty_span = Some(start..end);
723 }
724
725 spec
726 }
727
728 fn diagnostic(&mut self) -> FormatSpec<'input> {
730 let mut spec = FormatSpec::default();
731
732 let Some((Range { start, .. }, start_idx)) = self.consume_pos(':') else {
733 return spec;
734 };
735
736 spec.ty = self.string(start_idx);
737 spec.ty_span = {
738 let end = self.input_vec_index2range(self.input_vec_index).start;
739 Some(start..end)
740 };
741 spec
742 }
743
744 fn count(&mut self) -> Count<'input> {
748 if let Some(i) = self.integer() {
749 if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) }
750 } else {
751 let start_idx = self.input_vec_index;
752 let word = self.word();
753 if word.is_empty() {
754 CountImplied
755 } else if let Some((r, _)) = self.consume_pos('$') {
756 CountIsName(word, self.input_vec_index2range(start_idx).start..r.start)
757 } else {
758 self.input_vec_index = start_idx;
759 CountImplied
760 }
761 }
762 }
763
764 fn word(&mut self) -> &'input str {
767 let index = self.input_vec_index;
768 match self.peek() {
769 Some((ref r, i, c)) if rustc_lexer::is_id_start(c) => {
770 self.input_vec_index += 1;
771 (r.start, i)
772 }
773 _ => {
774 return "";
775 }
776 };
777 let (err_end, end): (usize, usize) = loop {
778 if let Some((ref r, i, c)) = self.peek() {
779 if rustc_lexer::is_id_continue(c) {
780 self.input_vec_index += 1;
781 } else {
782 break (r.start, i);
783 }
784 } else {
785 break (self.end_of_snippet, self.input.len());
786 }
787 };
788
789 let word = &self.input[self.input_vec_index2pos(index)..end];
790 if word == "_" {
791 self.errors.push(ParseError {
792 description: "invalid argument name `_`".into(),
793 note: Some("argument name cannot be a single underscore".into()),
794 label: "invalid argument name".into(),
795 span: self.input_vec_index2range(index).start..err_end,
796 secondary_label: None,
797 suggestion: Suggestion::None,
798 });
799 }
800 word
801 }
802
803 fn integer(&mut self) -> Option<u16> {
804 let mut cur: u16 = 0;
805 let mut found = false;
806 let mut overflow = false;
807 let start_index = self.input_vec_index;
808 while let Some((_, _, c)) = self.peek() {
809 if let Some(i) = c.to_digit(10) {
810 self.input_vec_index += 1;
811 let (tmp, mul_overflow) = cur.overflowing_mul(10);
812 let (tmp, add_overflow) = tmp.overflowing_add(i as u16);
813 if mul_overflow || add_overflow {
814 overflow = true;
815 }
816 cur = tmp;
817 found = true;
818 } else {
819 break;
820 }
821 }
822
823 if overflow {
824 let overflowed_int = &self.input[self.input_vec_index2pos(start_index)
825 ..self.input_vec_index2pos(self.input_vec_index)];
826 self.errors.push(ParseError {
827 description: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("integer `{0}` does not fit into the type `u16` whose range is `0..={1}`",
overflowed_int, u16::MAX))
})format!(
828 "integer `{}` does not fit into the type `u16` whose range is `0..={}`",
829 overflowed_int,
830 u16::MAX
831 ),
832 note: None,
833 label: "integer out of range for `u16`".into(),
834 span: self.input_vec_index2range(start_index).start
835 ..self.input_vec_index2range(self.input_vec_index).end,
836 secondary_label: None,
837 suggestion: Suggestion::None,
838 });
839 }
840
841 found.then_some(cur)
842 }
843
844 fn suggest_format_debug(&mut self) {
845 if let (Some((range, _)), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
846 let word = self.word();
847 self.errors.insert(
848 0,
849 ParseError {
850 description: "expected format parameter to occur after `:`".to_owned(),
851 note: Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`?` comes after `:`, try `{0}:{1}` instead",
word, "?"))
})format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
852 label: "expected `?` to occur after `:`".to_owned(),
853 span: range,
854 secondary_label: None,
855 suggestion: Suggestion::None,
856 },
857 );
858 }
859 }
860
861 fn missing_colon_before_debug_formatter(&mut self) {
862 if let Some((range, _)) = self.consume_pos('?') {
863 let span = range.clone();
864 self.errors.insert(
865 0,
866 ParseError {
867 description: "expected `}`, found `?`".to_owned(),
868 note: Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("to print `{{`, you can escape it using `{{{{`"))
})format!("to print `{{`, you can escape it using `{{{{`",)),
869 label: "expected `:` before `?` to format with `Debug`".to_owned(),
870 span: range,
871 secondary_label: None,
872 suggestion: Suggestion::AddMissingColon(span),
873 },
874 );
875 }
876 }
877
878 fn suggest_rust_debug_printing_macro(&mut self) {
879 if let Some((range, _)) = self.consume_pos('=') {
880 self.errors.insert(
881 0,
882 ParseError {
883 description:
884 "python's f-string debug `=` is not supported in rust, use `dbg(x)` instead"
885 .to_owned(),
886 note: Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("to print `{{`, you can escape it using `{{{{`"))
})format!("to print `{{`, you can escape it using `{{{{`",)),
887 label: "expected `}`".to_owned(),
888 span: range,
889 secondary_label: self
890 .last_open_brace
891 .clone()
892 .map(|sp| ("because of this opening brace".to_owned(), sp)),
893 suggestion: Suggestion::UseRustDebugPrintingMacro,
894 },
895 );
896 }
897 }
898
899 fn suggest_format_align(&mut self, alignment: char) {
900 if let Some((range, _)) = self.consume_pos(alignment) {
901 self.errors.insert(
902 0,
903 ParseError {
904 description:
905 "expected alignment specifier after `:` in format string; example: `{:>?}`"
906 .to_owned(),
907 note: None,
908 label: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected `{0}` to occur after `:`",
alignment))
})format!("expected `{}` to occur after `:`", alignment),
909 span: range,
910 secondary_label: None,
911 suggestion: Suggestion::None,
912 },
913 );
914 }
915 }
916
917 fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: &Argument<'_>) {
918 if !arg.is_identifier() {
920 return;
921 }
922
923 if let Some((_range, _pos)) = self.consume_pos('.') {
924 let field = self.argument();
925 if !self.consume('}') {
928 return;
929 }
930 if let ArgumentNamed(_) = arg.position {
931 match field.position {
932 ArgumentNamed(_) => {
933 self.errors.insert(
934 0,
935 ParseError {
936 description: "field access isn't supported".to_string(),
937 note: None,
938 label: "not supported".to_string(),
939 span: arg.position_span.start..field.position_span.end,
940 secondary_label: None,
941 suggestion: Suggestion::UsePositional,
942 },
943 );
944 }
945 ArgumentIs(_) => {
946 self.errors.insert(
947 0,
948 ParseError {
949 description: "tuple index access isn't supported".to_string(),
950 note: None,
951 label: "not supported".to_string(),
952 span: arg.position_span.start..field.position_span.end,
953 secondary_label: None,
954 suggestion: Suggestion::UsePositional,
955 },
956 );
957 }
958 _ => {}
959 };
960 }
961 }
962 }
963
964 fn suggest_unsupported_python_numeric_grouping(&mut self) {
965 if let Some((range, _)) = self.consume_pos(',') {
966 self.errors.insert(
967 0,
968 ParseError {
969 description:
970 "python's numeric grouping `,` is not supported in rust format strings"
971 .to_owned(),
972 note: Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("to print `{{`, you can escape it using `{{{{`"))
})format!("to print `{{`, you can escape it using `{{{{`",)),
973 label: "expected `}`".to_owned(),
974 span: range,
975 secondary_label: self
976 .last_open_brace
977 .clone()
978 .map(|sp| ("because of this opening brace".to_owned(), sp)),
979 suggestion: Suggestion::None,
980 },
981 );
982 }
983 }
984}
985
986#[cfg(all(test, target_pointer_width = "64"))]
988rustc_index::static_assert_size!(Piece<'_>, 16);
989
990#[cfg(test)]
991mod tests;