1#![deny(unstable_features)]
11#![doc(
12 html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/",
13 html_playground_url = "https://play.rust-lang.org/",
14 test(attr(deny(warnings)))
15)]
16use std::ops::Range;
19
20pub use Alignment::*;
21pub use Count::*;
22pub use Position::*;
23
24#[derive(Copy, Clone, Debug, Eq, PartialEq)]
26pub enum ParseMode {
27 Format,
29 InlineAsm,
31 Diagnostic,
36}
37
38#[derive(Clone, Debug, PartialEq)]
41pub enum Piece<'input> {
42 Lit(&'input str),
44 NextArgument(Box<Argument<'input>>),
47}
48
49#[derive(Clone, Debug, PartialEq)]
51pub struct Argument<'input> {
52 pub position: Position<'input>,
54 pub position_span: Range<usize>,
57 pub format: FormatSpec<'input>,
59}
60
61impl<'input> Argument<'input> {
62 pub fn is_identifier(&self) -> bool {
63 matches!(self.position, Position::ArgumentNamed(_)) && self.format == FormatSpec::default()
64 }
65}
66
67#[derive(Clone, Debug, PartialEq, Default)]
69pub struct FormatSpec<'input> {
70 pub fill: Option<char>,
72 pub fill_span: Option<Range<usize>>,
74 pub align: Alignment,
76 pub sign: Option<Sign>,
78 pub alternate: bool,
80 pub zero_pad: bool,
82 pub debug_hex: Option<DebugHex>,
84 pub precision: Count<'input>,
86 pub precision_span: Option<Range<usize>>,
88 pub width: Count<'input>,
90 pub width_span: Option<Range<usize>>,
92 pub ty: &'input str,
96 pub ty_span: Option<Range<usize>>,
98}
99
100#[derive(Clone, Debug, PartialEq)]
102pub enum Position<'input> {
103 ArgumentImplicitlyIs(usize),
105 ArgumentIs(usize),
107 ArgumentNamed(&'input str),
109}
110
111impl Position<'_> {
112 pub fn index(&self) -> Option<usize> {
113 match self {
114 ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
115 _ => None,
116 }
117 }
118}
119
120#[derive(Copy, Clone, Debug, PartialEq, Default)]
122pub enum Alignment {
123 AlignLeft,
125 AlignRight,
127 AlignCenter,
129 #[default]
131 AlignUnknown,
132}
133
134#[derive(Copy, Clone, Debug, PartialEq)]
136pub enum Sign {
137 Plus,
139 Minus,
141}
142
143#[derive(Copy, Clone, Debug, PartialEq)]
145pub enum DebugHex {
146 Lower,
148 Upper,
150}
151
152#[derive(Clone, Debug, PartialEq, Default)]
155pub enum Count<'input> {
156 CountIs(u16),
158 CountIsName(&'input str, Range<usize>),
160 CountIsParam(usize),
162 CountIsStar(usize),
164 #[default]
166 CountImplied,
167}
168
169pub struct ParseError {
170 pub description: String,
171 pub note: Option<String>,
172 pub label: String,
173 pub span: Range<usize>,
174 pub secondary_label: Option<(String, Range<usize>)>,
175 pub suggestion: Suggestion,
176}
177
178pub enum Suggestion {
179 None,
180 UsePositional,
183 RemoveRawIdent(Range<usize>),
186 ReorderFormatParameter(Range<usize>, String),
191}
192
193pub struct Parser<'input> {
200 mode: ParseMode,
201 input: &'input str,
203 input_vec: Vec<(Range<usize>, usize, char)>,
205 input_vec_index: usize,
207 pub errors: Vec<ParseError>,
209 pub curarg: usize,
211 pub arg_places: Vec<Range<usize>>,
213 last_open_brace: Option<Range<usize>>,
215 pub is_source_literal: bool,
219 end_of_snippet: usize,
221 cur_line_start: usize,
223 pub line_spans: Vec<Range<usize>>,
226}
227
228impl<'input> Iterator for Parser<'input> {
229 type Item = Piece<'input>;
230
231 fn next(&mut self) -> Option<Piece<'input>> {
232 if let Some((Range { start, end }, idx, ch)) = self.peek() {
233 match ch {
234 '{' => {
235 self.input_vec_index += 1;
236 if let Some((_, i, '{')) = self.peek() {
237 self.input_vec_index += 1;
238 Some(Piece::Lit(self.string(i)))
241 } else {
242 self.last_open_brace = Some(start..end);
244 let arg = self.argument();
245 self.ws();
246 if let Some((close_brace_range, _)) = self.consume_pos('}') {
247 if self.is_source_literal {
248 self.arg_places.push(start..close_brace_range.end);
249 }
250 } else {
251 self.missing_closing_brace(&arg);
252 }
253
254 Some(Piece::NextArgument(Box::new(arg)))
255 }
256 }
257 '}' => {
258 self.input_vec_index += 1;
259 if let Some((_, i, '}')) = self.peek() {
260 self.input_vec_index += 1;
261 Some(Piece::Lit(self.string(i)))
264 } else {
265 self.errors.push(ParseError {
267 description: "unmatched `}` found".into(),
268 note: Some(
269 "if you intended to print `}`, you can escape it using `}}`".into(),
270 ),
271 label: "unmatched `}`".into(),
272 span: start..end,
273 secondary_label: None,
274 suggestion: Suggestion::None,
275 });
276 None
277 }
278 }
279 _ => Some(Piece::Lit(self.string(idx))),
280 }
281 } else {
282 if self.is_source_literal {
284 let span = self.cur_line_start..self.end_of_snippet;
285 if self.line_spans.last() != Some(&span) {
286 self.line_spans.push(span);
287 }
288 }
289 None
290 }
291 }
292}
293
294impl<'input> Parser<'input> {
295 pub fn new(
301 input: &'input str,
302 style: Option<usize>,
303 snippet: Option<String>,
304 appended_newline: bool,
305 mode: ParseMode,
306 ) -> Self {
307 let quote_offset = style.map_or(1, |nr_hashes| nr_hashes + 2);
308
309 let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
310 if let Some(nr_hashes) = style {
311 (true, snippet.len() - nr_hashes - 1, vec![])
314 } else {
315 if snippet.starts_with('"') {
317 let without_quotes = &snippet[1..snippet.len() - 1];
320 let (mut ok, mut vec) = (true, vec![]);
321 let mut chars = input.chars();
322 rustc_literal_escaper::unescape_str(without_quotes, |range, res| match res {
323 Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
324 vec.push((range, ch));
325 }
326 _ => {
327 ok = false;
328 vec = vec![];
329 }
330 });
331 let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
332 if ok {
333 if appended_newline {
334 if chars.as_str() == "\n" {
335 vec.push((end..end + 1, '\n'));
336 (true, 1 + end, vec)
337 } else {
338 (false, snippet.len(), vec![])
339 }
340 } else if chars.as_str() == "" {
341 (true, 1 + end, vec)
342 } else {
343 (false, snippet.len(), vec![])
344 }
345 } else {
346 (false, snippet.len(), vec![])
347 }
348 } else {
349 (false, snippet.len(), vec![])
351 }
352 }
353 } else {
354 (false, input.len() - if appended_newline { 1 } else { 0 }, vec![])
356 };
357
358 let input_vec: Vec<(Range<usize>, usize, char)> = if pre_input_vec.is_empty() {
359 input
362 .char_indices()
363 .map(|(idx, c)| {
364 let i = idx + quote_offset;
365 (i..i + c.len_utf8(), idx, c)
366 })
367 .collect()
368 } else {
369 input
371 .char_indices()
372 .zip(pre_input_vec)
373 .map(|((i, c), (r, _))| (r.start + quote_offset..r.end + quote_offset, i, c))
374 .collect()
375 };
376
377 Parser {
378 mode,
379 input,
380 input_vec,
381 input_vec_index: 0,
382 errors: vec![],
383 curarg: 0,
384 arg_places: vec![],
385 last_open_brace: None,
386 is_source_literal,
387 end_of_snippet,
388 cur_line_start: quote_offset,
389 line_spans: vec![],
390 }
391 }
392
393 pub fn peek(&self) -> Option<(Range<usize>, usize, char)> {
395 self.input_vec.get(self.input_vec_index).cloned()
396 }
397
398 pub fn peek_ahead(&self) -> Option<(Range<usize>, usize, char)> {
400 self.input_vec.get(self.input_vec_index + 1).cloned()
401 }
402
403 fn consume(&mut self, c: char) -> bool {
407 self.consume_pos(c).is_some()
408 }
409
410 fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> {
415 if let Some((r, i, c)) = self.peek()
416 && ch == c
417 {
418 self.input_vec_index += 1;
419 return Some((r, i));
420 }
421
422 None
423 }
424
425 fn missing_closing_brace(&mut self, arg: &Argument<'_>) {
427 let (range, description) = if let Some((r, _, c)) = self.peek() {
428 (r.start..r.start, format!("expected `}}`, found `{}`", c.escape_debug()))
429 } else {
430 (
431 self.end_of_snippet..self.end_of_snippet,
433 "expected `}` but string was terminated".to_owned(),
434 )
435 };
436
437 let (note, secondary_label) = if arg.format.fill == Some('}') {
438 (
439 Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
440 arg.format.fill_span.clone().map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
441 )
442 } else {
443 (
444 Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
445 self.last_open_brace
446 .clone()
447 .map(|sp| ("because of this opening brace".to_owned(), sp)),
448 )
449 };
450
451 self.errors.push(ParseError {
452 description,
453 note,
454 label: "expected `}`".to_owned(),
455 span: range.start..range.start,
456 secondary_label,
457 suggestion: Suggestion::None,
458 });
459
460 if let Some((_, _, c)) = self.peek() {
461 match c {
462 '?' => self.suggest_format_debug(),
463 '<' | '^' | '>' => self.suggest_format_align(c),
464 _ => self.suggest_positional_arg_instead_of_captured_arg(arg),
465 }
466 }
467 }
468
469 fn ws(&mut self) {
471 let rest = &self.input_vec[self.input_vec_index..];
472 let step = rest.iter().position(|&(_, _, c)| !c.is_whitespace()).unwrap_or(rest.len());
473 self.input_vec_index += step;
474 }
475
476 fn string(&mut self, start: usize) -> &'input str {
479 while let Some((r, i, c)) = self.peek() {
480 match c {
481 '{' | '}' => {
482 return &self.input[start..i];
483 }
484 '\n' if self.is_source_literal => {
485 self.input_vec_index += 1;
486 self.line_spans.push(self.cur_line_start..r.start);
487 self.cur_line_start = r.end;
488 }
489 _ => {
490 self.input_vec_index += 1;
491 if self.is_source_literal && r.start == self.cur_line_start && c.is_whitespace()
492 {
493 self.cur_line_start = r.end;
494 }
495 }
496 }
497 }
498 &self.input[start..]
499 }
500
501 fn argument(&mut self) -> Argument<'input> {
503 let start_idx = self.input_vec_index;
504
505 let position = self.position();
506 self.ws();
507
508 let end_idx = self.input_vec_index;
509
510 let format = match self.mode {
511 ParseMode::Format => self.format(),
512 ParseMode::InlineAsm => self.inline_asm(),
513 ParseMode::Diagnostic => self.diagnostic(),
514 };
515
516 let position = position.unwrap_or_else(|| {
518 let i = self.curarg;
519 self.curarg += 1;
520 ArgumentImplicitlyIs(i)
521 });
522
523 let position_span =
524 self.input_vec_index2range(start_idx).start..self.input_vec_index2range(end_idx).start;
525 Argument { position, position_span, format }
526 }
527
528 fn position(&mut self) -> Option<Position<'input>> {
533 if let Some(i) = self.integer() {
534 Some(ArgumentIs(i.into()))
535 } else {
536 match self.peek() {
537 Some((range, _, c)) if rustc_lexer::is_id_start(c) => {
538 let start = range.start;
539 let word = self.word();
540
541 if word == "r"
543 && let Some((r, _, '#')) = self.peek()
544 && self.peek_ahead().is_some_and(|(_, _, c)| rustc_lexer::is_id_start(c))
545 {
546 self.input_vec_index += 1;
547 let prefix_end = r.end;
548 let word = self.word();
549 let prefix_span = start..prefix_end;
550 let full_span =
551 start..self.input_vec_index2range(self.input_vec_index).start;
552 self.errors.insert(0, ParseError {
553 description: "raw identifiers are not supported".to_owned(),
554 note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
555 label: "raw identifier used here".to_owned(),
556 span: full_span,
557 secondary_label: None,
558 suggestion: Suggestion::RemoveRawIdent(prefix_span),
559 });
560 return Some(ArgumentNamed(word));
561 }
562
563 Some(ArgumentNamed(word))
564 }
565 _ => None,
569 }
570 }
571 }
572
573 fn input_vec_index2pos(&self, index: usize) -> usize {
574 if let Some((_, pos, _)) = self.input_vec.get(index) { *pos } else { self.input.len() }
575 }
576
577 fn input_vec_index2range(&self, index: usize) -> Range<usize> {
578 if let Some((r, _, _)) = self.input_vec.get(index) {
579 r.clone()
580 } else {
581 self.end_of_snippet..self.end_of_snippet
582 }
583 }
584
585 fn format(&mut self) -> FormatSpec<'input> {
588 let mut spec = FormatSpec::default();
589
590 if !self.consume(':') {
591 return spec;
592 }
593
594 if let (Some((r, _, c)), Some((_, _, '>' | '<' | '^'))) = (self.peek(), self.peek_ahead()) {
596 self.input_vec_index += 1;
597 spec.fill = Some(c);
598 spec.fill_span = Some(r);
599 }
600 if self.consume('<') {
602 spec.align = AlignLeft;
603 } else if self.consume('>') {
604 spec.align = AlignRight;
605 } else if self.consume('^') {
606 spec.align = AlignCenter;
607 }
608 if self.consume('+') {
610 spec.sign = Some(Sign::Plus);
611 } else if self.consume('-') {
612 spec.sign = Some(Sign::Minus);
613 }
614 if self.consume('#') {
616 spec.alternate = true;
617 }
618 let mut havewidth = false;
620
621 if let Some((range, _)) = self.consume_pos('0') {
622 if let Some((r, _)) = self.consume_pos('$') {
627 spec.width = CountIsParam(0);
628 spec.width_span = Some(range.start..r.end);
629 havewidth = true;
630 } else {
631 spec.zero_pad = true;
632 }
633 }
634
635 if !havewidth {
636 let start_idx = self.input_vec_index;
637 spec.width = self.count();
638 if spec.width != CountImplied {
639 let end = self.input_vec_index2range(self.input_vec_index).start;
640 spec.width_span = Some(self.input_vec_index2range(start_idx).start..end);
641 }
642 }
643
644 if let Some((range, _)) = self.consume_pos('.') {
645 if self.consume('*') {
646 let i = self.curarg;
649 self.curarg += 1;
650 spec.precision = CountIsStar(i);
651 } else {
652 spec.precision = self.count();
653 }
654 spec.precision_span =
655 Some(range.start..self.input_vec_index2range(self.input_vec_index).start);
656 }
657
658 let start_idx = self.input_vec_index;
659 if self.consume('x') {
661 if self.consume('?') {
662 spec.debug_hex = Some(DebugHex::Lower);
663 spec.ty = "?";
664 } else {
665 spec.ty = "x";
666 }
667 } else if self.consume('X') {
668 if self.consume('?') {
669 spec.debug_hex = Some(DebugHex::Upper);
670 spec.ty = "?";
671 } else {
672 spec.ty = "X";
673 }
674 } else if let Some((range, _)) = self.consume_pos('?') {
675 spec.ty = "?";
676 if let Some((r, _, c @ ('#' | 'x' | 'X'))) = self.peek() {
677 self.errors.insert(
678 0,
679 ParseError {
680 description: format!("expected `}}`, found `{c}`"),
681 note: None,
682 label: "expected `'}'`".into(),
683 span: r.clone(),
684 secondary_label: None,
685 suggestion: Suggestion::ReorderFormatParameter(
686 range.start..r.end,
687 format!("{c}?"),
688 ),
689 },
690 );
691 }
692 } else {
693 spec.ty = self.word();
694 if !spec.ty.is_empty() {
695 let start = self.input_vec_index2range(start_idx).start;
696 let end = self.input_vec_index2range(self.input_vec_index).start;
697 spec.ty_span = Some(start..end);
698 }
699 }
700 spec
701 }
702
703 fn inline_asm(&mut self) -> FormatSpec<'input> {
706 let mut spec = FormatSpec::default();
707
708 if !self.consume(':') {
709 return spec;
710 }
711
712 let start_idx = self.input_vec_index;
713 spec.ty = self.word();
714 if !spec.ty.is_empty() {
715 let start = self.input_vec_index2range(start_idx).start;
716 let end = self.input_vec_index2range(self.input_vec_index).start;
717 spec.ty_span = Some(start..end);
718 }
719
720 spec
721 }
722
723 fn diagnostic(&mut self) -> FormatSpec<'input> {
725 let mut spec = FormatSpec::default();
726
727 let Some((Range { start, .. }, start_idx)) = self.consume_pos(':') else {
728 return spec;
729 };
730
731 spec.ty = self.string(start_idx);
732 spec.ty_span = {
733 let end = self.input_vec_index2range(self.input_vec_index).start;
734 Some(start..end)
735 };
736 spec
737 }
738
739 fn count(&mut self) -> Count<'input> {
743 if let Some(i) = self.integer() {
744 if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) }
745 } else {
746 let start_idx = self.input_vec_index;
747 let word = self.word();
748 if word.is_empty() {
749 CountImplied
750 } else if let Some((r, _)) = self.consume_pos('$') {
751 CountIsName(word, self.input_vec_index2range(start_idx).start..r.start)
752 } else {
753 self.input_vec_index = start_idx;
754 CountImplied
755 }
756 }
757 }
758
759 fn word(&mut self) -> &'input str {
762 let index = self.input_vec_index;
763 match self.peek() {
764 Some((ref r, i, c)) if rustc_lexer::is_id_start(c) => {
765 self.input_vec_index += 1;
766 (r.start, i)
767 }
768 _ => {
769 return "";
770 }
771 };
772 let (err_end, end): (usize, usize) = loop {
773 if let Some((ref r, i, c)) = self.peek() {
774 if rustc_lexer::is_id_continue(c) {
775 self.input_vec_index += 1;
776 } else {
777 break (r.start, i);
778 }
779 } else {
780 break (self.end_of_snippet, self.input.len());
781 }
782 };
783
784 let word = &self.input[self.input_vec_index2pos(index)..end];
785 if word == "_" {
786 self.errors.push(ParseError {
787 description: "invalid argument name `_`".into(),
788 note: Some("argument name cannot be a single underscore".into()),
789 label: "invalid argument name".into(),
790 span: self.input_vec_index2range(index).start..err_end,
791 secondary_label: None,
792 suggestion: Suggestion::None,
793 });
794 }
795 word
796 }
797
798 fn integer(&mut self) -> Option<u16> {
799 let mut cur: u16 = 0;
800 let mut found = false;
801 let mut overflow = false;
802 let start_index = self.input_vec_index;
803 while let Some((_, _, c)) = self.peek() {
804 if let Some(i) = c.to_digit(10) {
805 self.input_vec_index += 1;
806 let (tmp, mul_overflow) = cur.overflowing_mul(10);
807 let (tmp, add_overflow) = tmp.overflowing_add(i as u16);
808 if mul_overflow || add_overflow {
809 overflow = true;
810 }
811 cur = tmp;
812 found = true;
813 } else {
814 break;
815 }
816 }
817
818 if overflow {
819 let overflowed_int = &self.input[self.input_vec_index2pos(start_index)
820 ..self.input_vec_index2pos(self.input_vec_index)];
821 self.errors.push(ParseError {
822 description: format!(
823 "integer `{}` does not fit into the type `u16` whose range is `0..={}`",
824 overflowed_int,
825 u16::MAX
826 ),
827 note: None,
828 label: "integer out of range for `u16`".into(),
829 span: self.input_vec_index2range(start_index).start
830 ..self.input_vec_index2range(self.input_vec_index).end,
831 secondary_label: None,
832 suggestion: Suggestion::None,
833 });
834 }
835
836 found.then_some(cur)
837 }
838
839 fn suggest_format_debug(&mut self) {
840 if let (Some((range, _)), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
841 let word = self.word();
842 self.errors.insert(
843 0,
844 ParseError {
845 description: "expected format parameter to occur after `:`".to_owned(),
846 note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
847 label: "expected `?` to occur after `:`".to_owned(),
848 span: range,
849 secondary_label: None,
850 suggestion: Suggestion::None,
851 },
852 );
853 }
854 }
855
856 fn suggest_format_align(&mut self, alignment: char) {
857 if let Some((range, _)) = self.consume_pos(alignment) {
858 self.errors.insert(
859 0,
860 ParseError {
861 description: "expected format parameter to occur after `:`".to_owned(),
862 note: None,
863 label: format!("expected `{}` to occur after `:`", alignment),
864 span: range,
865 secondary_label: None,
866 suggestion: Suggestion::None,
867 },
868 );
869 }
870 }
871
872 fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: &Argument<'_>) {
873 if !arg.is_identifier() {
875 return;
876 }
877
878 if let Some((_range, _pos)) = self.consume_pos('.') {
879 let field = self.argument();
880 if !self.consume('}') {
883 return;
884 }
885 if let ArgumentNamed(_) = arg.position {
886 match field.position {
887 ArgumentNamed(_) => {
888 self.errors.insert(
889 0,
890 ParseError {
891 description: "field access isn't supported".to_string(),
892 note: None,
893 label: "not supported".to_string(),
894 span: arg.position_span.start..field.position_span.end,
895 secondary_label: None,
896 suggestion: Suggestion::UsePositional,
897 },
898 );
899 }
900 ArgumentIs(_) => {
901 self.errors.insert(
902 0,
903 ParseError {
904 description: "tuple index access isn't supported".to_string(),
905 note: None,
906 label: "not supported".to_string(),
907 span: arg.position_span.start..field.position_span.end,
908 secondary_label: None,
909 suggestion: Suggestion::UsePositional,
910 },
911 );
912 }
913 _ => {}
914 };
915 }
916 }
917 }
918}
919
920#[cfg(all(test, target_pointer_width = "64"))]
922rustc_index::static_assert_size!(Piece<'_>, 16);
923
924#[cfg(test)]
925mod tests;