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