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)]
16#![warn(unreachable_pub)]
17pub use Alignment::*;
20pub use Count::*;
21pub use Position::*;
22use rustc_lexer::unescape;
23
24#[derive(Copy, Clone, PartialEq, Eq, Debug)]
27pub struct InnerSpan {
28 pub start: usize,
29 pub end: usize,
30}
31
32impl InnerSpan {
33 pub fn new(start: usize, end: usize) -> InnerSpan {
34 InnerSpan { start, end }
35 }
36}
37
38#[derive(Copy, Clone, PartialEq, Eq)]
41pub struct InnerWidthMapping {
42 pub position: usize,
44 pub before: usize,
46 pub after: usize,
48}
49
50impl InnerWidthMapping {
51 pub fn new(position: usize, before: usize, after: usize) -> InnerWidthMapping {
52 InnerWidthMapping { position, before, after }
53 }
54}
55
56#[derive(Clone, PartialEq, Eq)]
58enum InputStringKind {
59 NotALiteral,
60 Literal { width_mappings: Vec<InnerWidthMapping> },
61}
62
63#[derive(Copy, Clone, Debug, Eq, PartialEq)]
65pub enum ParseMode {
66 Format,
68 InlineAsm,
70}
71
72#[derive(Copy, Clone)]
73struct InnerOffset(usize);
74
75impl InnerOffset {
76 fn to(self, end: InnerOffset) -> InnerSpan {
77 InnerSpan::new(self.0, end.0)
78 }
79}
80
81#[derive(Clone, Debug, PartialEq)]
84pub enum Piece<'a> {
85 Lit(&'a str),
87 NextArgument(Box<Argument<'a>>),
90}
91
92#[derive(Copy, Clone, Debug, PartialEq)]
94pub struct Argument<'a> {
95 pub position: Position<'a>,
97 pub position_span: InnerSpan,
100 pub format: FormatSpec<'a>,
102}
103
104#[derive(Copy, Clone, Debug, PartialEq)]
106pub struct FormatSpec<'a> {
107 pub fill: Option<char>,
109 pub fill_span: Option<InnerSpan>,
111 pub align: Alignment,
113 pub sign: Option<Sign>,
115 pub alternate: bool,
117 pub zero_pad: bool,
119 pub debug_hex: Option<DebugHex>,
121 pub precision: Count<'a>,
123 pub precision_span: Option<InnerSpan>,
125 pub width: Count<'a>,
127 pub width_span: Option<InnerSpan>,
129 pub ty: &'a str,
133 pub ty_span: Option<InnerSpan>,
135}
136
137#[derive(Copy, Clone, Debug, PartialEq)]
139pub enum Position<'a> {
140 ArgumentImplicitlyIs(usize),
142 ArgumentIs(usize),
144 ArgumentNamed(&'a str),
146}
147
148impl Position<'_> {
149 pub fn index(&self) -> Option<usize> {
150 match self {
151 ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
152 _ => None,
153 }
154 }
155}
156
157#[derive(Copy, Clone, Debug, PartialEq)]
159pub enum Alignment {
160 AlignLeft,
162 AlignRight,
164 AlignCenter,
166 AlignUnknown,
168}
169
170#[derive(Copy, Clone, Debug, PartialEq)]
172pub enum Sign {
173 Plus,
175 Minus,
177}
178
179#[derive(Copy, Clone, Debug, PartialEq)]
181pub enum DebugHex {
182 Lower,
184 Upper,
186}
187
188#[derive(Copy, Clone, Debug, PartialEq)]
191pub enum Count<'a> {
192 CountIs(usize),
194 CountIsName(&'a str, InnerSpan),
196 CountIsParam(usize),
198 CountIsStar(usize),
200 CountImplied,
202}
203
204pub struct ParseError {
205 pub description: String,
206 pub note: Option<String>,
207 pub label: String,
208 pub span: InnerSpan,
209 pub secondary_label: Option<(String, InnerSpan)>,
210 pub suggestion: Suggestion,
211}
212
213pub enum Suggestion {
214 None,
215 UsePositional,
218 RemoveRawIdent(InnerSpan),
221 ReorderFormatParameter(InnerSpan, String),
226}
227
228pub struct Parser<'a> {
235 mode: ParseMode,
236 input: &'a str,
237 cur: std::iter::Peekable<std::str::CharIndices<'a>>,
238 pub errors: Vec<ParseError>,
240 pub curarg: usize,
242 style: Option<usize>,
244 pub arg_places: Vec<InnerSpan>,
246 width_map: Vec<InnerWidthMapping>,
248 last_opening_brace: Option<InnerSpan>,
250 append_newline: bool,
252 pub is_source_literal: bool,
256 cur_line_start: usize,
258 pub line_spans: Vec<InnerSpan>,
261}
262
263impl<'a> Iterator for Parser<'a> {
264 type Item = Piece<'a>;
265
266 fn next(&mut self) -> Option<Piece<'a>> {
267 if let Some(&(pos, c)) = self.cur.peek() {
268 match c {
269 '{' => {
270 let curr_last_brace = self.last_opening_brace;
271 let byte_pos = self.to_span_index(pos);
272 let lbrace_end = InnerOffset(byte_pos.0 + self.to_span_width(pos));
273 self.last_opening_brace = Some(byte_pos.to(lbrace_end));
274 self.cur.next();
275 if self.consume('{') {
276 self.last_opening_brace = curr_last_brace;
277
278 Some(Piece::Lit(self.string(pos + 1)))
279 } else {
280 let arg = self.argument(lbrace_end);
281 if let Some(rbrace_pos) = self.consume_closing_brace(&arg) {
282 if self.is_source_literal {
283 let lbrace_byte_pos = self.to_span_index(pos);
284 let rbrace_byte_pos = self.to_span_index(rbrace_pos);
285
286 let width = self.to_span_width(rbrace_pos);
287
288 self.arg_places.push(
289 lbrace_byte_pos.to(InnerOffset(rbrace_byte_pos.0 + width)),
290 );
291 }
292 } else if let Some(&(_, maybe)) = self.cur.peek() {
293 match maybe {
294 '?' => self.suggest_format_debug(),
295 '<' | '^' | '>' => self.suggest_format_align(maybe),
296 _ => self.suggest_positional_arg_instead_of_captured_arg(arg),
297 }
298 }
299 Some(Piece::NextArgument(Box::new(arg)))
300 }
301 }
302 '}' => {
303 self.cur.next();
304 if self.consume('}') {
305 Some(Piece::Lit(self.string(pos + 1)))
306 } else {
307 let err_pos = self.to_span_index(pos);
308 self.err_with_note(
309 "unmatched `}` found",
310 "unmatched `}`",
311 "if you intended to print `}`, you can escape it using `}}`",
312 err_pos.to(err_pos),
313 );
314 None
315 }
316 }
317 _ => Some(Piece::Lit(self.string(pos))),
318 }
319 } else {
320 if self.is_source_literal {
321 let span = self.span(self.cur_line_start, self.input.len());
322 if self.line_spans.last() != Some(&span) {
323 self.line_spans.push(span);
324 }
325 }
326 None
327 }
328 }
329}
330
331impl<'a> Parser<'a> {
332 pub fn new(
334 s: &'a str,
335 style: Option<usize>,
336 snippet: Option<String>,
337 append_newline: bool,
338 mode: ParseMode,
339 ) -> Parser<'a> {
340 let input_string_kind = find_width_map_from_snippet(s, snippet, style);
341 let (width_map, is_source_literal) = match input_string_kind {
342 InputStringKind::Literal { width_mappings } => (width_mappings, true),
343 InputStringKind::NotALiteral => (Vec::new(), false),
344 };
345
346 Parser {
347 mode,
348 input: s,
349 cur: s.char_indices().peekable(),
350 errors: vec![],
351 curarg: 0,
352 style,
353 arg_places: vec![],
354 width_map,
355 last_opening_brace: None,
356 append_newline,
357 is_source_literal,
358 cur_line_start: 0,
359 line_spans: vec![],
360 }
361 }
362
363 fn err(&mut self, description: impl Into<String>, label: impl Into<String>, span: InnerSpan) {
367 self.errors.push(ParseError {
368 description: description.into(),
369 note: None,
370 label: label.into(),
371 span,
372 secondary_label: None,
373 suggestion: Suggestion::None,
374 });
375 }
376
377 fn err_with_note(
381 &mut self,
382 description: impl Into<String>,
383 label: impl Into<String>,
384 note: impl Into<String>,
385 span: InnerSpan,
386 ) {
387 self.errors.push(ParseError {
388 description: description.into(),
389 note: Some(note.into()),
390 label: label.into(),
391 span,
392 secondary_label: None,
393 suggestion: Suggestion::None,
394 });
395 }
396
397 fn consume(&mut self, c: char) -> bool {
401 self.consume_pos(c).is_some()
402 }
403
404 fn consume_pos(&mut self, c: char) -> Option<usize> {
409 if let Some(&(pos, maybe)) = self.cur.peek() {
410 if c == maybe {
411 self.cur.next();
412 return Some(pos);
413 }
414 }
415 None
416 }
417
418 fn remap_pos(&self, mut pos: usize) -> InnerOffset {
419 for width in &self.width_map {
420 if pos > width.position {
421 pos += width.before - width.after;
422 } else if pos == width.position && width.after == 0 {
423 pos += width.before;
424 } else {
425 break;
426 }
427 }
428
429 InnerOffset(pos)
430 }
431
432 fn to_span_index(&self, pos: usize) -> InnerOffset {
433 let raw = self.style.map_or(0, |raw| raw + 1);
436 let pos = self.remap_pos(pos);
437 InnerOffset(raw + pos.0 + 1)
438 }
439
440 fn to_span_width(&self, pos: usize) -> usize {
441 let pos = self.remap_pos(pos);
442 match self.width_map.iter().find(|w| w.position == pos.0) {
443 Some(w) => w.before,
444 None => 1,
445 }
446 }
447
448 fn span(&self, start_pos: usize, end_pos: usize) -> InnerSpan {
449 let start = self.to_span_index(start_pos);
450 let end = self.to_span_index(end_pos);
451 start.to(end)
452 }
453
454 fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<usize> {
457 self.ws();
458
459 let pos;
460 let description;
461
462 if let Some(&(peek_pos, maybe)) = self.cur.peek() {
463 if maybe == '}' {
464 self.cur.next();
465 return Some(peek_pos);
466 }
467
468 pos = peek_pos;
469 description = format!("expected `}}`, found `{}`", maybe.escape_debug());
470 } else {
471 description = "expected `}` but string was terminated".to_owned();
472 pos = self.input.len() - if self.append_newline { 1 } else { 0 };
474 }
475
476 let pos = self.to_span_index(pos);
477
478 let label = "expected `}`".to_owned();
479 let (note, secondary_label) = if arg.format.fill == Some('}') {
480 (
481 Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
482 arg.format.fill_span.map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
483 )
484 } else {
485 (
486 Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
487 self.last_opening_brace.map(|sp| ("because of this opening brace".to_owned(), sp)),
488 )
489 };
490
491 self.errors.push(ParseError {
492 description,
493 note,
494 label,
495 span: pos.to(pos),
496 secondary_label,
497 suggestion: Suggestion::None,
498 });
499
500 None
501 }
502
503 fn ws(&mut self) {
505 while let Some(_) = self.cur.next_if(|&(_, c)| c.is_whitespace()) {}
506 }
507
508 fn string(&mut self, start: usize) -> &'a str {
511 while let Some(&(pos, c)) = self.cur.peek() {
513 match c {
514 '{' | '}' => {
515 return &self.input[start..pos];
516 }
517 '\n' if self.is_source_literal => {
518 self.line_spans.push(self.span(self.cur_line_start, pos));
519 self.cur_line_start = pos + 1;
520 self.cur.next();
521 }
522 _ => {
523 if self.is_source_literal && pos == self.cur_line_start && c.is_whitespace() {
524 self.cur_line_start = pos + c.len_utf8();
525 }
526 self.cur.next();
527 }
528 }
529 }
530 &self.input[start..]
531 }
532
533 fn argument(&mut self, start: InnerOffset) -> Argument<'a> {
535 let pos = self.position();
536
537 let end = self
538 .cur
539 .clone()
540 .find(|(_, ch)| !ch.is_whitespace())
541 .map_or(start, |(end, _)| self.to_span_index(end));
542 let position_span = start.to(end);
543
544 let format = match self.mode {
545 ParseMode::Format => self.format(),
546 ParseMode::InlineAsm => self.inline_asm(),
547 };
548
549 let pos = match pos {
551 Some(position) => position,
552 None => {
553 let i = self.curarg;
554 self.curarg += 1;
555 ArgumentImplicitlyIs(i)
556 }
557 };
558
559 Argument { position: pos, position_span, format }
560 }
561
562 fn position(&mut self) -> Option<Position<'a>> {
567 if let Some(i) = self.integer() {
568 Some(ArgumentIs(i))
569 } else {
570 match self.cur.peek() {
571 Some(&(lo, c)) if rustc_lexer::is_id_start(c) => {
572 let word = self.word();
573
574 if word == "r" {
577 if let Some((pos, '#')) = self.cur.peek() {
578 if self.input[pos + 1..]
579 .chars()
580 .next()
581 .is_some_and(rustc_lexer::is_id_start)
582 {
583 self.cur.next();
584 let word = self.word();
585 let prefix_span = self.span(lo, lo + 2);
586 let full_span = self.span(lo, lo + 2 + word.len());
587 self.errors.insert(0, ParseError {
588 description: "raw identifiers are not supported".to_owned(),
589 note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
590 label: "raw identifier used here".to_owned(),
591 span: full_span,
592 secondary_label: None,
593 suggestion: Suggestion::RemoveRawIdent(prefix_span),
594 });
595 return Some(ArgumentNamed(word));
596 }
597 }
598 }
599
600 Some(ArgumentNamed(word))
601 }
602
603 _ => None,
607 }
608 }
609 }
610
611 fn current_pos(&mut self) -> usize {
612 if let Some(&(pos, _)) = self.cur.peek() { pos } else { self.input.len() }
613 }
614
615 fn format(&mut self) -> FormatSpec<'a> {
618 let mut spec = FormatSpec {
619 fill: None,
620 fill_span: None,
621 align: AlignUnknown,
622 sign: None,
623 alternate: false,
624 zero_pad: false,
625 debug_hex: None,
626 precision: CountImplied,
627 precision_span: None,
628 width: CountImplied,
629 width_span: None,
630 ty: &self.input[..0],
631 ty_span: None,
632 };
633 if !self.consume(':') {
634 return spec;
635 }
636
637 if let Some(&(idx, c)) = self.cur.peek() {
639 if let Some((_, '>' | '<' | '^')) = self.cur.clone().nth(1) {
640 spec.fill = Some(c);
641 spec.fill_span = Some(self.span(idx, idx + 1));
642 self.cur.next();
643 }
644 }
645 if self.consume('<') {
647 spec.align = AlignLeft;
648 } else if self.consume('>') {
649 spec.align = AlignRight;
650 } else if self.consume('^') {
651 spec.align = AlignCenter;
652 }
653 if self.consume('+') {
655 spec.sign = Some(Sign::Plus);
656 } else if self.consume('-') {
657 spec.sign = Some(Sign::Minus);
658 }
659 if self.consume('#') {
661 spec.alternate = true;
662 }
663 let mut havewidth = false;
665
666 if self.consume('0') {
667 if let Some(end) = self.consume_pos('$') {
672 spec.width = CountIsParam(0);
673 spec.width_span = Some(self.span(end - 1, end + 1));
674 havewidth = true;
675 } else {
676 spec.zero_pad = true;
677 }
678 }
679
680 if !havewidth {
681 let start = self.current_pos();
682 spec.width = self.count(start);
683 if spec.width != CountImplied {
684 let end = self.current_pos();
685 spec.width_span = Some(self.span(start, end));
686 }
687 }
688
689 if let Some(start) = self.consume_pos('.') {
690 if self.consume('*') {
691 let i = self.curarg;
694 self.curarg += 1;
695 spec.precision = CountIsStar(i);
696 } else {
697 spec.precision = self.count(start + 1);
698 }
699 let end = self.current_pos();
700 spec.precision_span = Some(self.span(start, end));
701 }
702
703 let ty_span_start = self.current_pos();
704 if self.consume('x') {
706 if self.consume('?') {
707 spec.debug_hex = Some(DebugHex::Lower);
708 spec.ty = "?";
709 } else {
710 spec.ty = "x";
711 }
712 } else if self.consume('X') {
713 if self.consume('?') {
714 spec.debug_hex = Some(DebugHex::Upper);
715 spec.ty = "?";
716 } else {
717 spec.ty = "X";
718 }
719 } else if self.consume('?') {
720 spec.ty = "?";
721 if let Some(&(_, maybe)) = self.cur.peek() {
722 match maybe {
723 '#' | 'x' | 'X' => self.suggest_format_parameter(maybe),
724 _ => (),
725 }
726 }
727 } else {
728 spec.ty = self.word();
729 if !spec.ty.is_empty() {
730 let ty_span_end = self.current_pos();
731 spec.ty_span = Some(self.span(ty_span_start, ty_span_end));
732 }
733 }
734 spec
735 }
736
737 fn inline_asm(&mut self) -> FormatSpec<'a> {
740 let mut spec = FormatSpec {
741 fill: None,
742 fill_span: None,
743 align: AlignUnknown,
744 sign: None,
745 alternate: false,
746 zero_pad: false,
747 debug_hex: None,
748 precision: CountImplied,
749 precision_span: None,
750 width: CountImplied,
751 width_span: None,
752 ty: &self.input[..0],
753 ty_span: None,
754 };
755 if !self.consume(':') {
756 return spec;
757 }
758
759 let ty_span_start = self.current_pos();
760 spec.ty = self.word();
761 if !spec.ty.is_empty() {
762 let ty_span_end = self.current_pos();
763 spec.ty_span = Some(self.span(ty_span_start, ty_span_end));
764 }
765
766 spec
767 }
768
769 fn count(&mut self, start: usize) -> Count<'a> {
773 if let Some(i) = self.integer() {
774 if self.consume('$') { CountIsParam(i) } else { CountIs(i) }
775 } else {
776 let tmp = self.cur.clone();
777 let word = self.word();
778 if word.is_empty() {
779 self.cur = tmp;
780 CountImplied
781 } else if let Some(end) = self.consume_pos('$') {
782 let name_span = self.span(start, end);
783 CountIsName(word, name_span)
784 } else {
785 self.cur = tmp;
786 CountImplied
787 }
788 }
789 }
790
791 fn word(&mut self) -> &'a str {
794 let start = match self.cur.peek() {
795 Some(&(pos, c)) if rustc_lexer::is_id_start(c) => {
796 self.cur.next();
797 pos
798 }
799 _ => {
800 return "";
801 }
802 };
803 let mut end = None;
804 while let Some(&(pos, c)) = self.cur.peek() {
805 if rustc_lexer::is_id_continue(c) {
806 self.cur.next();
807 } else {
808 end = Some(pos);
809 break;
810 }
811 }
812 let end = end.unwrap_or(self.input.len());
813 let word = &self.input[start..end];
814 if word == "_" {
815 self.err_with_note(
816 "invalid argument name `_`",
817 "invalid argument name",
818 "argument name cannot be a single underscore",
819 self.span(start, end),
820 );
821 }
822 word
823 }
824
825 fn integer(&mut self) -> Option<usize> {
826 let mut cur: usize = 0;
827 let mut found = false;
828 let mut overflow = false;
829 let start = self.current_pos();
830 while let Some(&(_, c)) = self.cur.peek() {
831 if let Some(i) = c.to_digit(10) {
832 let (tmp, mul_overflow) = cur.overflowing_mul(10);
833 let (tmp, add_overflow) = tmp.overflowing_add(i as usize);
834 if mul_overflow || add_overflow {
835 overflow = true;
836 }
837 cur = tmp;
838 found = true;
839 self.cur.next();
840 } else {
841 break;
842 }
843 }
844
845 if overflow {
846 let end = self.current_pos();
847 let overflowed_int = &self.input[start..end];
848 self.err(
849 format!(
850 "integer `{}` does not fit into the type `usize` whose range is `0..={}`",
851 overflowed_int,
852 usize::MAX
853 ),
854 "integer out of range for `usize`",
855 self.span(start, end),
856 );
857 }
858
859 found.then_some(cur)
860 }
861
862 fn suggest_format_debug(&mut self) {
863 if let (Some(pos), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
864 let word = self.word();
865 let pos = self.to_span_index(pos);
866 self.errors.insert(
867 0,
868 ParseError {
869 description: "expected format parameter to occur after `:`".to_owned(),
870 note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
871 label: "expected `?` to occur after `:`".to_owned(),
872 span: pos.to(pos),
873 secondary_label: None,
874 suggestion: Suggestion::None,
875 },
876 );
877 }
878 }
879
880 fn suggest_format_align(&mut self, alignment: char) {
881 if let Some(pos) = self.consume_pos(alignment) {
882 let pos = self.to_span_index(pos);
883 self.errors.insert(
884 0,
885 ParseError {
886 description: "expected format parameter to occur after `:`".to_owned(),
887 note: None,
888 label: format!("expected `{}` to occur after `:`", alignment),
889 span: pos.to(pos),
890 secondary_label: None,
891 suggestion: Suggestion::None,
892 },
893 );
894 }
895 }
896
897 fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) {
898 if let Some(end) = self.consume_pos('.') {
899 let byte_pos = self.to_span_index(end);
900 let start = InnerOffset(byte_pos.0 + 1);
901 let field = self.argument(start);
902 if !self.consume('}') {
905 return;
906 }
907 if let ArgumentNamed(_) = arg.position {
908 match field.position {
909 ArgumentNamed(_) => {
910 self.errors.insert(
911 0,
912 ParseError {
913 description: "field access isn't supported".to_string(),
914 note: None,
915 label: "not supported".to_string(),
916 span: InnerSpan::new(
917 arg.position_span.start,
918 field.position_span.end,
919 ),
920 secondary_label: None,
921 suggestion: Suggestion::UsePositional,
922 },
923 );
924 }
925 ArgumentIs(_) => {
926 self.errors.insert(
927 0,
928 ParseError {
929 description: "tuple index access isn't supported".to_string(),
930 note: None,
931 label: "not supported".to_string(),
932 span: InnerSpan::new(
933 arg.position_span.start,
934 field.position_span.end,
935 ),
936 secondary_label: None,
937 suggestion: Suggestion::UsePositional,
938 },
939 );
940 }
941 _ => {}
942 };
943 }
944 }
945 }
946
947 fn suggest_format_parameter(&mut self, c: char) {
948 let replacement = match c {
949 '#' => "#?",
950 'x' => "x?",
951 'X' => "X?",
952 _ => return,
953 };
954 let Some(pos) = self.consume_pos(c) else {
955 return;
956 };
957
958 let span = self.span(pos - 1, pos + 1);
959 let pos = self.to_span_index(pos);
960
961 self.errors.insert(
962 0,
963 ParseError {
964 description: format!("expected `}}`, found `{c}`"),
965 note: None,
966 label: "expected `'}'`".into(),
967 span: pos.to(pos),
968 secondary_label: None,
969 suggestion: Suggestion::ReorderFormatParameter(span, format!("{replacement}")),
970 },
971 )
972 }
973}
974
975fn find_width_map_from_snippet(
979 input: &str,
980 snippet: Option<String>,
981 str_style: Option<usize>,
982) -> InputStringKind {
983 let snippet = match snippet {
984 Some(ref s) if s.starts_with('"') || s.starts_with("r\"") || s.starts_with("r#") => s,
985 _ => return InputStringKind::NotALiteral,
986 };
987
988 if str_style.is_some() {
989 return InputStringKind::Literal { width_mappings: Vec::new() };
990 }
991
992 let snippet = &snippet[1..snippet.len() - 1];
994
995 let input_no_nl = input.trim_end_matches('\n');
1001 let Some(unescaped) = unescape_string(snippet) else {
1002 return InputStringKind::NotALiteral;
1003 };
1004
1005 let unescaped_no_nl = unescaped.trim_end_matches('\n');
1006
1007 if unescaped_no_nl != input_no_nl {
1008 return InputStringKind::NotALiteral;
1011 }
1012
1013 let mut s = snippet.char_indices();
1014 let mut width_mappings = vec![];
1015 while let Some((pos, c)) = s.next() {
1016 match (c, s.clone().next()) {
1017 ('\\', Some((_, '\n'))) => {
1019 let _ = s.next();
1020 let mut width = 2;
1021
1022 while let Some((_, c)) = s.clone().next() {
1023 if matches!(c, ' ' | '\n' | '\t') {
1024 width += 1;
1025 let _ = s.next();
1026 } else {
1027 break;
1028 }
1029 }
1030
1031 width_mappings.push(InnerWidthMapping::new(pos, width, 0));
1032 }
1033 ('\\', Some((_, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
1034 width_mappings.push(InnerWidthMapping::new(pos, 2, 1));
1035 let _ = s.next();
1036 }
1037 ('\\', Some((_, 'x'))) => {
1038 s.nth(2);
1040 width_mappings.push(InnerWidthMapping::new(pos, 4, 1));
1041 }
1042 ('\\', Some((_, 'u'))) => {
1043 let mut width = 2;
1044 let _ = s.next();
1045
1046 if let Some((_, next_c)) = s.next() {
1047 if next_c == '{' {
1048 let digits_len =
1050 s.clone().take(6).take_while(|(_, c)| c.is_ascii_hexdigit()).count();
1051
1052 let len_utf8 = s
1053 .as_str()
1054 .get(..digits_len)
1055 .and_then(|digits| u32::from_str_radix(digits, 16).ok())
1056 .and_then(char::from_u32)
1057 .map_or(1, char::len_utf8);
1058
1059 let required_skips = digits_len.saturating_sub(len_utf8.saturating_sub(1));
1064
1065 width += required_skips + 2;
1067
1068 s.nth(digits_len);
1069 } else if next_c.is_ascii_hexdigit() {
1070 width += 1;
1071
1072 let mut i = 0; while let (Some((_, c)), _) = (s.next(), i < 6) {
1076 if c.is_ascii_hexdigit() {
1077 width += 1;
1078 } else {
1079 break;
1080 }
1081 i += 1;
1082 }
1083 }
1084 }
1085
1086 width_mappings.push(InnerWidthMapping::new(pos, width, 1));
1087 }
1088 _ => {}
1089 }
1090 }
1091
1092 InputStringKind::Literal { width_mappings }
1093}
1094
1095fn unescape_string(string: &str) -> Option<String> {
1096 let mut buf = String::new();
1097 let mut ok = true;
1098 unescape::unescape_unicode(string, unescape::Mode::Str, &mut |_, unescaped_char| {
1099 match unescaped_char {
1100 Ok(c) => buf.push(c),
1101 Err(_) => ok = false,
1102 }
1103 });
1104
1105 ok.then_some(buf)
1106}
1107
1108#[cfg(target_pointer_width = "64")]
1110rustc_index::static_assert_size!(Piece<'_>, 16);
1111
1112#[cfg(test)]
1113mod tests;