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(Copy, Clone, Debug, Eq, PartialEq)]
22pub enum ParseMode {
23 Format,
25 InlineAsm,
27 Diagnostic,
32}
33
34#[derive(Clone, Debug, PartialEq)]
37pub enum Piece<'input> {
38 Lit(&'input str),
40 NextArgument(Box<Argument<'input>>),
43}
44
45#[derive(Clone, Debug, 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 matches!(self.position, Position::ArgumentNamed(_)) && self.format == FormatSpec::default()
60 }
61}
62
63#[derive(Clone, Debug, PartialEq, 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(Clone, Debug, 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(Copy, Clone, Debug, PartialEq, Default)]
118pub enum Alignment {
119 AlignLeft,
121 AlignRight,
123 AlignCenter,
125 #[default]
127 AlignUnknown,
128}
129
130#[derive(Copy, Clone, Debug, PartialEq)]
132pub enum Sign {
133 Plus,
135 Minus,
137}
138
139#[derive(Copy, Clone, Debug, PartialEq)]
141pub enum DebugHex {
142 Lower,
144 Upper,
146}
147
148#[derive(Clone, Debug, PartialEq, 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}
188
189pub struct Parser<'input> {
196 mode: ParseMode,
197 input: &'input str,
199 input_vec: Vec<(Range<usize>, usize, char)>,
201 input_vec_index: usize,
203 pub errors: Vec<ParseError>,
205 pub curarg: usize,
207 pub arg_places: Vec<Range<usize>>,
209 last_open_brace: Option<Range<usize>>,
211 pub is_source_literal: bool,
215 end_of_snippet: usize,
217 cur_line_start: usize,
219 pub line_spans: Vec<Range<usize>>,
222}
223
224impl<'input> Iterator for Parser<'input> {
225 type Item = Piece<'input>;
226
227 fn next(&mut self) -> Option<Piece<'input>> {
228 if let Some((Range { start, end }, idx, ch)) = self.peek() {
229 match ch {
230 '{' => {
231 self.input_vec_index += 1;
232 if let Some((_, i, '{')) = self.peek() {
233 self.input_vec_index += 1;
234 Some(Piece::Lit(self.string(i)))
237 } else {
238 self.last_open_brace = Some(start..end);
240 let arg = self.argument();
241 self.ws();
242 if let Some((close_brace_range, _)) = self.consume_pos('}') {
243 if self.is_source_literal {
244 self.arg_places.push(start..close_brace_range.end);
245 }
246 } else {
247 self.missing_closing_brace(&arg);
248 }
249
250 Some(Piece::NextArgument(Box::new(arg)))
251 }
252 }
253 '}' => {
254 self.input_vec_index += 1;
255 if let Some((_, i, '}')) = self.peek() {
256 self.input_vec_index += 1;
257 Some(Piece::Lit(self.string(i)))
260 } else {
261 self.errors.push(ParseError {
263 description: "unmatched `}` found".into(),
264 note: Some(
265 "if you intended to print `}`, you can escape it using `}}`".into(),
266 ),
267 label: "unmatched `}`".into(),
268 span: start..end,
269 secondary_label: None,
270 suggestion: Suggestion::None,
271 });
272 None
273 }
274 }
275 _ => Some(Piece::Lit(self.string(idx))),
276 }
277 } else {
278 if self.is_source_literal {
280 let span = self.cur_line_start..self.end_of_snippet;
281 if self.line_spans.last() != Some(&span) {
282 self.line_spans.push(span);
283 }
284 }
285 None
286 }
287 }
288}
289
290impl<'input> Parser<'input> {
291 pub fn new(
297 input: &'input str,
298 style: Option<usize>,
299 snippet: Option<String>,
300 appended_newline: bool,
301 mode: ParseMode,
302 ) -> Self {
303 let quote_offset = style.map_or(1, |nr_hashes| nr_hashes + 2);
304
305 let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
306 if let Some(nr_hashes) = style {
307 (true, snippet.len() - nr_hashes - 1, vec![])
310 } else {
311 if snippet.starts_with('"') {
313 let without_quotes = &snippet[1..snippet.len() - 1];
316 let (mut ok, mut vec) = (true, vec![]);
317 let mut chars = input.chars();
318 rustc_literal_escaper::unescape_str(without_quotes, |range, res| match res {
319 Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
320 vec.push((range, ch));
321 }
322 _ => {
323 ok = false;
324 vec = vec![];
325 }
326 });
327 let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
328 if ok {
329 if appended_newline {
330 if chars.as_str() == "\n" {
331 vec.push((end..end + 1, '\n'));
332 (true, 1 + end, vec)
333 } else {
334 (false, snippet.len(), vec![])
335 }
336 } else if chars.as_str() == "" {
337 (true, 1 + end, vec)
338 } else {
339 (false, snippet.len(), vec![])
340 }
341 } else {
342 (false, snippet.len(), vec![])
343 }
344 } else {
345 (false, snippet.len(), vec![])
347 }
348 }
349 } else {
350 (false, input.len() - if appended_newline { 1 } else { 0 }, vec![])
352 };
353
354 let input_vec: Vec<(Range<usize>, usize, char)> = if pre_input_vec.is_empty() {
355 input
358 .char_indices()
359 .map(|(idx, c)| {
360 let i = idx + quote_offset;
361 (i..i + c.len_utf8(), idx, c)
362 })
363 .collect()
364 } else {
365 input
367 .char_indices()
368 .zip(pre_input_vec)
369 .map(|((i, c), (r, _))| (r.start + quote_offset..r.end + quote_offset, i, c))
370 .collect()
371 };
372
373 Parser {
374 mode,
375 input,
376 input_vec,
377 input_vec_index: 0,
378 errors: vec![],
379 curarg: 0,
380 arg_places: vec![],
381 last_open_brace: None,
382 is_source_literal,
383 end_of_snippet,
384 cur_line_start: quote_offset,
385 line_spans: vec![],
386 }
387 }
388
389 pub fn peek(&self) -> Option<(Range<usize>, usize, char)> {
391 self.input_vec.get(self.input_vec_index).cloned()
392 }
393
394 pub fn peek_ahead(&self) -> Option<(Range<usize>, usize, char)> {
396 self.input_vec.get(self.input_vec_index + 1).cloned()
397 }
398
399 fn consume(&mut self, c: char) -> bool {
403 self.consume_pos(c).is_some()
404 }
405
406 fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> {
411 if let Some((r, i, c)) = self.peek()
412 && ch == c
413 {
414 self.input_vec_index += 1;
415 return Some((r, i));
416 }
417
418 None
419 }
420
421 fn missing_closing_brace(&mut self, arg: &Argument<'_>) {
423 let (range, description) = if let Some((r, _, c)) = self.peek() {
424 (r.start..r.start, format!("expected `}}`, found `{}`", c.escape_debug()))
425 } else {
426 (
427 self.end_of_snippet..self.end_of_snippet,
429 "expected `}` but string was terminated".to_owned(),
430 )
431 };
432
433 let (note, secondary_label) = if arg.format.fill == Some('}') {
434 (
435 Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
436 arg.format.fill_span.clone().map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
437 )
438 } else {
439 (
440 Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
441 self.last_open_brace
442 .clone()
443 .map(|sp| ("because of this opening brace".to_owned(), sp)),
444 )
445 };
446
447 self.errors.push(ParseError {
448 description,
449 note,
450 label: "expected `}`".to_owned(),
451 span: range.start..range.start,
452 secondary_label,
453 suggestion: Suggestion::None,
454 });
455
456 if let Some((_, _, c)) = self.peek() {
457 match c {
458 '?' => self.suggest_format_debug(),
459 '<' | '^' | '>' => self.suggest_format_align(c),
460 _ => self.suggest_positional_arg_instead_of_captured_arg(arg),
461 }
462 }
463 }
464
465 fn ws(&mut self) {
467 let rest = &self.input_vec[self.input_vec_index..];
468 let step = rest.iter().position(|&(_, _, c)| !c.is_whitespace()).unwrap_or(rest.len());
469 self.input_vec_index += step;
470 }
471
472 fn string(&mut self, start: usize) -> &'input str {
475 while let Some((r, i, c)) = self.peek() {
476 match c {
477 '{' | '}' => {
478 return &self.input[start..i];
479 }
480 '\n' if self.is_source_literal => {
481 self.input_vec_index += 1;
482 self.line_spans.push(self.cur_line_start..r.start);
483 self.cur_line_start = r.end;
484 }
485 _ => {
486 self.input_vec_index += 1;
487 if self.is_source_literal && r.start == self.cur_line_start && c.is_whitespace()
488 {
489 self.cur_line_start = r.end;
490 }
491 }
492 }
493 }
494 &self.input[start..]
495 }
496
497 fn argument(&mut self) -> Argument<'input> {
499 let start_idx = self.input_vec_index;
500
501 let position = self.position();
502 self.ws();
503
504 let end_idx = self.input_vec_index;
505
506 let format = match self.mode {
507 ParseMode::Format => self.format(),
508 ParseMode::InlineAsm => self.inline_asm(),
509 ParseMode::Diagnostic => self.diagnostic(),
510 };
511
512 let position = position.unwrap_or_else(|| {
514 let i = self.curarg;
515 self.curarg += 1;
516 ArgumentImplicitlyIs(i)
517 });
518
519 let position_span =
520 self.input_vec_index2range(start_idx).start..self.input_vec_index2range(end_idx).start;
521 Argument { position, position_span, format }
522 }
523
524 fn position(&mut self) -> Option<Position<'input>> {
529 if let Some(i) = self.integer() {
530 Some(ArgumentIs(i.into()))
531 } else {
532 match self.peek() {
533 Some((range, _, c)) if rustc_lexer::is_id_start(c) => {
534 let start = range.start;
535 let word = self.word();
536
537 if word == "r"
539 && let Some((r, _, '#')) = self.peek()
540 && self.peek_ahead().is_some_and(|(_, _, c)| rustc_lexer::is_id_start(c))
541 {
542 self.input_vec_index += 1;
543 let prefix_end = r.end;
544 let word = self.word();
545 let prefix_span = start..prefix_end;
546 let full_span =
547 start..self.input_vec_index2range(self.input_vec_index).start;
548 self.errors.insert(0, ParseError {
549 description: "raw identifiers are not supported".to_owned(),
550 note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
551 label: "raw identifier used here".to_owned(),
552 span: full_span,
553 secondary_label: None,
554 suggestion: Suggestion::RemoveRawIdent(prefix_span),
555 });
556 return Some(ArgumentNamed(word));
557 }
558
559 Some(ArgumentNamed(word))
560 }
561 _ => None,
565 }
566 }
567 }
568
569 fn input_vec_index2pos(&self, index: usize) -> usize {
570 if let Some((_, pos, _)) = self.input_vec.get(index) { *pos } else { self.input.len() }
571 }
572
573 fn input_vec_index2range(&self, index: usize) -> Range<usize> {
574 if let Some((r, _, _)) = self.input_vec.get(index) {
575 r.clone()
576 } else {
577 self.end_of_snippet..self.end_of_snippet
578 }
579 }
580
581 fn format(&mut self) -> FormatSpec<'input> {
584 let mut spec = FormatSpec::default();
585
586 if !self.consume(':') {
587 return spec;
588 }
589
590 if let (Some((r, _, c)), Some((_, _, '>' | '<' | '^'))) = (self.peek(), self.peek_ahead()) {
592 self.input_vec_index += 1;
593 spec.fill = Some(c);
594 spec.fill_span = Some(r);
595 }
596 if self.consume('<') {
598 spec.align = AlignLeft;
599 } else if self.consume('>') {
600 spec.align = AlignRight;
601 } else if self.consume('^') {
602 spec.align = AlignCenter;
603 }
604 if self.consume('+') {
606 spec.sign = Some(Sign::Plus);
607 } else if self.consume('-') {
608 spec.sign = Some(Sign::Minus);
609 }
610 if self.consume('#') {
612 spec.alternate = true;
613 }
614 let mut havewidth = false;
616
617 if let Some((range, _)) = self.consume_pos('0') {
618 if let Some((r, _)) = self.consume_pos('$') {
623 spec.width = CountIsParam(0);
624 spec.width_span = Some(range.start..r.end);
625 havewidth = true;
626 } else {
627 spec.zero_pad = true;
628 }
629 }
630
631 if !havewidth {
632 let start_idx = self.input_vec_index;
633 spec.width = self.count();
634 if spec.width != CountImplied {
635 let end = self.input_vec_index2range(self.input_vec_index).start;
636 spec.width_span = Some(self.input_vec_index2range(start_idx).start..end);
637 }
638 }
639
640 if let Some((range, _)) = self.consume_pos('.') {
641 if self.consume('*') {
642 let i = self.curarg;
645 self.curarg += 1;
646 spec.precision = CountIsStar(i);
647 } else {
648 spec.precision = self.count();
649 }
650 spec.precision_span =
651 Some(range.start..self.input_vec_index2range(self.input_vec_index).start);
652 }
653
654 let start_idx = self.input_vec_index;
655 if self.consume('x') {
657 if self.consume('?') {
658 spec.debug_hex = Some(DebugHex::Lower);
659 spec.ty = "?";
660 } else {
661 spec.ty = "x";
662 }
663 } else if self.consume('X') {
664 if self.consume('?') {
665 spec.debug_hex = Some(DebugHex::Upper);
666 spec.ty = "?";
667 } else {
668 spec.ty = "X";
669 }
670 } else if let Some((range, _)) = self.consume_pos('?') {
671 spec.ty = "?";
672 if let Some((r, _, c @ ('#' | 'x' | 'X'))) = self.peek() {
673 self.errors.insert(
674 0,
675 ParseError {
676 description: format!("expected `}}`, found `{c}`"),
677 note: None,
678 label: "expected `'}'`".into(),
679 span: r.clone(),
680 secondary_label: None,
681 suggestion: Suggestion::ReorderFormatParameter(
682 range.start..r.end,
683 format!("{c}?"),
684 ),
685 },
686 );
687 }
688 } else {
689 spec.ty = self.word();
690 if !spec.ty.is_empty() {
691 let start = self.input_vec_index2range(start_idx).start;
692 let end = self.input_vec_index2range(self.input_vec_index).start;
693 spec.ty_span = Some(start..end);
694 }
695 }
696 spec
697 }
698
699 fn inline_asm(&mut self) -> FormatSpec<'input> {
702 let mut spec = FormatSpec::default();
703
704 if !self.consume(':') {
705 return spec;
706 }
707
708 let start_idx = self.input_vec_index;
709 spec.ty = self.word();
710 if !spec.ty.is_empty() {
711 let start = self.input_vec_index2range(start_idx).start;
712 let end = self.input_vec_index2range(self.input_vec_index).start;
713 spec.ty_span = Some(start..end);
714 }
715
716 spec
717 }
718
719 fn diagnostic(&mut self) -> FormatSpec<'input> {
721 let mut spec = FormatSpec::default();
722
723 let Some((Range { start, .. }, start_idx)) = self.consume_pos(':') else {
724 return spec;
725 };
726
727 spec.ty = self.string(start_idx);
728 spec.ty_span = {
729 let end = self.input_vec_index2range(self.input_vec_index).start;
730 Some(start..end)
731 };
732 spec
733 }
734
735 fn count(&mut self) -> Count<'input> {
739 if let Some(i) = self.integer() {
740 if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) }
741 } else {
742 let start_idx = self.input_vec_index;
743 let word = self.word();
744 if word.is_empty() {
745 CountImplied
746 } else if let Some((r, _)) = self.consume_pos('$') {
747 CountIsName(word, self.input_vec_index2range(start_idx).start..r.start)
748 } else {
749 self.input_vec_index = start_idx;
750 CountImplied
751 }
752 }
753 }
754
755 fn word(&mut self) -> &'input str {
758 let index = self.input_vec_index;
759 match self.peek() {
760 Some((ref r, i, c)) if rustc_lexer::is_id_start(c) => {
761 self.input_vec_index += 1;
762 (r.start, i)
763 }
764 _ => {
765 return "";
766 }
767 };
768 let (err_end, end): (usize, usize) = loop {
769 if let Some((ref r, i, c)) = self.peek() {
770 if rustc_lexer::is_id_continue(c) {
771 self.input_vec_index += 1;
772 } else {
773 break (r.start, i);
774 }
775 } else {
776 break (self.end_of_snippet, self.input.len());
777 }
778 };
779
780 let word = &self.input[self.input_vec_index2pos(index)..end];
781 if word == "_" {
782 self.errors.push(ParseError {
783 description: "invalid argument name `_`".into(),
784 note: Some("argument name cannot be a single underscore".into()),
785 label: "invalid argument name".into(),
786 span: self.input_vec_index2range(index).start..err_end,
787 secondary_label: None,
788 suggestion: Suggestion::None,
789 });
790 }
791 word
792 }
793
794 fn integer(&mut self) -> Option<u16> {
795 let mut cur: u16 = 0;
796 let mut found = false;
797 let mut overflow = false;
798 let start_index = self.input_vec_index;
799 while let Some((_, _, c)) = self.peek() {
800 if let Some(i) = c.to_digit(10) {
801 self.input_vec_index += 1;
802 let (tmp, mul_overflow) = cur.overflowing_mul(10);
803 let (tmp, add_overflow) = tmp.overflowing_add(i as u16);
804 if mul_overflow || add_overflow {
805 overflow = true;
806 }
807 cur = tmp;
808 found = true;
809 } else {
810 break;
811 }
812 }
813
814 if overflow {
815 let overflowed_int = &self.input[self.input_vec_index2pos(start_index)
816 ..self.input_vec_index2pos(self.input_vec_index)];
817 self.errors.push(ParseError {
818 description: format!(
819 "integer `{}` does not fit into the type `u16` whose range is `0..={}`",
820 overflowed_int,
821 u16::MAX
822 ),
823 note: None,
824 label: "integer out of range for `u16`".into(),
825 span: self.input_vec_index2range(start_index).start
826 ..self.input_vec_index2range(self.input_vec_index).end,
827 secondary_label: None,
828 suggestion: Suggestion::None,
829 });
830 }
831
832 found.then_some(cur)
833 }
834
835 fn suggest_format_debug(&mut self) {
836 if let (Some((range, _)), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
837 let word = self.word();
838 self.errors.insert(
839 0,
840 ParseError {
841 description: "expected format parameter to occur after `:`".to_owned(),
842 note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
843 label: "expected `?` to occur after `:`".to_owned(),
844 span: range,
845 secondary_label: None,
846 suggestion: Suggestion::None,
847 },
848 );
849 }
850 }
851
852 fn suggest_format_align(&mut self, alignment: char) {
853 if let Some((range, _)) = self.consume_pos(alignment) {
854 self.errors.insert(
855 0,
856 ParseError {
857 description:
858 "expected alignment specifier after `:` in format string; example: `{:>?}`"
859 .to_owned(),
860 note: None,
861 label: format!("expected `{}` to occur after `:`", alignment),
862 span: range,
863 secondary_label: None,
864 suggestion: Suggestion::None,
865 },
866 );
867 }
868 }
869
870 fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: &Argument<'_>) {
871 if !arg.is_identifier() {
873 return;
874 }
875
876 if let Some((_range, _pos)) = self.consume_pos('.') {
877 let field = self.argument();
878 if !self.consume('}') {
881 return;
882 }
883 if let ArgumentNamed(_) = arg.position {
884 match field.position {
885 ArgumentNamed(_) => {
886 self.errors.insert(
887 0,
888 ParseError {
889 description: "field access isn't supported".to_string(),
890 note: None,
891 label: "not supported".to_string(),
892 span: arg.position_span.start..field.position_span.end,
893 secondary_label: None,
894 suggestion: Suggestion::UsePositional,
895 },
896 );
897 }
898 ArgumentIs(_) => {
899 self.errors.insert(
900 0,
901 ParseError {
902 description: "tuple index access isn't supported".to_string(),
903 note: None,
904 label: "not supported".to_string(),
905 span: arg.position_span.start..field.position_span.end,
906 secondary_label: None,
907 suggestion: Suggestion::UsePositional,
908 },
909 );
910 }
911 _ => {}
912 };
913 }
914 }
915 }
916}
917
918#[cfg(all(test, target_pointer_width = "64"))]
920rustc_index::static_assert_size!(Piece<'_>, 16);
921
922#[cfg(test)]
923mod tests;