1 //! Macro support for format strings
2 //!
3 //! These structures are used when parsing format strings for the compiler.
4 //! Parsing does not happen at runtime: structures of `std::fmt::rt` are
5 //! generated instead.
6
7 #![doc(
8 html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/",
9 html_playground_url = "https://play.rust-lang.org/",
10 test(attr(deny(warnings)))
11 )]
12 #![deny(rustc::untranslatable_diagnostic)]
13 #![deny(rustc::diagnostic_outside_of_impl)]
14 // We want to be able to build this crate with a stable compiler, so no
15 // `#![feature]` attributes should be added.
16
17 use rustc_lexer::unescape;
18 pub use Alignment::*;
19 pub use Count::*;
20 pub use Piece::*;
21 pub use Position::*;
22
23 use std::iter;
24 use std::str;
25 use std::string;
26
27 // Note: copied from rustc_span
28 /// Range inside of a `Span` used for diagnostics when we only have access to relative positions.
29 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
30 pub struct InnerSpan {
31 pub start: usize,
32 pub end: usize,
33 }
34
35 impl InnerSpan {
new(start: usize, end: usize) -> InnerSpan36 pub fn new(start: usize, end: usize) -> InnerSpan {
37 InnerSpan { start, end }
38 }
39 }
40
41 /// The location and before/after width of a character whose width has changed from its source code
42 /// representation
43 #[derive(Copy, Clone, PartialEq, Eq)]
44 pub struct InnerWidthMapping {
45 /// Index of the character in the source
46 pub position: usize,
47 /// The inner width in characters
48 pub before: usize,
49 /// The transformed width in characters
50 pub after: usize,
51 }
52
53 impl InnerWidthMapping {
new(position: usize, before: usize, after: usize) -> InnerWidthMapping54 pub fn new(position: usize, before: usize, after: usize) -> InnerWidthMapping {
55 InnerWidthMapping { position, before, after }
56 }
57 }
58
59 /// Whether the input string is a literal. If yes, it contains the inner width mappings.
60 #[derive(Clone, PartialEq, Eq)]
61 enum InputStringKind {
62 NotALiteral,
63 Literal { width_mappings: Vec<InnerWidthMapping> },
64 }
65
66 /// The type of format string that we are parsing.
67 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
68 pub enum ParseMode {
69 /// A normal format string as per `format_args!`.
70 Format,
71 /// An inline assembly template string for `asm!`.
72 InlineAsm,
73 }
74
75 #[derive(Copy, Clone)]
76 struct InnerOffset(usize);
77
78 impl InnerOffset {
to(self, end: InnerOffset) -> InnerSpan79 fn to(self, end: InnerOffset) -> InnerSpan {
80 InnerSpan::new(self.0, end.0)
81 }
82 }
83
84 /// A piece is a portion of the format string which represents the next part
85 /// to emit. These are emitted as a stream by the `Parser` class.
86 #[derive(Clone, Debug, PartialEq)]
87 pub enum Piece<'a> {
88 /// A literal string which should directly be emitted
89 String(&'a str),
90 /// This describes that formatting should process the next argument (as
91 /// specified inside) for emission.
92 NextArgument(Box<Argument<'a>>),
93 }
94
95 /// Representation of an argument specification.
96 #[derive(Copy, Clone, Debug, PartialEq)]
97 pub struct Argument<'a> {
98 /// Where to find this argument
99 pub position: Position<'a>,
100 /// The span of the position indicator. Includes any whitespace in implicit
101 /// positions (`{ }`).
102 pub position_span: InnerSpan,
103 /// How to format the argument
104 pub format: FormatSpec<'a>,
105 }
106
107 /// Specification for the formatting of an argument in the format string.
108 #[derive(Copy, Clone, Debug, PartialEq)]
109 pub struct FormatSpec<'a> {
110 /// Optionally specified character to fill alignment with.
111 pub fill: Option<char>,
112 /// Optionally specified alignment.
113 pub align: Alignment,
114 /// The `+` or `-` flag.
115 pub sign: Option<Sign>,
116 /// The `#` flag.
117 pub alternate: bool,
118 /// The `0` flag.
119 pub zero_pad: bool,
120 /// The `x` or `X` flag. (Only for `Debug`.)
121 pub debug_hex: Option<DebugHex>,
122 /// The integer precision to use.
123 pub precision: Count<'a>,
124 /// The span of the precision formatting flag (for diagnostics).
125 pub precision_span: Option<InnerSpan>,
126 /// The string width requested for the resulting format.
127 pub width: Count<'a>,
128 /// The span of the width formatting flag (for diagnostics).
129 pub width_span: Option<InnerSpan>,
130 /// The descriptor string representing the name of the format desired for
131 /// this argument, this can be empty or any number of characters, although
132 /// it is required to be one word.
133 pub ty: &'a str,
134 /// The span of the descriptor string (for diagnostics).
135 pub ty_span: Option<InnerSpan>,
136 }
137
138 /// Enum describing where an argument for a format can be located.
139 #[derive(Copy, Clone, Debug, PartialEq)]
140 pub enum Position<'a> {
141 /// The argument is implied to be located at an index
142 ArgumentImplicitlyIs(usize),
143 /// The argument is located at a specific index given in the format,
144 ArgumentIs(usize),
145 /// The argument has a name.
146 ArgumentNamed(&'a str),
147 }
148
149 impl Position<'_> {
index(&self) -> Option<usize>150 pub fn index(&self) -> Option<usize> {
151 match self {
152 ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
153 _ => None,
154 }
155 }
156 }
157
158 /// Enum of alignments which are supported.
159 #[derive(Copy, Clone, Debug, PartialEq)]
160 pub enum Alignment {
161 /// The value will be aligned to the left.
162 AlignLeft,
163 /// The value will be aligned to the right.
164 AlignRight,
165 /// The value will be aligned in the center.
166 AlignCenter,
167 /// The value will take on a default alignment.
168 AlignUnknown,
169 }
170
171 /// Enum for the sign flags.
172 #[derive(Copy, Clone, Debug, PartialEq)]
173 pub enum Sign {
174 /// The `+` flag.
175 Plus,
176 /// The `-` flag.
177 Minus,
178 }
179
180 /// Enum for the debug hex flags.
181 #[derive(Copy, Clone, Debug, PartialEq)]
182 pub enum DebugHex {
183 /// The `x` flag in `{:x?}`.
184 Lower,
185 /// The `X` flag in `{:X?}`.
186 Upper,
187 }
188
189 /// A count is used for the precision and width parameters of an integer, and
190 /// can reference either an argument or a literal integer.
191 #[derive(Copy, Clone, Debug, PartialEq)]
192 pub enum Count<'a> {
193 /// The count is specified explicitly.
194 CountIs(usize),
195 /// The count is specified by the argument with the given name.
196 CountIsName(&'a str, InnerSpan),
197 /// The count is specified by the argument at the given index.
198 CountIsParam(usize),
199 /// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index.
200 CountIsStar(usize),
201 /// The count is implied and cannot be explicitly specified.
202 CountImplied,
203 }
204
205 pub struct ParseError {
206 pub description: string::String,
207 pub note: Option<string::String>,
208 pub label: string::String,
209 pub span: InnerSpan,
210 pub secondary_label: Option<(string::String, InnerSpan)>,
211 pub should_be_replaced_with_positional_argument: bool,
212 }
213
214 /// The parser structure for interpreting the input format string. This is
215 /// modeled as an iterator over `Piece` structures to form a stream of tokens
216 /// being output.
217 ///
218 /// This is a recursive-descent parser for the sake of simplicity, and if
219 /// necessary there's probably lots of room for improvement performance-wise.
220 pub struct Parser<'a> {
221 mode: ParseMode,
222 input: &'a str,
223 cur: iter::Peekable<str::CharIndices<'a>>,
224 /// Error messages accumulated during parsing
225 pub errors: Vec<ParseError>,
226 /// Current position of implicit positional argument pointer
227 pub curarg: usize,
228 /// `Some(raw count)` when the string is "raw", used to position spans correctly
229 style: Option<usize>,
230 /// Start and end byte offset of every successfully parsed argument
231 pub arg_places: Vec<InnerSpan>,
232 /// Characters whose length has been changed from their in-code representation
233 width_map: Vec<InnerWidthMapping>,
234 /// Span of the last opening brace seen, used for error reporting
235 last_opening_brace: Option<InnerSpan>,
236 /// Whether the source string is comes from `println!` as opposed to `format!` or `print!`
237 append_newline: bool,
238 /// Whether this formatting string was written directly in the source. This controls whether we
239 /// can use spans to refer into it and give better error messages.
240 /// N.B: This does _not_ control whether implicit argument captures can be used.
241 pub is_source_literal: bool,
242 /// Start position of the current line.
243 cur_line_start: usize,
244 /// Start and end byte offset of every line of the format string. Excludes
245 /// newline characters and leading whitespace.
246 pub line_spans: Vec<InnerSpan>,
247 }
248
249 impl<'a> Iterator for Parser<'a> {
250 type Item = Piece<'a>;
251
next(&mut self) -> Option<Piece<'a>>252 fn next(&mut self) -> Option<Piece<'a>> {
253 if let Some(&(pos, c)) = self.cur.peek() {
254 match c {
255 '{' => {
256 let curr_last_brace = self.last_opening_brace;
257 let byte_pos = self.to_span_index(pos);
258 let lbrace_end = InnerOffset(byte_pos.0 + self.to_span_width(pos));
259 self.last_opening_brace = Some(byte_pos.to(lbrace_end));
260 self.cur.next();
261 if self.consume('{') {
262 self.last_opening_brace = curr_last_brace;
263
264 Some(String(self.string(pos + 1)))
265 } else {
266 let arg = self.argument(lbrace_end);
267 if let Some(rbrace_pos) = self.must_consume('}') {
268 if self.is_source_literal {
269 let lbrace_byte_pos = self.to_span_index(pos);
270 let rbrace_byte_pos = self.to_span_index(rbrace_pos);
271
272 let width = self.to_span_width(rbrace_pos);
273
274 self.arg_places.push(
275 lbrace_byte_pos.to(InnerOffset(rbrace_byte_pos.0 + width)),
276 );
277 }
278 } else {
279 if let Some(&(_, maybe)) = self.cur.peek() {
280 if maybe == '?' {
281 self.suggest_format();
282 } else {
283 self.suggest_positional_arg_instead_of_captured_arg(arg);
284 }
285 }
286 }
287 Some(NextArgument(Box::new(arg)))
288 }
289 }
290 '}' => {
291 self.cur.next();
292 if self.consume('}') {
293 Some(String(self.string(pos + 1)))
294 } else {
295 let err_pos = self.to_span_index(pos);
296 self.err_with_note(
297 "unmatched `}` found",
298 "unmatched `}`",
299 "if you intended to print `}`, you can escape it using `}}`",
300 err_pos.to(err_pos),
301 );
302 None
303 }
304 }
305 _ => Some(String(self.string(pos))),
306 }
307 } else {
308 if self.is_source_literal {
309 let span = self.span(self.cur_line_start, self.input.len());
310 if self.line_spans.last() != Some(&span) {
311 self.line_spans.push(span);
312 }
313 }
314 None
315 }
316 }
317 }
318
319 impl<'a> Parser<'a> {
320 /// Creates a new parser for the given format string
new( s: &'a str, style: Option<usize>, snippet: Option<string::String>, append_newline: bool, mode: ParseMode, ) -> Parser<'a>321 pub fn new(
322 s: &'a str,
323 style: Option<usize>,
324 snippet: Option<string::String>,
325 append_newline: bool,
326 mode: ParseMode,
327 ) -> Parser<'a> {
328 let input_string_kind = find_width_map_from_snippet(s, snippet, style);
329 let (width_map, is_source_literal) = match input_string_kind {
330 InputStringKind::Literal { width_mappings } => (width_mappings, true),
331 InputStringKind::NotALiteral => (Vec::new(), false),
332 };
333
334 Parser {
335 mode,
336 input: s,
337 cur: s.char_indices().peekable(),
338 errors: vec![],
339 curarg: 0,
340 style,
341 arg_places: vec![],
342 width_map,
343 last_opening_brace: None,
344 append_newline,
345 is_source_literal,
346 cur_line_start: 0,
347 line_spans: vec![],
348 }
349 }
350
351 /// Notifies of an error. The message doesn't actually need to be of type
352 /// String, but I think it does when this eventually uses conditions so it
353 /// might as well start using it now.
err<S1: Into<string::String>, S2: Into<string::String>>( &mut self, description: S1, label: S2, span: InnerSpan, )354 fn err<S1: Into<string::String>, S2: Into<string::String>>(
355 &mut self,
356 description: S1,
357 label: S2,
358 span: InnerSpan,
359 ) {
360 self.errors.push(ParseError {
361 description: description.into(),
362 note: None,
363 label: label.into(),
364 span,
365 secondary_label: None,
366 should_be_replaced_with_positional_argument: false,
367 });
368 }
369
370 /// Notifies of an error. The message doesn't actually need to be of type
371 /// String, but I think it does when this eventually uses conditions so it
372 /// might as well start using it now.
err_with_note< S1: Into<string::String>, S2: Into<string::String>, S3: Into<string::String>, >( &mut self, description: S1, label: S2, note: S3, span: InnerSpan, )373 fn err_with_note<
374 S1: Into<string::String>,
375 S2: Into<string::String>,
376 S3: Into<string::String>,
377 >(
378 &mut self,
379 description: S1,
380 label: S2,
381 note: S3,
382 span: InnerSpan,
383 ) {
384 self.errors.push(ParseError {
385 description: description.into(),
386 note: Some(note.into()),
387 label: label.into(),
388 span,
389 secondary_label: None,
390 should_be_replaced_with_positional_argument: false,
391 });
392 }
393
394 /// Optionally consumes the specified character. If the character is not at
395 /// the current position, then the current iterator isn't moved and `false` is
396 /// returned, otherwise the character is consumed and `true` is returned.
consume(&mut self, c: char) -> bool397 fn consume(&mut self, c: char) -> bool {
398 self.consume_pos(c).is_some()
399 }
400
401 /// Optionally consumes the specified character. If the character is not at
402 /// the current position, then the current iterator isn't moved and `None` is
403 /// returned, otherwise the character is consumed and the current position is
404 /// returned.
consume_pos(&mut self, c: char) -> Option<usize>405 fn consume_pos(&mut self, c: char) -> Option<usize> {
406 if let Some(&(pos, maybe)) = self.cur.peek() {
407 if c == maybe {
408 self.cur.next();
409 return Some(pos);
410 }
411 }
412 None
413 }
414
remap_pos(&self, mut pos: usize) -> InnerOffset415 fn remap_pos(&self, mut pos: usize) -> InnerOffset {
416 for width in &self.width_map {
417 if pos > width.position {
418 pos += width.before - width.after;
419 } else if pos == width.position && width.after == 0 {
420 pos += width.before;
421 } else {
422 break;
423 }
424 }
425
426 InnerOffset(pos)
427 }
428
to_span_index(&self, pos: usize) -> InnerOffset429 fn to_span_index(&self, pos: usize) -> InnerOffset {
430 // This handles the raw string case, the raw argument is the number of #
431 // in r###"..."### (we need to add one because of the `r`).
432 let raw = self.style.map_or(0, |raw| raw + 1);
433 let pos = self.remap_pos(pos);
434 InnerOffset(raw + pos.0 + 1)
435 }
436
to_span_width(&self, pos: usize) -> usize437 fn to_span_width(&self, pos: usize) -> usize {
438 let pos = self.remap_pos(pos);
439 match self.width_map.iter().find(|w| w.position == pos.0) {
440 Some(w) => w.before,
441 None => 1,
442 }
443 }
444
span(&self, start_pos: usize, end_pos: usize) -> InnerSpan445 fn span(&self, start_pos: usize, end_pos: usize) -> InnerSpan {
446 let start = self.to_span_index(start_pos);
447 let end = self.to_span_index(end_pos);
448 start.to(end)
449 }
450
451 /// Forces consumption of the specified character. If the character is not
452 /// found, an error is emitted.
must_consume(&mut self, c: char) -> Option<usize>453 fn must_consume(&mut self, c: char) -> Option<usize> {
454 self.ws();
455
456 if let Some(&(pos, maybe)) = self.cur.peek() {
457 if c == maybe {
458 self.cur.next();
459 Some(pos)
460 } else {
461 let pos = self.to_span_index(pos);
462 let description = format!("expected `'}}'`, found `{maybe:?}`");
463 let label = "expected `}`".to_owned();
464 let (note, secondary_label) = if c == '}' {
465 (
466 Some(
467 "if you intended to print `{`, you can escape it using `{{`".to_owned(),
468 ),
469 self.last_opening_brace
470 .map(|sp| ("because of this opening brace".to_owned(), sp)),
471 )
472 } else {
473 (None, None)
474 };
475 self.errors.push(ParseError {
476 description,
477 note,
478 label,
479 span: pos.to(pos),
480 secondary_label,
481 should_be_replaced_with_positional_argument: false,
482 });
483 None
484 }
485 } else {
486 let description = format!("expected `{c:?}` but string was terminated");
487 // point at closing `"`
488 let pos = self.input.len() - if self.append_newline { 1 } else { 0 };
489 let pos = self.to_span_index(pos);
490 if c == '}' {
491 let label = format!("expected `{c:?}`");
492 let (note, secondary_label) = if c == '}' {
493 (
494 Some(
495 "if you intended to print `{`, you can escape it using `{{`".to_owned(),
496 ),
497 self.last_opening_brace
498 .map(|sp| ("because of this opening brace".to_owned(), sp)),
499 )
500 } else {
501 (None, None)
502 };
503 self.errors.push(ParseError {
504 description,
505 note,
506 label,
507 span: pos.to(pos),
508 secondary_label,
509 should_be_replaced_with_positional_argument: false,
510 });
511 } else {
512 self.err(description, format!("expected `{c:?}`"), pos.to(pos));
513 }
514 None
515 }
516 }
517
518 /// Consumes all whitespace characters until the first non-whitespace character
ws(&mut self)519 fn ws(&mut self) {
520 while let Some(&(_, c)) = self.cur.peek() {
521 if c.is_whitespace() {
522 self.cur.next();
523 } else {
524 break;
525 }
526 }
527 }
528
529 /// Parses all of a string which is to be considered a "raw literal" in a
530 /// format string. This is everything outside of the braces.
string(&mut self, start: usize) -> &'a str531 fn string(&mut self, start: usize) -> &'a str {
532 // we may not consume the character, peek the iterator
533 while let Some(&(pos, c)) = self.cur.peek() {
534 match c {
535 '{' | '}' => {
536 return &self.input[start..pos];
537 }
538 '\n' if self.is_source_literal => {
539 self.line_spans.push(self.span(self.cur_line_start, pos));
540 self.cur_line_start = pos + 1;
541 self.cur.next();
542 }
543 _ => {
544 if self.is_source_literal && pos == self.cur_line_start && c.is_whitespace() {
545 self.cur_line_start = pos + c.len_utf8();
546 }
547 self.cur.next();
548 }
549 }
550 }
551 &self.input[start..self.input.len()]
552 }
553
554 /// Parses an `Argument` structure, or what's contained within braces inside the format string.
argument(&mut self, start: InnerOffset) -> Argument<'a>555 fn argument(&mut self, start: InnerOffset) -> Argument<'a> {
556 let pos = self.position();
557
558 let end = self
559 .cur
560 .clone()
561 .find(|(_, ch)| !ch.is_whitespace())
562 .map_or(start, |(end, _)| self.to_span_index(end));
563 let position_span = start.to(end);
564
565 let format = match self.mode {
566 ParseMode::Format => self.format(),
567 ParseMode::InlineAsm => self.inline_asm(),
568 };
569
570 // Resolve position after parsing format spec.
571 let pos = match pos {
572 Some(position) => position,
573 None => {
574 let i = self.curarg;
575 self.curarg += 1;
576 ArgumentImplicitlyIs(i)
577 }
578 };
579
580 Argument { position: pos, position_span, format }
581 }
582
583 /// Parses a positional argument for a format. This could either be an
584 /// integer index of an argument, a named argument, or a blank string.
585 /// Returns `Some(parsed_position)` if the position is not implicitly
586 /// consuming a macro argument, `None` if it's the case.
position(&mut self) -> Option<Position<'a>>587 fn position(&mut self) -> Option<Position<'a>> {
588 if let Some(i) = self.integer() {
589 Some(ArgumentIs(i))
590 } else {
591 match self.cur.peek() {
592 Some(&(_, c)) if rustc_lexer::is_id_start(c) => Some(ArgumentNamed(self.word())),
593
594 // This is an `ArgumentNext`.
595 // Record the fact and do the resolution after parsing the
596 // format spec, to make things like `{:.*}` work.
597 _ => None,
598 }
599 }
600 }
601
current_pos(&mut self) -> usize602 fn current_pos(&mut self) -> usize {
603 if let Some(&(pos, _)) = self.cur.peek() { pos } else { self.input.len() }
604 }
605
606 /// Parses a format specifier at the current position, returning all of the
607 /// relevant information in the `FormatSpec` struct.
format(&mut self) -> FormatSpec<'a>608 fn format(&mut self) -> FormatSpec<'a> {
609 let mut spec = FormatSpec {
610 fill: None,
611 align: AlignUnknown,
612 sign: None,
613 alternate: false,
614 zero_pad: false,
615 debug_hex: None,
616 precision: CountImplied,
617 precision_span: None,
618 width: CountImplied,
619 width_span: None,
620 ty: &self.input[..0],
621 ty_span: None,
622 };
623 if !self.consume(':') {
624 return spec;
625 }
626
627 // fill character
628 if let Some(&(_, c)) = self.cur.peek() {
629 if let Some((_, '>' | '<' | '^')) = self.cur.clone().nth(1) {
630 spec.fill = Some(c);
631 self.cur.next();
632 }
633 }
634 // Alignment
635 if self.consume('<') {
636 spec.align = AlignLeft;
637 } else if self.consume('>') {
638 spec.align = AlignRight;
639 } else if self.consume('^') {
640 spec.align = AlignCenter;
641 }
642 // Sign flags
643 if self.consume('+') {
644 spec.sign = Some(Sign::Plus);
645 } else if self.consume('-') {
646 spec.sign = Some(Sign::Minus);
647 }
648 // Alternate marker
649 if self.consume('#') {
650 spec.alternate = true;
651 }
652 // Width and precision
653 let mut havewidth = false;
654
655 if self.consume('0') {
656 // small ambiguity with '0$' as a format string. In theory this is a
657 // '0' flag and then an ill-formatted format string with just a '$'
658 // and no count, but this is better if we instead interpret this as
659 // no '0' flag and '0$' as the width instead.
660 if let Some(end) = self.consume_pos('$') {
661 spec.width = CountIsParam(0);
662 spec.width_span = Some(self.span(end - 1, end + 1));
663 havewidth = true;
664 } else {
665 spec.zero_pad = true;
666 }
667 }
668
669 if !havewidth {
670 let start = self.current_pos();
671 spec.width = self.count(start);
672 if spec.width != CountImplied {
673 let end = self.current_pos();
674 spec.width_span = Some(self.span(start, end));
675 }
676 }
677
678 if let Some(start) = self.consume_pos('.') {
679 if self.consume('*') {
680 // Resolve `CountIsNextParam`.
681 // We can do this immediately as `position` is resolved later.
682 let i = self.curarg;
683 self.curarg += 1;
684 spec.precision = CountIsStar(i);
685 } else {
686 spec.precision = self.count(start + 1);
687 }
688 let end = self.current_pos();
689 spec.precision_span = Some(self.span(start, end));
690 }
691
692 let ty_span_start = self.current_pos();
693 // Optional radix followed by the actual format specifier
694 if self.consume('x') {
695 if self.consume('?') {
696 spec.debug_hex = Some(DebugHex::Lower);
697 spec.ty = "?";
698 } else {
699 spec.ty = "x";
700 }
701 } else if self.consume('X') {
702 if self.consume('?') {
703 spec.debug_hex = Some(DebugHex::Upper);
704 spec.ty = "?";
705 } else {
706 spec.ty = "X";
707 }
708 } else if self.consume('?') {
709 spec.ty = "?";
710 } else {
711 spec.ty = self.word();
712 if !spec.ty.is_empty() {
713 let ty_span_end = self.current_pos();
714 spec.ty_span = Some(self.span(ty_span_start, ty_span_end));
715 }
716 }
717 spec
718 }
719
720 /// Parses an inline assembly template modifier at the current position, returning the modifier
721 /// in the `ty` field of the `FormatSpec` struct.
inline_asm(&mut self) -> FormatSpec<'a>722 fn inline_asm(&mut self) -> FormatSpec<'a> {
723 let mut spec = FormatSpec {
724 fill: None,
725 align: AlignUnknown,
726 sign: None,
727 alternate: false,
728 zero_pad: false,
729 debug_hex: None,
730 precision: CountImplied,
731 precision_span: None,
732 width: CountImplied,
733 width_span: None,
734 ty: &self.input[..0],
735 ty_span: None,
736 };
737 if !self.consume(':') {
738 return spec;
739 }
740
741 let ty_span_start = self.current_pos();
742 spec.ty = self.word();
743 if !spec.ty.is_empty() {
744 let ty_span_end = self.current_pos();
745 spec.ty_span = Some(self.span(ty_span_start, ty_span_end));
746 }
747
748 spec
749 }
750
751 /// Parses a `Count` parameter at the current position. This does not check
752 /// for 'CountIsNextParam' because that is only used in precision, not
753 /// width.
count(&mut self, start: usize) -> Count<'a>754 fn count(&mut self, start: usize) -> Count<'a> {
755 if let Some(i) = self.integer() {
756 if self.consume('$') { CountIsParam(i) } else { CountIs(i) }
757 } else {
758 let tmp = self.cur.clone();
759 let word = self.word();
760 if word.is_empty() {
761 self.cur = tmp;
762 CountImplied
763 } else if let Some(end) = self.consume_pos('$') {
764 let name_span = self.span(start, end);
765 CountIsName(word, name_span)
766 } else {
767 self.cur = tmp;
768 CountImplied
769 }
770 }
771 }
772
773 /// Parses a word starting at the current position. A word is the same as
774 /// Rust identifier, except that it can't start with `_` character.
word(&mut self) -> &'a str775 fn word(&mut self) -> &'a str {
776 let start = match self.cur.peek() {
777 Some(&(pos, c)) if rustc_lexer::is_id_start(c) => {
778 self.cur.next();
779 pos
780 }
781 _ => {
782 return "";
783 }
784 };
785 let mut end = None;
786 while let Some(&(pos, c)) = self.cur.peek() {
787 if rustc_lexer::is_id_continue(c) {
788 self.cur.next();
789 } else {
790 end = Some(pos);
791 break;
792 }
793 }
794 let end = end.unwrap_or(self.input.len());
795 let word = &self.input[start..end];
796 if word == "_" {
797 self.err_with_note(
798 "invalid argument name `_`",
799 "invalid argument name",
800 "argument name cannot be a single underscore",
801 self.span(start, end),
802 );
803 }
804 word
805 }
806
integer(&mut self) -> Option<usize>807 fn integer(&mut self) -> Option<usize> {
808 let mut cur: usize = 0;
809 let mut found = false;
810 let mut overflow = false;
811 let start = self.current_pos();
812 while let Some(&(_, c)) = self.cur.peek() {
813 if let Some(i) = c.to_digit(10) {
814 let (tmp, mul_overflow) = cur.overflowing_mul(10);
815 let (tmp, add_overflow) = tmp.overflowing_add(i as usize);
816 if mul_overflow || add_overflow {
817 overflow = true;
818 }
819 cur = tmp;
820 found = true;
821 self.cur.next();
822 } else {
823 break;
824 }
825 }
826
827 if overflow {
828 let end = self.current_pos();
829 let overflowed_int = &self.input[start..end];
830 self.err(
831 format!(
832 "integer `{}` does not fit into the type `usize` whose range is `0..={}`",
833 overflowed_int,
834 usize::MAX
835 ),
836 "integer out of range for `usize`",
837 self.span(start, end),
838 );
839 }
840
841 found.then_some(cur)
842 }
843
suggest_format(&mut self)844 fn suggest_format(&mut self) {
845 if let (Some(pos), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
846 let word = self.word();
847 let _end = self.current_pos();
848 let pos = self.to_span_index(pos);
849 self.errors.insert(
850 0,
851 ParseError {
852 description: "expected format parameter to occur after `:`".to_owned(),
853 note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
854 label: "expected `?` to occur after `:`".to_owned(),
855 span: pos.to(pos),
856 secondary_label: None,
857 should_be_replaced_with_positional_argument: false,
858 },
859 );
860 }
861 }
862
suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>)863 fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) {
864 if let Some(end) = self.consume_pos('.') {
865 let byte_pos = self.to_span_index(end);
866 let start = InnerOffset(byte_pos.0 + 1);
867 let field = self.argument(start);
868 // We can only parse `foo.bar` field access, any deeper nesting,
869 // or another type of expression, like method calls, are not supported
870 if !self.consume('}') {
871 return;
872 }
873 if let ArgumentNamed(_) = arg.position {
874 if let ArgumentNamed(_) = field.position {
875 self.errors.insert(
876 0,
877 ParseError {
878 description: "field access isn't supported".to_string(),
879 note: None,
880 label: "not supported".to_string(),
881 span: InnerSpan::new(arg.position_span.start, field.position_span.end),
882 secondary_label: None,
883 should_be_replaced_with_positional_argument: true,
884 },
885 );
886 }
887 }
888 }
889 }
890 }
891
892 /// Finds the indices of all characters that have been processed and differ between the actual
893 /// written code (code snippet) and the `InternedString` that gets processed in the `Parser`
894 /// in order to properly synthesise the intra-string `Span`s for error diagnostics.
find_width_map_from_snippet( input: &str, snippet: Option<string::String>, str_style: Option<usize>, ) -> InputStringKind895 fn find_width_map_from_snippet(
896 input: &str,
897 snippet: Option<string::String>,
898 str_style: Option<usize>,
899 ) -> InputStringKind {
900 let snippet = match snippet {
901 Some(ref s) if s.starts_with('"') || s.starts_with("r\"") || s.starts_with("r#") => s,
902 _ => return InputStringKind::NotALiteral,
903 };
904
905 if str_style.is_some() {
906 return InputStringKind::Literal { width_mappings: Vec::new() };
907 }
908
909 // Strip quotes.
910 let snippet = &snippet[1..snippet.len() - 1];
911
912 // Macros like `println` add a newline at the end. That technically doesn't make them "literals" anymore, but it's fine
913 // since we will never need to point our spans there, so we lie about it here by ignoring it.
914 // Since there might actually be newlines in the source code, we need to normalize away all trailing newlines.
915 // If we only trimmed it off the input, `format!("\n")` would cause a mismatch as here we they actually match up.
916 // Alternatively, we could just count the trailing newlines and only trim one from the input if they don't match up.
917 let input_no_nl = input.trim_end_matches('\n');
918 let Some(unescaped) = unescape_string(snippet) else {
919 return InputStringKind::NotALiteral;
920 };
921
922 let unescaped_no_nl = unescaped.trim_end_matches('\n');
923
924 if unescaped_no_nl != input_no_nl {
925 // The source string that we're pointing at isn't our input, so spans pointing at it will be incorrect.
926 // This can for example happen with proc macros that respan generated literals.
927 return InputStringKind::NotALiteral;
928 }
929
930 let mut s = snippet.char_indices();
931 let mut width_mappings = vec![];
932 while let Some((pos, c)) = s.next() {
933 match (c, s.clone().next()) {
934 // skip whitespace and empty lines ending in '\\'
935 ('\\', Some((_, '\n'))) => {
936 let _ = s.next();
937 let mut width = 2;
938
939 while let Some((_, c)) = s.clone().next() {
940 if matches!(c, ' ' | '\n' | '\t') {
941 width += 1;
942 let _ = s.next();
943 } else {
944 break;
945 }
946 }
947
948 width_mappings.push(InnerWidthMapping::new(pos, width, 0));
949 }
950 ('\\', Some((_, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
951 width_mappings.push(InnerWidthMapping::new(pos, 2, 1));
952 let _ = s.next();
953 }
954 ('\\', Some((_, 'x'))) => {
955 // consume `\xAB` literal
956 s.nth(2);
957 width_mappings.push(InnerWidthMapping::new(pos, 4, 1));
958 }
959 ('\\', Some((_, 'u'))) => {
960 let mut width = 2;
961 let _ = s.next();
962
963 if let Some((_, next_c)) = s.next() {
964 if next_c == '{' {
965 // consume up to 6 hexanumeric chars
966 let digits_len =
967 s.clone().take(6).take_while(|(_, c)| c.is_digit(16)).count();
968
969 let len_utf8 = s
970 .as_str()
971 .get(..digits_len)
972 .and_then(|digits| u32::from_str_radix(digits, 16).ok())
973 .and_then(char::from_u32)
974 .map_or(1, char::len_utf8);
975
976 // Skip the digits, for chars that encode to more than 1 utf-8 byte
977 // exclude as many digits as it is greater than 1 byte
978 //
979 // So for a 3 byte character, exclude 2 digits
980 let required_skips = digits_len.saturating_sub(len_utf8.saturating_sub(1));
981
982 // skip '{' and '}' also
983 width += required_skips + 2;
984
985 s.nth(digits_len);
986 } else if next_c.is_digit(16) {
987 width += 1;
988
989 // We suggest adding `{` and `}` when appropriate, accept it here as if
990 // it were correct
991 let mut i = 0; // consume up to 6 hexanumeric chars
992 while let (Some((_, c)), _) = (s.next(), i < 6) {
993 if c.is_digit(16) {
994 width += 1;
995 } else {
996 break;
997 }
998 i += 1;
999 }
1000 }
1001 }
1002
1003 width_mappings.push(InnerWidthMapping::new(pos, width, 1));
1004 }
1005 _ => {}
1006 }
1007 }
1008
1009 InputStringKind::Literal { width_mappings }
1010 }
1011
unescape_string(string: &str) -> Option<string::String>1012 fn unescape_string(string: &str) -> Option<string::String> {
1013 let mut buf = string::String::new();
1014 let mut ok = true;
1015 unescape::unescape_literal(string, unescape::Mode::Str, &mut |_, unescaped_char| {
1016 match unescaped_char {
1017 Ok(c) => buf.push(c),
1018 Err(_) => ok = false,
1019 }
1020 });
1021
1022 ok.then_some(buf)
1023 }
1024
1025 // Assert a reasonable size for `Piece`
1026 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
1027 rustc_data_structures::static_assert_size!(Piece<'_>, 16);
1028
1029 #[cfg(test)]
1030 mod tests;
1031