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}
33
34#[derive(Clone, Debug, PartialEq)]
37pub enum Piece<'a> {
38 Lit(&'a str),
40 NextArgument(Box<Argument<'a>>),
43}
44
45#[derive(Clone, Debug, PartialEq)]
47pub struct Argument<'a> {
48 pub position: Position<'a>,
50 pub position_span: Range<usize>,
53 pub format: FormatSpec<'a>,
55}
56
57impl<'a> Argument<'a> {
58 pub fn is_identifier(&self) -> bool {
59 matches!(self.position, Position::ArgumentNamed(_))
60 && matches!(
61 self.format,
62 FormatSpec {
63 fill: None,
64 fill_span: None,
65 align: AlignUnknown,
66 sign: None,
67 alternate: false,
68 zero_pad: false,
69 debug_hex: None,
70 precision: CountImplied,
71 precision_span: None,
72 width: CountImplied,
73 width_span: None,
74 ty: "",
75 ty_span: None,
76 },
77 )
78 }
79}
80
81#[derive(Clone, Debug, PartialEq)]
83pub struct FormatSpec<'a> {
84 pub fill: Option<char>,
86 pub fill_span: Option<Range<usize>>,
88 pub align: Alignment,
90 pub sign: Option<Sign>,
92 pub alternate: bool,
94 pub zero_pad: bool,
96 pub debug_hex: Option<DebugHex>,
98 pub precision: Count<'a>,
100 pub precision_span: Option<Range<usize>>,
102 pub width: Count<'a>,
104 pub width_span: Option<Range<usize>>,
106 pub ty: &'a str,
110 pub ty_span: Option<Range<usize>>,
112}
113
114#[derive(Clone, Debug, PartialEq)]
116pub enum Position<'a> {
117 ArgumentImplicitlyIs(usize),
119 ArgumentIs(usize),
121 ArgumentNamed(&'a str),
123}
124
125impl Position<'_> {
126 pub fn index(&self) -> Option<usize> {
127 match self {
128 ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
129 _ => None,
130 }
131 }
132}
133
134#[derive(Copy, Clone, Debug, PartialEq)]
136pub enum Alignment {
137 AlignLeft,
139 AlignRight,
141 AlignCenter,
143 AlignUnknown,
145}
146
147#[derive(Copy, Clone, Debug, PartialEq)]
149pub enum Sign {
150 Plus,
152 Minus,
154}
155
156#[derive(Copy, Clone, Debug, PartialEq)]
158pub enum DebugHex {
159 Lower,
161 Upper,
163}
164
165#[derive(Clone, Debug, PartialEq)]
168pub enum Count<'a> {
169 CountIs(u16),
171 CountIsName(&'a str, Range<usize>),
173 CountIsParam(usize),
175 CountIsStar(usize),
177 CountImplied,
179}
180
181pub struct ParseError {
182 pub description: String,
183 pub note: Option<String>,
184 pub label: String,
185 pub span: Range<usize>,
186 pub secondary_label: Option<(String, Range<usize>)>,
187 pub suggestion: Suggestion,
188}
189
190pub enum Suggestion {
191 None,
192 UsePositional,
195 RemoveRawIdent(Range<usize>),
198 ReorderFormatParameter(Range<usize>, String),
203}
204
205pub struct Parser<'a> {
212 mode: ParseMode,
213 input: &'a str,
215 input_vec: Vec<(Range<usize>, usize, char)>,
217 input_vec_index: usize,
219 pub errors: Vec<ParseError>,
221 pub curarg: usize,
223 pub arg_places: Vec<Range<usize>>,
225 last_open_brace: Option<Range<usize>>,
227 pub is_source_literal: bool,
231 end_of_snippet: usize,
233 cur_line_start: usize,
235 pub line_spans: Vec<Range<usize>>,
238}
239
240impl<'a> Iterator for Parser<'a> {
241 type Item = Piece<'a>;
242
243 fn next(&mut self) -> Option<Piece<'a>> {
244 if let Some(&(Range { start, end }, idx, ch)) = self.input_vec.get(self.input_vec_index) {
245 match ch {
246 '{' => {
247 self.input_vec_index += 1;
248 if let Some(&(_, i, '{')) = self.input_vec.get(self.input_vec_index) {
249 self.input_vec_index += 1;
250 Some(Piece::Lit(self.string(i)))
253 } else {
254 self.last_open_brace = Some(start..end);
256 let arg = self.argument();
257 if let Some(close_brace_range) = self.consume_closing_brace(&arg) {
258 if self.is_source_literal {
259 self.arg_places.push(start..close_brace_range.end);
260 }
261 } else if let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) {
262 match c {
263 '?' => self.suggest_format_debug(),
264 '<' | '^' | '>' => self.suggest_format_align(c),
265 _ => {
266 self.suggest_positional_arg_instead_of_captured_arg(arg.clone())
267 }
268 }
269 }
270 Some(Piece::NextArgument(Box::new(arg)))
271 }
272 }
273 '}' => {
274 self.input_vec_index += 1;
275 if let Some(&(_, i, '}')) = self.input_vec.get(self.input_vec_index) {
276 self.input_vec_index += 1;
277 Some(Piece::Lit(self.string(i)))
280 } else {
281 self.errors.push(ParseError {
283 description: "unmatched `}` found".into(),
284 note: Some(
285 "if you intended to print `}`, you can escape it using `}}`".into(),
286 ),
287 label: "unmatched `}`".into(),
288 span: start..end,
289 secondary_label: None,
290 suggestion: Suggestion::None,
291 });
292 None
293 }
294 }
295 _ => Some(Piece::Lit(self.string(idx))),
296 }
297 } else {
298 if self.is_source_literal {
300 let span = self.cur_line_start..self.end_of_snippet;
301 if self.line_spans.last() != Some(&span) {
302 self.line_spans.push(span);
303 }
304 }
305 None
306 }
307 }
308}
309
310impl<'a> Parser<'a> {
311 pub fn new(
317 input: &'a str,
318 style: Option<usize>,
319 snippet: Option<String>,
320 appended_newline: bool,
321 mode: ParseMode,
322 ) -> Self {
323 let quote_offset = style.map_or(1, |nr_hashes| nr_hashes + 2);
324
325 let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
326 if let Some(nr_hashes) = style {
327 (true, snippet.len() - nr_hashes - 1, vec![])
330 } else {
331 if snippet.starts_with('"') {
333 let without_quotes = &snippet[1..snippet.len() - 1];
336 let (mut ok, mut vec) = (true, vec![]);
337 let mut chars = input.chars();
338 unescape_unicode(without_quotes, Mode::Str, &mut |range, res| match res {
339 Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
340 vec.push((range, ch));
341 }
342 _ => {
343 ok = false;
344 vec = vec![];
345 }
346 });
347 let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
348 if ok {
349 if appended_newline {
350 if chars.as_str() == "\n" {
351 vec.push((end..end + 1, '\n'));
352 (true, 1 + end, vec)
353 } else {
354 (false, snippet.len(), vec![])
355 }
356 } else if chars.as_str() == "" {
357 (true, 1 + end, vec)
358 } else {
359 (false, snippet.len(), vec![])
360 }
361 } else {
362 (false, snippet.len(), vec![])
363 }
364 } else {
365 (false, snippet.len(), vec![])
367 }
368 }
369 } else {
370 (false, input.len() - if appended_newline { 1 } else { 0 }, vec![])
372 };
373
374 let input_vec: Vec<(Range<usize>, usize, char)> = if pre_input_vec.is_empty() {
375 input
378 .char_indices()
379 .map(|(idx, c)| {
380 let i = idx + quote_offset;
381 (i..i + c.len_utf8(), idx, c)
382 })
383 .collect()
384 } else {
385 input
387 .char_indices()
388 .zip(pre_input_vec)
389 .map(|((i, c), (r, _))| (r.start + quote_offset..r.end + quote_offset, i, c))
390 .collect()
391 };
392
393 Parser {
394 mode,
395 input,
396 input_vec,
397 input_vec_index: 0,
398 errors: vec![],
399 curarg: 0,
400 arg_places: vec![],
401 last_open_brace: None,
402 is_source_literal,
403 end_of_snippet,
404 cur_line_start: quote_offset,
405 line_spans: vec![],
406 }
407 }
408
409 fn consume(&mut self, c: char) -> bool {
413 self.consume_pos(c).is_some()
414 }
415
416 fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> {
421 if let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) {
422 if ch == *c {
423 self.input_vec_index += 1;
424 return Some((r.clone(), *i));
425 }
426 }
427 None
428 }
429
430 fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<Range<usize>> {
433 self.ws();
434
435 let (range, description) = if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index)
436 {
437 if *c == '}' {
438 self.input_vec_index += 1;
439 return Some(r.clone());
440 }
441 (r.start..r.start, format!("expected `}}`, found `{}`", c.escape_debug()))
443 } else {
444 (
445 self.end_of_snippet..self.end_of_snippet,
447 "expected `}` but string was terminated".to_owned(),
448 )
449 };
450
451 let (note, secondary_label) = if arg.format.fill == Some('}') {
452 (
453 Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
454 arg.format.fill_span.clone().map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
455 )
456 } else {
457 (
458 Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
459 self.last_open_brace
460 .clone()
461 .map(|sp| ("because of this opening brace".to_owned(), sp)),
462 )
463 };
464
465 self.errors.push(ParseError {
466 description,
467 note,
468 label: "expected `}`".to_owned(),
469 span: range.start..range.start,
470 secondary_label,
471 suggestion: Suggestion::None,
472 });
473
474 None
475 }
476
477 fn ws(&mut self) {
479 let rest = &self.input_vec[self.input_vec_index..];
480 let step = rest.iter().position(|&(_, _, c)| !c.is_whitespace()).unwrap_or(rest.len());
481 self.input_vec_index += step;
482 }
483
484 fn string(&mut self, start: usize) -> &'a str {
487 while let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) {
488 match c {
489 '{' | '}' => {
490 return &self.input[start..*i];
491 }
492 '\n' if self.is_source_literal => {
493 self.input_vec_index += 1;
494 self.line_spans.push(self.cur_line_start..r.start);
495 self.cur_line_start = r.end;
496 }
497 _ => {
498 self.input_vec_index += 1;
499 if self.is_source_literal && r.start == self.cur_line_start && c.is_whitespace()
500 {
501 self.cur_line_start = r.end;
502 }
503 }
504 }
505 }
506 &self.input[start..]
507 }
508
509 fn argument(&mut self) -> Argument<'a> {
511 let start_idx = self.input_vec_index;
512
513 let position = self.position();
514 self.ws();
515
516 let end_idx = self.input_vec_index;
517
518 let format = match self.mode {
519 ParseMode::Format => self.format(),
520 ParseMode::InlineAsm => self.inline_asm(),
521 };
522
523 let position = position.unwrap_or_else(|| {
525 let i = self.curarg;
526 self.curarg += 1;
527 ArgumentImplicitlyIs(i)
528 });
529
530 let position_span =
531 self.input_vec_index2range(start_idx).start..self.input_vec_index2range(end_idx).start;
532 Argument { position, position_span, format }
533 }
534
535 fn position(&mut self) -> Option<Position<'a>> {
540 if let Some(i) = self.integer() {
541 Some(ArgumentIs(i.into()))
542 } else {
543 match self.input_vec.get(self.input_vec_index) {
544 Some((range, _, c)) if rustc_lexer::is_id_start(*c) => {
545 let start = range.start;
546 let word = self.word();
547
548 if word == "r" {
551 if let Some((r, _, '#')) = self.input_vec.get(self.input_vec_index) {
552 if self
553 .input_vec
554 .get(self.input_vec_index + 1)
555 .is_some_and(|(_, _, c)| rustc_lexer::is_id_start(*c))
556 {
557 self.input_vec_index += 1;
558 let prefix_end = r.end;
559 let word = self.word();
560 let prefix_span = start..prefix_end;
561 let full_span =
562 start..self.input_vec_index2range(self.input_vec_index).start;
563 self.errors.insert(0, ParseError {
564 description: "raw identifiers are not supported".to_owned(),
565 note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
566 label: "raw identifier used here".to_owned(),
567 span: full_span,
568 secondary_label: None,
569 suggestion: Suggestion::RemoveRawIdent(prefix_span),
570 });
571 return Some(ArgumentNamed(word));
572 }
573 }
574 }
575
576 Some(ArgumentNamed(word))
577 }
578 _ => None,
582 }
583 }
584 }
585
586 fn input_vec_index2pos(&self, index: usize) -> usize {
587 if let Some(&(_, pos, _)) = self.input_vec.get(index) { pos } else { self.input.len() }
588 }
589
590 fn input_vec_index2range(&self, index: usize) -> Range<usize> {
591 if let Some((r, _, _)) = self.input_vec.get(index) {
592 r.clone()
593 } else {
594 self.end_of_snippet..self.end_of_snippet
595 }
596 }
597
598 fn format(&mut self) -> FormatSpec<'a> {
601 let mut spec = FormatSpec {
602 fill: None,
603 fill_span: None,
604 align: AlignUnknown,
605 sign: None,
606 alternate: false,
607 zero_pad: false,
608 debug_hex: None,
609 precision: CountImplied,
610 precision_span: None,
611 width: CountImplied,
612 width_span: None,
613 ty: &self.input[..0],
614 ty_span: None,
615 };
616 if !self.consume(':') {
617 return spec;
618 }
619
620 if let Some(&(ref r, _, c)) = self.input_vec.get(self.input_vec_index) {
622 if let Some((_, _, '>' | '<' | '^')) = self.input_vec.get(self.input_vec_index + 1) {
623 self.input_vec_index += 1;
624 spec.fill = Some(c);
625 spec.fill_span = Some(r.clone());
626 }
627 }
628 if self.consume('<') {
630 spec.align = AlignLeft;
631 } else if self.consume('>') {
632 spec.align = AlignRight;
633 } else if self.consume('^') {
634 spec.align = AlignCenter;
635 }
636 if self.consume('+') {
638 spec.sign = Some(Sign::Plus);
639 } else if self.consume('-') {
640 spec.sign = Some(Sign::Minus);
641 }
642 if self.consume('#') {
644 spec.alternate = true;
645 }
646 let mut havewidth = false;
648
649 if let Some((range, _)) = self.consume_pos('0') {
650 if let Some((r, _)) = self.consume_pos('$') {
655 spec.width = CountIsParam(0);
656 spec.width_span = Some(range.start..r.end);
657 havewidth = true;
658 } else {
659 spec.zero_pad = true;
660 }
661 }
662
663 if !havewidth {
664 let start_idx = self.input_vec_index;
665 spec.width = self.count();
666 if spec.width != CountImplied {
667 let end = self.input_vec_index2range(self.input_vec_index).start;
668 spec.width_span = Some(self.input_vec_index2range(start_idx).start..end);
669 }
670 }
671
672 if let Some((range, _)) = self.consume_pos('.') {
673 if self.consume('*') {
674 let i = self.curarg;
677 self.curarg += 1;
678 spec.precision = CountIsStar(i);
679 } else {
680 spec.precision = self.count();
681 }
682 spec.precision_span =
683 Some(range.start..self.input_vec_index2range(self.input_vec_index).start);
684 }
685
686 let start_idx = self.input_vec_index;
687 if self.consume('x') {
689 if self.consume('?') {
690 spec.debug_hex = Some(DebugHex::Lower);
691 spec.ty = "?";
692 } else {
693 spec.ty = "x";
694 }
695 } else if self.consume('X') {
696 if self.consume('?') {
697 spec.debug_hex = Some(DebugHex::Upper);
698 spec.ty = "?";
699 } else {
700 spec.ty = "X";
701 }
702 } else if let Some((range, _)) = self.consume_pos('?') {
703 spec.ty = "?";
704 if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index) {
705 match c {
706 '#' | 'x' | 'X' => self.errors.insert(
707 0,
708 ParseError {
709 description: format!("expected `}}`, found `{c}`"),
710 note: None,
711 label: "expected `'}'`".into(),
712 span: r.clone(),
713 secondary_label: None,
714 suggestion: Suggestion::ReorderFormatParameter(
715 range.start..r.end,
716 format!("{c}?"),
717 ),
718 },
719 ),
720 _ => (),
721 }
722 }
723 } else {
724 spec.ty = self.word();
725 if !spec.ty.is_empty() {
726 let start = self.input_vec_index2range(start_idx).start;
727 let end = self.input_vec_index2range(self.input_vec_index).start;
728 spec.ty_span = Some(start..end);
729 }
730 }
731 spec
732 }
733
734 fn inline_asm(&mut self) -> FormatSpec<'a> {
737 let mut spec = FormatSpec {
738 fill: None,
739 fill_span: None,
740 align: AlignUnknown,
741 sign: None,
742 alternate: false,
743 zero_pad: false,
744 debug_hex: None,
745 precision: CountImplied,
746 precision_span: None,
747 width: CountImplied,
748 width_span: None,
749 ty: &self.input[..0],
750 ty_span: None,
751 };
752 if !self.consume(':') {
753 return spec;
754 }
755
756 let start_idx = self.input_vec_index;
757 spec.ty = self.word();
758 if !spec.ty.is_empty() {
759 let start = self.input_vec_index2range(start_idx).start;
760 let end = self.input_vec_index2range(self.input_vec_index).start;
761 spec.ty_span = Some(start..end);
762 }
763
764 spec
765 }
766
767 fn count(&mut self) -> Count<'a> {
771 if let Some(i) = self.integer() {
772 if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) }
773 } else {
774 let start_idx = self.input_vec_index;
775 let word = self.word();
776 if word.is_empty() {
777 CountImplied
778 } else if let Some((r, _)) = self.consume_pos('$') {
779 CountIsName(word, self.input_vec_index2range(start_idx).start..r.start)
780 } else {
781 self.input_vec_index = start_idx;
782 CountImplied
783 }
784 }
785 }
786
787 fn word(&mut self) -> &'a str {
790 let index = self.input_vec_index;
791 match self.input_vec.get(self.input_vec_index) {
792 Some(&(ref r, i, c)) if rustc_lexer::is_id_start(c) => {
793 self.input_vec_index += 1;
794 (r.start, i)
795 }
796 _ => {
797 return "";
798 }
799 };
800 let (err_end, end): (usize, usize) = loop {
801 if let Some(&(ref r, i, c)) = self.input_vec.get(self.input_vec_index) {
802 if rustc_lexer::is_id_continue(c) {
803 self.input_vec_index += 1;
804 } else {
805 break (r.start, i);
806 }
807 } else {
808 break (self.end_of_snippet, self.input.len());
809 }
810 };
811
812 let word = &self.input[self.input_vec_index2pos(index)..end];
813 if word == "_" {
814 self.errors.push(ParseError {
815 description: "invalid argument name `_`".into(),
816 note: Some("argument name cannot be a single underscore".into()),
817 label: "invalid argument name".into(),
818 span: self.input_vec_index2range(index).start..err_end,
819 secondary_label: None,
820 suggestion: Suggestion::None,
821 });
822 }
823 word
824 }
825
826 fn integer(&mut self) -> Option<u16> {
827 let mut cur: u16 = 0;
828 let mut found = false;
829 let mut overflow = false;
830 let start_index = self.input_vec_index;
831 while let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) {
832 if let Some(i) = c.to_digit(10) {
833 self.input_vec_index += 1;
834 let (tmp, mul_overflow) = cur.overflowing_mul(10);
835 let (tmp, add_overflow) = tmp.overflowing_add(i as u16);
836 if mul_overflow || add_overflow {
837 overflow = true;
838 }
839 cur = tmp;
840 found = true;
841 } else {
842 break;
843 }
844 }
845
846 if overflow {
847 let overflowed_int = &self.input[self.input_vec_index2pos(start_index)
848 ..self.input_vec_index2pos(self.input_vec_index)];
849 self.errors.push(ParseError {
850 description: format!(
851 "integer `{}` does not fit into the type `u16` whose range is `0..={}`",
852 overflowed_int,
853 u16::MAX
854 ),
855 note: None,
856 label: "integer out of range for `u16`".into(),
857 span: self.input_vec_index2range(start_index).start
858 ..self.input_vec_index2range(self.input_vec_index).end,
859 secondary_label: None,
860 suggestion: Suggestion::None,
861 });
862 }
863
864 found.then_some(cur)
865 }
866
867 fn suggest_format_debug(&mut self) {
868 if let (Some((range, _)), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
869 let word = self.word();
870 self.errors.insert(
871 0,
872 ParseError {
873 description: "expected format parameter to occur after `:`".to_owned(),
874 note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
875 label: "expected `?` to occur after `:`".to_owned(),
876 span: range,
877 secondary_label: None,
878 suggestion: Suggestion::None,
879 },
880 );
881 }
882 }
883
884 fn suggest_format_align(&mut self, alignment: char) {
885 if let Some((range, _)) = self.consume_pos(alignment) {
886 self.errors.insert(
887 0,
888 ParseError {
889 description: "expected format parameter to occur after `:`".to_owned(),
890 note: None,
891 label: format!("expected `{}` to occur after `:`", alignment),
892 span: range,
893 secondary_label: None,
894 suggestion: Suggestion::None,
895 },
896 );
897 }
898 }
899
900 fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) {
901 if !arg.is_identifier() {
903 return;
904 }
905
906 if let Some((_range, _pos)) = self.consume_pos('.') {
907 let field = self.argument();
908 if !self.consume('}') {
911 return;
912 }
913 if let ArgumentNamed(_) = arg.position {
914 match field.position {
915 ArgumentNamed(_) => {
916 self.errors.insert(
917 0,
918 ParseError {
919 description: "field access isn't supported".to_string(),
920 note: None,
921 label: "not supported".to_string(),
922 span: arg.position_span.start..field.position_span.end,
923 secondary_label: None,
924 suggestion: Suggestion::UsePositional,
925 },
926 );
927 }
928 ArgumentIs(_) => {
929 self.errors.insert(
930 0,
931 ParseError {
932 description: "tuple index access isn't supported".to_string(),
933 note: None,
934 label: "not supported".to_string(),
935 span: arg.position_span.start..field.position_span.end,
936 secondary_label: None,
937 suggestion: Suggestion::UsePositional,
938 },
939 );
940 }
941 _ => {}
942 };
943 }
944 }
945 }
946}
947
948#[cfg(all(test, target_pointer_width = "64"))]
950rustc_index::static_assert_size!(Piece<'_>, 16);
951
952#[cfg(test)]
953mod tests;