about summary refs log tree commit diff
path: root/compiler/rustc_parse_format
diff options
context:
space:
mode:
authorMarijn Schouten <mhkbst@gmail.com>2025-02-19 11:41:31 +0100
committerMarijn Schouten <mhkbst@gmail.com>2025-05-06 10:00:22 +0000
commit3d4737fb5e999ef66e57753e432f7f8076bc5192 (patch)
tree79432af4e9b453ec9e8743dc3385be11b6abdbd4 /compiler/rustc_parse_format
parent7295b08a17d1107155acd4b552069e3705b0ab1f (diff)
downloadrust-3d4737fb5e999ef66e57753e432f7f8076bc5192.tar.gz
rust-3d4737fb5e999ef66e57753e432f7f8076bc5192.zip
Remove duplicate impl of string unescape
Diffstat (limited to 'compiler/rustc_parse_format')
-rw-r--r--compiler/rustc_parse_format/src/lib.rs812
-rw-r--r--compiler/rustc_parse_format/src/tests.rs159
2 files changed, 435 insertions, 536 deletions
diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs
index 999e7159274..9dd064aca66 100644
--- a/compiler/rustc_parse_format/src/lib.rs
+++ b/compiler/rustc_parse_format/src/lib.rs
@@ -15,50 +15,13 @@
 )]
 // tidy-alphabetical-end
 
+use std::ops::Range;
+
 pub use Alignment::*;
 pub use Count::*;
 pub use Position::*;
 use rustc_literal_escaper::{Mode, unescape_unicode};
 
-// Note: copied from rustc_span
-/// Range inside of a `Span` used for diagnostics when we only have access to relative positions.
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub struct InnerSpan {
-    pub start: usize,
-    pub end: usize,
-}
-
-impl InnerSpan {
-    pub fn new(start: usize, end: usize) -> InnerSpan {
-        InnerSpan { start, end }
-    }
-}
-
-/// The location and before/after width of a character whose width has changed from its source code
-/// representation
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub struct InnerWidthMapping {
-    /// Index of the character in the source
-    pub position: usize,
-    /// The inner width in characters
-    pub before: usize,
-    /// The transformed width in characters
-    pub after: usize,
-}
-
-impl InnerWidthMapping {
-    pub fn new(position: usize, before: usize, after: usize) -> InnerWidthMapping {
-        InnerWidthMapping { position, before, after }
-    }
-}
-
-/// Whether the input string is a literal. If yes, it contains the inner width mappings.
-#[derive(Clone, PartialEq, Eq)]
-enum InputStringKind {
-    NotALiteral,
-    Literal { width_mappings: Vec<InnerWidthMapping> },
-}
-
 /// The type of format string that we are parsing.
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum ParseMode {
@@ -68,15 +31,6 @@ pub enum ParseMode {
     InlineAsm,
 }
 
-#[derive(Copy, Clone)]
-struct InnerOffset(usize);
-
-impl InnerOffset {
-    fn to(self, end: InnerOffset) -> InnerSpan {
-        InnerSpan::new(self.0, end.0)
-    }
-}
-
 /// A piece is a portion of the format string which represents the next part
 /// to emit. These are emitted as a stream by the `Parser` class.
 #[derive(Clone, Debug, PartialEq)]
@@ -89,13 +43,13 @@ pub enum Piece<'a> {
 }
 
 /// Representation of an argument specification.
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub struct Argument<'a> {
     /// Where to find this argument
     pub position: Position<'a>,
     /// The span of the position indicator. Includes any whitespace in implicit
     /// positions (`{  }`).
-    pub position_span: InnerSpan,
+    pub position_span: Range<usize>,
     /// How to format the argument
     pub format: FormatSpec<'a>,
 }
@@ -125,12 +79,12 @@ impl<'a> Argument<'a> {
 }
 
 /// Specification for the formatting of an argument in the format string.
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub struct FormatSpec<'a> {
     /// Optionally specified character to fill alignment with.
     pub fill: Option<char>,
     /// Span of the optionally specified fill character.
-    pub fill_span: Option<InnerSpan>,
+    pub fill_span: Option<Range<usize>>,
     /// Optionally specified alignment.
     pub align: Alignment,
     /// The `+` or `-` flag.
@@ -144,21 +98,21 @@ pub struct FormatSpec<'a> {
     /// The integer precision to use.
     pub precision: Count<'a>,
     /// The span of the precision formatting flag (for diagnostics).
-    pub precision_span: Option<InnerSpan>,
+    pub precision_span: Option<Range<usize>>,
     /// The string width requested for the resulting format.
     pub width: Count<'a>,
     /// The span of the width formatting flag (for diagnostics).
-    pub width_span: Option<InnerSpan>,
+    pub width_span: Option<Range<usize>>,
     /// The descriptor string representing the name of the format desired for
     /// this argument, this can be empty or any number of characters, although
     /// it is required to be one word.
     pub ty: &'a str,
     /// The span of the descriptor string (for diagnostics).
-    pub ty_span: Option<InnerSpan>,
+    pub ty_span: Option<Range<usize>>,
 }
 
 /// Enum describing where an argument for a format can be located.
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub enum Position<'a> {
     /// The argument is implied to be located at an index
     ArgumentImplicitlyIs(usize),
@@ -210,12 +164,12 @@ pub enum DebugHex {
 
 /// A count is used for the precision and width parameters of an integer, and
 /// can reference either an argument or a literal integer.
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub enum Count<'a> {
     /// The count is specified explicitly.
     CountIs(u16),
     /// The count is specified by the argument with the given name.
-    CountIsName(&'a str, InnerSpan),
+    CountIsName(&'a str, Range<usize>),
     /// The count is specified by the argument at the given index.
     CountIsParam(usize),
     /// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index.
@@ -228,8 +182,8 @@ pub struct ParseError {
     pub description: String,
     pub note: Option<String>,
     pub label: String,
-    pub span: InnerSpan,
-    pub secondary_label: Option<(String, InnerSpan)>,
+    pub span: Range<usize>,
+    pub secondary_label: Option<(String, Range<usize>)>,
     pub suggestion: Suggestion,
 }
 
@@ -240,12 +194,12 @@ pub enum Suggestion {
     UsePositional,
     /// Remove `r#` from identifier:
     /// `format!("{r#foo}")` -> `format!("{foo}")`
-    RemoveRawIdent(InnerSpan),
+    RemoveRawIdent(Range<usize>),
     /// Reorder format parameter:
     /// `format!("{foo:?#}")` -> `format!("{foo:#?}")`
     /// `format!("{foo:?x}")` -> `format!("{foo:x?}")`
     /// `format!("{foo:?X}")` -> `format!("{foo:X?}")`
-    ReorderFormatParameter(InnerSpan, String),
+    ReorderFormatParameter(Range<usize>, String),
 }
 
 /// The parser structure for interpreting the input format string. This is
@@ -256,92 +210,94 @@ pub enum Suggestion {
 /// necessary there's probably lots of room for improvement performance-wise.
 pub struct Parser<'a> {
     mode: ParseMode,
+    /// Input to be parsed
     input: &'a str,
-    cur: std::iter::Peekable<std::str::CharIndices<'a>>,
+    /// Tuples of the span in the code snippet (input as written before being unescaped), the pos in input, and the char in input
+    input_vec: Vec<(Range<usize>, usize, char)>,
+    /// Index into input_vec
+    input_vec_index: usize,
     /// Error messages accumulated during parsing
     pub errors: Vec<ParseError>,
     /// Current position of implicit positional argument pointer
     pub curarg: usize,
-    /// `Some(raw count)` when the string is "raw", used to position spans correctly
-    style: Option<usize>,
     /// Start and end byte offset of every successfully parsed argument
-    pub arg_places: Vec<InnerSpan>,
-    /// Characters whose length has been changed from their in-code representation
-    width_map: Vec<InnerWidthMapping>,
+    pub arg_places: Vec<Range<usize>>,
     /// Span of the last opening brace seen, used for error reporting
-    last_opening_brace: Option<InnerSpan>,
-    /// Whether the source string is comes from `println!` as opposed to `format!` or `print!`
-    append_newline: bool,
+    last_open_brace: Option<Range<usize>>,
     /// Whether this formatting string was written directly in the source. This controls whether we
     /// can use spans to refer into it and give better error messages.
     /// N.B: This does _not_ control whether implicit argument captures can be used.
     pub is_source_literal: bool,
+    /// Index to the end of the literal snippet
+    end_of_snippet: usize,
     /// Start position of the current line.
     cur_line_start: usize,
     /// Start and end byte offset of every line of the format string. Excludes
     /// newline characters and leading whitespace.
-    pub line_spans: Vec<InnerSpan>,
+    pub line_spans: Vec<Range<usize>>,
 }
 
 impl<'a> Iterator for Parser<'a> {
     type Item = Piece<'a>;
 
     fn next(&mut self) -> Option<Piece<'a>> {
-        if let Some(&(pos, c)) = self.cur.peek() {
-            match c {
+        if let Some(&(Range { start, end }, idx, ch)) = self.input_vec.get(self.input_vec_index) {
+            match ch {
                 '{' => {
-                    let curr_last_brace = self.last_opening_brace;
-                    let byte_pos = self.to_span_index(pos);
-                    let lbrace_end = InnerOffset(byte_pos.0 + self.to_span_width(pos));
-                    self.last_opening_brace = Some(byte_pos.to(lbrace_end));
-                    self.cur.next();
-                    if self.consume('{') {
-                        self.last_opening_brace = curr_last_brace;
-
-                        Some(Piece::Lit(self.string(pos + 1)))
+                    self.input_vec_index += 1;
+                    if let Some(&(_, i, '{')) = self.input_vec.get(self.input_vec_index) {
+                        self.input_vec_index += 1;
+                        // double open brace escape: "{{"
+                        // next state after this is either end-of-input or seen-a-brace
+                        Some(Piece::Lit(self.string(i)))
                     } else {
-                        let arg = self.argument(lbrace_end);
-                        if let Some(rbrace_pos) = self.consume_closing_brace(&arg) {
+                        // single open brace
+                        self.last_open_brace = Some(start..end);
+                        let arg = self.argument();
+                        if let Some(close_brace_range) = self.consume_closing_brace(&arg) {
                             if self.is_source_literal {
-                                let lbrace_byte_pos = self.to_span_index(pos);
-                                let rbrace_byte_pos = self.to_span_index(rbrace_pos);
-
-                                let width = self.to_span_width(rbrace_pos);
-
-                                self.arg_places.push(
-                                    lbrace_byte_pos.to(InnerOffset(rbrace_byte_pos.0 + width)),
-                                );
+                                self.arg_places.push(start..close_brace_range.end);
                             }
-                        } else if let Some(&(_, maybe)) = self.cur.peek() {
-                            match maybe {
+                        } else if let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) {
+                            match c {
                                 '?' => self.suggest_format_debug(),
-                                '<' | '^' | '>' => self.suggest_format_align(maybe),
-                                _ => self.suggest_positional_arg_instead_of_captured_arg(arg),
+                                '<' | '^' | '>' => self.suggest_format_align(c),
+                                _ => {
+                                    self.suggest_positional_arg_instead_of_captured_arg(arg.clone())
+                                }
                             }
                         }
                         Some(Piece::NextArgument(Box::new(arg)))
                     }
                 }
                 '}' => {
-                    self.cur.next();
-                    if self.consume('}') {
-                        Some(Piece::Lit(self.string(pos + 1)))
+                    self.input_vec_index += 1;
+                    if let Some(&(_, i, '}')) = self.input_vec.get(self.input_vec_index) {
+                        self.input_vec_index += 1;
+                        // double close brace escape: "}}"
+                        // next state after this is either end-of-input or start
+                        Some(Piece::Lit(self.string(i)))
                     } else {
-                        let err_pos = self.to_span_index(pos);
-                        self.err_with_note(
-                            "unmatched `}` found",
-                            "unmatched `}`",
-                            "if you intended to print `}`, you can escape it using `}}`",
-                            err_pos.to(err_pos),
-                        );
+                        // error: single close brace without corresponding open brace
+                        self.errors.push(ParseError {
+                            description: "unmatched `}` found".into(),
+                            note: Some(
+                                "if you intended to print `}`, you can escape it using `}}`".into(),
+                            ),
+                            label: "unmatched `}`".into(),
+                            span: start..end,
+                            secondary_label: None,
+                            suggestion: Suggestion::None,
+                        });
                         None
                     }
                 }
-                _ => Some(Piece::Lit(self.string(pos))),
+                _ => Some(Piece::Lit(self.string(idx))),
             }
         } else {
+            // end of input
             if self.is_source_literal {
-                let span = self.span(self.cur_line_start, self.input.len());
+                let span = self.cur_line_start..self.end_of_snippet;
                 if self.line_spans.last() != Some(&span) {
                     self.line_spans.push(span);
                 }
@@ -352,71 +308,104 @@ impl<'a> Iterator for Parser<'a> {
 }
 
 impl<'a> Parser<'a> {
-    /// Creates a new parser for the given format string
+    /// Creates a new parser for the given unescaped input string and
+    /// optional code snippet (the input as written before being unescaped),
+    /// where `style` is `Some(nr_hashes)` when the snippet is a raw string with that many hashes.
+    /// If the input comes via `println` or `panic`, then it has a newline already appended,
+    /// which is reflected in the `appended_newline` parameter.
     pub fn new(
-        s: &'a str,
+        input: &'a str,
         style: Option<usize>,
         snippet: Option<String>,
-        append_newline: bool,
+        appended_newline: bool,
         mode: ParseMode,
-    ) -> Parser<'a> {
-        let input_string_kind = find_width_map_from_snippet(s, snippet, style);
-        let (width_map, is_source_literal) = match input_string_kind {
-            InputStringKind::Literal { width_mappings } => (width_mappings, true),
-            InputStringKind::NotALiteral => (Vec::new(), false),
+    ) -> Self {
+        let quote_offset = style.map_or(1, |nr_hashes| nr_hashes + 2);
+
+        let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
+            if let Some(nr_hashes) = style {
+                // snippet is a raw string, which starts with 'r', a number of hashes, and a quote
+                // and ends with a quote and the same number of hashes
+                (true, snippet.len() - nr_hashes - 1, vec![])
+            } else {
+                // snippet is not a raw string
+                if snippet.starts_with('"') {
+                    // snippet looks like an ordinary string literal
+                    // check whether it is the escaped version of input
+                    let without_quotes = &snippet[1..snippet.len() - 1];
+                    let (mut ok, mut vec) = (true, vec![]);
+                    let mut chars = input.chars();
+                    unescape_unicode(without_quotes, Mode::Str, &mut |range, res| match res {
+                        Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
+                            vec.push((range, ch));
+                        }
+                        _ => {
+                            ok = false;
+                            vec = vec![];
+                        }
+                    });
+                    let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
+                    if ok {
+                        if appended_newline {
+                            if chars.as_str() == "\n" {
+                                vec.push((end..end + 1, '\n'));
+                                (true, 1 + end, vec)
+                            } else {
+                                (false, snippet.len(), vec![])
+                            }
+                        } else if chars.as_str() == "" {
+                            (true, 1 + end, vec)
+                        } else {
+                            (false, snippet.len(), vec![])
+                        }
+                    } else {
+                        (false, snippet.len(), vec![])
+                    }
+                } else {
+                    // snippet is not a raw string and does not start with '"'
+                    (false, snippet.len(), vec![])
+                }
+            }
+        } else {
+            // snippet is None
+            (false, input.len() - if appended_newline { 1 } else { 0 }, vec![])
+        };
+
+        let input_vec: Vec<(Range<usize>, usize, char)> = if pre_input_vec.is_empty() {
+            // Snippet is *not* input before unescaping, so spans pointing at it will be incorrect.
+            // This can happen with proc macros that respan generated literals.
+            input
+                .char_indices()
+                .map(|(idx, c)| {
+                    let i = idx + quote_offset;
+                    (i..i + c.len_utf8(), idx, c)
+                })
+                .collect()
+        } else {
+            // Snippet is input before unescaping
+            input
+                .char_indices()
+                .zip(pre_input_vec)
+                .map(|((i, c), (r, _))| (r.start + quote_offset..r.end + quote_offset, i, c))
+                .collect()
         };
 
         Parser {
             mode,
-            input: s,
-            cur: s.char_indices().peekable(),
+            input,
+            input_vec,
+            input_vec_index: 0,
             errors: vec![],
             curarg: 0,
-            style,
             arg_places: vec![],
-            width_map,
-            last_opening_brace: None,
-            append_newline,
+            last_open_brace: None,
             is_source_literal,
-            cur_line_start: 0,
+            end_of_snippet,
+            cur_line_start: quote_offset,
             line_spans: vec![],
         }
     }
 
-    /// Notifies of an error. The message doesn't actually need to be of type
-    /// String, but I think it does when this eventually uses conditions so it
-    /// might as well start using it now.
-    fn err(&mut self, description: impl Into<String>, label: impl Into<String>, span: InnerSpan) {
-        self.errors.push(ParseError {
-            description: description.into(),
-            note: None,
-            label: label.into(),
-            span,
-            secondary_label: None,
-            suggestion: Suggestion::None,
-        });
-    }
-
-    /// Notifies of an error. The message doesn't actually need to be of type
-    /// String, but I think it does when this eventually uses conditions so it
-    /// might as well start using it now.
-    fn err_with_note(
-        &mut self,
-        description: impl Into<String>,
-        label: impl Into<String>,
-        note: impl Into<String>,
-        span: InnerSpan,
-    ) {
-        self.errors.push(ParseError {
-            description: description.into(),
-            note: Some(note.into()),
-            label: label.into(),
-            span,
-            secondary_label: None,
-            suggestion: Suggestion::None,
-        });
-    }
-
     /// Optionally consumes the specified character. If the character is not at
     /// the current position, then the current iterator isn't moved and `false` is
     /// returned, otherwise the character is consumed and `true` is returned.
@@ -428,94 +417,56 @@ impl<'a> Parser<'a> {
     /// the current position, then the current iterator isn't moved and `None` is
     /// returned, otherwise the character is consumed and the current position is
     /// returned.
-    fn consume_pos(&mut self, c: char) -> Option<usize> {
-        if let Some(&(pos, maybe)) = self.cur.peek() {
-            if c == maybe {
-                self.cur.next();
-                return Some(pos);
+    fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> {
+        if let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) {
+            if ch == *c {
+                self.input_vec_index += 1;
+                return Some((r.clone(), *i));
             }
         }
         None
     }
 
-    fn remap_pos(&self, mut pos: usize) -> InnerOffset {
-        for width in &self.width_map {
-            if pos > width.position {
-                pos += width.before - width.after;
-            } else if pos == width.position && width.after == 0 {
-                pos += width.before;
-            } else {
-                break;
-            }
-        }
-
-        InnerOffset(pos)
-    }
-
-    fn to_span_index(&self, pos: usize) -> InnerOffset {
-        // This handles the raw string case, the raw argument is the number of #
-        // in r###"..."### (we need to add one because of the `r`).
-        let raw = self.style.map_or(0, |raw| raw + 1);
-        let pos = self.remap_pos(pos);
-        InnerOffset(raw + pos.0 + 1)
-    }
-
-    fn to_span_width(&self, pos: usize) -> usize {
-        let pos = self.remap_pos(pos);
-        match self.width_map.iter().find(|w| w.position == pos.0) {
-            Some(w) => w.before,
-            None => 1,
-        }
-    }
-
-    fn span(&self, start_pos: usize, end_pos: usize) -> InnerSpan {
-        let start = self.to_span_index(start_pos);
-        let end = self.to_span_index(end_pos);
-        start.to(end)
-    }
-
     /// Forces consumption of the specified character. If the character is not
     /// found, an error is emitted.
-    fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<usize> {
+    fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<Range<usize>> {
         self.ws();
 
-        let pos;
-        let description;
-
-        if let Some(&(peek_pos, maybe)) = self.cur.peek() {
-            if maybe == '}' {
-                self.cur.next();
-                return Some(peek_pos);
+        let (range, description) = if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index)
+        {
+            if *c == '}' {
+                self.input_vec_index += 1;
+                return Some(r.clone());
             }
-
-            pos = peek_pos;
-            description = format!("expected `}}`, found `{}`", maybe.escape_debug());
+            // or r.clone()?
+            (r.start..r.start, format!("expected `}}`, found `{}`", c.escape_debug()))
         } else {
-            description = "expected `}` but string was terminated".to_owned();
-            // point at closing `"`
-            pos = self.input.len() - if self.append_newline { 1 } else { 0 };
-        }
-
-        let pos = self.to_span_index(pos);
+            (
+                // point at closing `"`
+                self.end_of_snippet..self.end_of_snippet,
+                "expected `}` but string was terminated".to_owned(),
+            )
+        };
 
-        let label = "expected `}`".to_owned();
         let (note, secondary_label) = if arg.format.fill == Some('}') {
             (
                 Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
-                arg.format.fill_span.map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
+                arg.format.fill_span.clone().map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
             )
         } else {
             (
                 Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
-                self.last_opening_brace.map(|sp| ("because of this opening brace".to_owned(), sp)),
+                self.last_open_brace
+                    .clone()
+                    .map(|sp| ("because of this opening brace".to_owned(), sp)),
             )
         };
 
         self.errors.push(ParseError {
             description,
             note,
-            label,
-            span: pos.to(pos),
+            label: "expected `}`".to_owned(),
+            span: range.start..range.start,
             secondary_label,
             suggestion: Suggestion::None,
         });
@@ -525,28 +476,30 @@ impl<'a> Parser<'a> {
 
     /// Consumes all whitespace characters until the first non-whitespace character
     fn ws(&mut self) {
-        while let Some(_) = self.cur.next_if(|&(_, c)| c.is_whitespace()) {}
+        let rest = &self.input_vec[self.input_vec_index..];
+        let step = rest.iter().position(|&(_, _, c)| !c.is_whitespace()).unwrap_or(rest.len());
+        self.input_vec_index += step;
     }
 
     /// Parses all of a string which is to be considered a "raw literal" in a
     /// format string. This is everything outside of the braces.
     fn string(&mut self, start: usize) -> &'a str {
-        // we may not consume the character, peek the iterator
-        while let Some(&(pos, c)) = self.cur.peek() {
+        while let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) {
             match c {
                 '{' | '}' => {
-                    return &self.input[start..pos];
+                    return &self.input[start..*i];
                 }
                 '\n' if self.is_source_literal => {
-                    self.line_spans.push(self.span(self.cur_line_start, pos));
-                    self.cur_line_start = pos + 1;
-                    self.cur.next();
+                    self.input_vec_index += 1;
+                    self.line_spans.push(self.cur_line_start..r.start);
+                    self.cur_line_start = r.end;
                 }
                 _ => {
-                    if self.is_source_literal && pos == self.cur_line_start && c.is_whitespace() {
-                        self.cur_line_start = pos + c.len_utf8();
+                    self.input_vec_index += 1;
+                    if self.is_source_literal && r.start == self.cur_line_start && c.is_whitespace()
+                    {
+                        self.cur_line_start = r.end;
                     }
-                    self.cur.next();
                 }
             }
         }
@@ -554,15 +507,13 @@ impl<'a> Parser<'a> {
     }
 
     /// Parses an `Argument` structure, or what's contained within braces inside the format string.
-    fn argument(&mut self, start: InnerOffset) -> Argument<'a> {
-        let pos = self.position();
+    fn argument(&mut self) -> Argument<'a> {
+        let start_idx = self.input_vec_index;
 
-        let end = self
-            .cur
-            .clone()
-            .find(|(_, ch)| !ch.is_whitespace())
-            .map_or(start, |(end, _)| self.to_span_index(end));
-        let position_span = start.to(end);
+        let position = self.position();
+        self.ws();
+
+        let end_idx = self.input_vec_index;
 
         let format = match self.mode {
             ParseMode::Format => self.format(),
@@ -570,16 +521,15 @@ impl<'a> Parser<'a> {
         };
 
         // Resolve position after parsing format spec.
-        let pos = match pos {
-            Some(position) => position,
-            None => {
-                let i = self.curarg;
-                self.curarg += 1;
-                ArgumentImplicitlyIs(i)
-            }
-        };
+        let position = position.unwrap_or_else(|| {
+            let i = self.curarg;
+            self.curarg += 1;
+            ArgumentImplicitlyIs(i)
+        });
 
-        Argument { position: pos, position_span, format }
+        let position_span =
+            self.input_vec_index2range(start_idx).start..self.input_vec_index2range(end_idx).start;
+        Argument { position, position_span, format }
     }
 
     /// Parses a positional argument for a format. This could either be an
@@ -590,23 +540,26 @@ impl<'a> Parser<'a> {
         if let Some(i) = self.integer() {
             Some(ArgumentIs(i.into()))
         } else {
-            match self.cur.peek() {
-                Some(&(lo, c)) if rustc_lexer::is_id_start(c) => {
+            match self.input_vec.get(self.input_vec_index) {
+                Some((range, _, c)) if rustc_lexer::is_id_start(*c) => {
+                    let start = range.start;
                     let word = self.word();
 
                     // Recover from `r#ident` in format strings.
                     // FIXME: use a let chain
                     if word == "r" {
-                        if let Some((pos, '#')) = self.cur.peek() {
-                            if self.input[pos + 1..]
-                                .chars()
-                                .next()
-                                .is_some_and(rustc_lexer::is_id_start)
+                        if let Some((r, _, '#')) = self.input_vec.get(self.input_vec_index) {
+                            if self
+                                .input_vec
+                                .get(self.input_vec_index + 1)
+                                .is_some_and(|(_, _, c)| rustc_lexer::is_id_start(*c))
                             {
-                                self.cur.next();
+                                self.input_vec_index += 1;
+                                let prefix_end = r.end;
                                 let word = self.word();
-                                let prefix_span = self.span(lo, lo + 2);
-                                let full_span = self.span(lo, lo + 2 + word.len());
+                                let prefix_span = start..prefix_end;
+                                let full_span =
+                                    start..self.input_vec_index2range(self.input_vec_index).start;
                                 self.errors.insert(0, ParseError {
                                     description: "raw identifiers are not supported".to_owned(),
                                     note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
@@ -622,7 +575,6 @@ impl<'a> Parser<'a> {
 
                     Some(ArgumentNamed(word))
                 }
-
                 // This is an `ArgumentNext`.
                 // Record the fact and do the resolution after parsing the
                 // format spec, to make things like `{:.*}` work.
@@ -631,8 +583,16 @@ impl<'a> Parser<'a> {
         }
     }
 
-    fn current_pos(&mut self) -> usize {
-        if let Some(&(pos, _)) = self.cur.peek() { pos } else { self.input.len() }
+    fn input_vec_index2pos(&self, index: usize) -> usize {
+        if let Some(&(_, pos, _)) = self.input_vec.get(index) { pos } else { self.input.len() }
+    }
+
+    fn input_vec_index2range(&self, index: usize) -> Range<usize> {
+        if let Some((r, _, _)) = self.input_vec.get(index) {
+            r.clone()
+        } else {
+            self.end_of_snippet..self.end_of_snippet
+        }
     }
 
     /// Parses a format specifier at the current position, returning all of the
@@ -658,11 +618,11 @@ impl<'a> Parser<'a> {
         }
 
         // fill character
-        if let Some(&(idx, c)) = self.cur.peek() {
-            if let Some((_, '>' | '<' | '^')) = self.cur.clone().nth(1) {
+        if let Some(&(ref r, _, c)) = self.input_vec.get(self.input_vec_index) {
+            if let Some((_, _, '>' | '<' | '^')) = self.input_vec.get(self.input_vec_index + 1) {
+                self.input_vec_index += 1;
                 spec.fill = Some(c);
-                spec.fill_span = Some(self.span(idx, idx + 1));
-                self.cur.next();
+                spec.fill_span = Some(r.clone());
             }
         }
         // Alignment
@@ -686,14 +646,14 @@ impl<'a> Parser<'a> {
         // Width and precision
         let mut havewidth = false;
 
-        if self.consume('0') {
+        if let Some((range, _)) = self.consume_pos('0') {
             // small ambiguity with '0$' as a format string. In theory this is a
             // '0' flag and then an ill-formatted format string with just a '$'
             // and no count, but this is better if we instead interpret this as
             // no '0' flag and '0$' as the width instead.
-            if let Some(end) = self.consume_pos('$') {
+            if let Some((r, _)) = self.consume_pos('$') {
                 spec.width = CountIsParam(0);
-                spec.width_span = Some(self.span(end - 1, end + 1));
+                spec.width_span = Some(range.start..r.end);
                 havewidth = true;
             } else {
                 spec.zero_pad = true;
@@ -701,15 +661,15 @@ impl<'a> Parser<'a> {
         }
 
         if !havewidth {
-            let start = self.current_pos();
-            spec.width = self.count(start);
+            let start_idx = self.input_vec_index;
+            spec.width = self.count();
             if spec.width != CountImplied {
-                let end = self.current_pos();
-                spec.width_span = Some(self.span(start, end));
+                let end = self.input_vec_index2range(self.input_vec_index).start;
+                spec.width_span = Some(self.input_vec_index2range(start_idx).start..end);
             }
         }
 
-        if let Some(start) = self.consume_pos('.') {
+        if let Some((range, _)) = self.consume_pos('.') {
             if self.consume('*') {
                 // Resolve `CountIsNextParam`.
                 // We can do this immediately as `position` is resolved later.
@@ -717,13 +677,13 @@ impl<'a> Parser<'a> {
                 self.curarg += 1;
                 spec.precision = CountIsStar(i);
             } else {
-                spec.precision = self.count(start + 1);
+                spec.precision = self.count();
             }
-            let end = self.current_pos();
-            spec.precision_span = Some(self.span(start, end));
+            spec.precision_span =
+                Some(range.start..self.input_vec_index2range(self.input_vec_index).start);
         }
 
-        let ty_span_start = self.current_pos();
+        let start_idx = self.input_vec_index;
         // Optional radix followed by the actual format specifier
         if self.consume('x') {
             if self.consume('?') {
@@ -739,19 +699,33 @@ impl<'a> Parser<'a> {
             } else {
                 spec.ty = "X";
             }
-        } else if self.consume('?') {
+        } else if let Some((range, _)) = self.consume_pos('?') {
             spec.ty = "?";
-            if let Some(&(_, maybe)) = self.cur.peek() {
-                match maybe {
-                    '#' | 'x' | 'X' => self.suggest_format_parameter(maybe),
+            if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index) {
+                match c {
+                    '#' | 'x' | 'X' => self.errors.insert(
+                        0,
+                        ParseError {
+                            description: format!("expected `}}`, found `{c}`"),
+                            note: None,
+                            label: "expected `'}'`".into(),
+                            span: r.clone(),
+                            secondary_label: None,
+                            suggestion: Suggestion::ReorderFormatParameter(
+                                range.start..r.end,
+                                format!("{c}?"),
+                            ),
+                        },
+                    ),
                     _ => (),
                 }
             }
         } else {
             spec.ty = self.word();
             if !spec.ty.is_empty() {
-                let ty_span_end = self.current_pos();
-                spec.ty_span = Some(self.span(ty_span_start, ty_span_end));
+                let start = self.input_vec_index2range(start_idx).start;
+                let end = self.input_vec_index2range(self.input_vec_index).start;
+                spec.ty_span = Some(start..end);
             }
         }
         spec
@@ -779,11 +753,12 @@ impl<'a> Parser<'a> {
             return spec;
         }
 
-        let ty_span_start = self.current_pos();
+        let start_idx = self.input_vec_index;
         spec.ty = self.word();
         if !spec.ty.is_empty() {
-            let ty_span_end = self.current_pos();
-            spec.ty_span = Some(self.span(ty_span_start, ty_span_end));
+            let start = self.input_vec_index2range(start_idx).start;
+            let end = self.input_vec_index2range(self.input_vec_index).start;
+            spec.ty_span = Some(start..end);
         }
 
         spec
@@ -792,55 +767,58 @@ impl<'a> Parser<'a> {
     /// Parses a `Count` parameter at the current position. This does not check
     /// for 'CountIsNextParam' because that is only used in precision, not
     /// width.
-    fn count(&mut self, start: usize) -> Count<'a> {
+    fn count(&mut self) -> Count<'a> {
         if let Some(i) = self.integer() {
             if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) }
         } else {
-            let tmp = self.cur.clone();
+            let start_idx = self.input_vec_index;
             let word = self.word();
             if word.is_empty() {
-                self.cur = tmp;
                 CountImplied
-            } else if let Some(end) = self.consume_pos('$') {
-                let name_span = self.span(start, end);
-                CountIsName(word, name_span)
+            } else if let Some((r, _)) = self.consume_pos('$') {
+                CountIsName(word, self.input_vec_index2range(start_idx).start..r.start)
             } else {
-                self.cur = tmp;
+                self.input_vec_index = start_idx;
                 CountImplied
             }
         }
     }
 
-    /// Parses a word starting at the current position. A word is the same as
+    /// Parses a word starting at the current position. A word is the same as a
     /// Rust identifier, except that it can't start with `_` character.
     fn word(&mut self) -> &'a str {
-        let start = match self.cur.peek() {
-            Some(&(pos, c)) if rustc_lexer::is_id_start(c) => {
-                self.cur.next();
-                pos
+        let index = self.input_vec_index;
+        match self.input_vec.get(self.input_vec_index) {
+            Some(&(ref r, i, c)) if rustc_lexer::is_id_start(c) => {
+                self.input_vec_index += 1;
+                (r.start, i)
             }
             _ => {
                 return "";
             }
         };
-        let mut end = None;
-        while let Some(&(pos, c)) = self.cur.peek() {
-            if rustc_lexer::is_id_continue(c) {
-                self.cur.next();
+        let (err_end, end): (usize, usize) = loop {
+            if let Some(&(ref r, i, c)) = self.input_vec.get(self.input_vec_index) {
+                if rustc_lexer::is_id_continue(c) {
+                    self.input_vec_index += 1;
+                } else {
+                    break (r.start, i);
+                }
             } else {
-                end = Some(pos);
-                break;
+                break (self.end_of_snippet, self.input.len());
             }
-        }
-        let end = end.unwrap_or(self.input.len());
-        let word = &self.input[start..end];
+        };
+
+        let word = &self.input[self.input_vec_index2pos(index)..end];
         if word == "_" {
-            self.err_with_note(
-                "invalid argument name `_`",
-                "invalid argument name",
-                "argument name cannot be a single underscore",
-                self.span(start, end),
-            );
+            self.errors.push(ParseError {
+                description: "invalid argument name `_`".into(),
+                note: Some("argument name cannot be a single underscore".into()),
+                label: "invalid argument name".into(),
+                span: self.input_vec_index2range(index).start..err_end,
+                secondary_label: None,
+                suggestion: Suggestion::None,
+            });
         }
         word
     }
@@ -849,9 +827,10 @@ impl<'a> Parser<'a> {
         let mut cur: u16 = 0;
         let mut found = false;
         let mut overflow = false;
-        let start = self.current_pos();
-        while let Some(&(_, c)) = self.cur.peek() {
+        let start_index = self.input_vec_index;
+        while let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) {
             if let Some(i) = c.to_digit(10) {
+                self.input_vec_index += 1;
                 let (tmp, mul_overflow) = cur.overflowing_mul(10);
                 let (tmp, add_overflow) = tmp.overflowing_add(i as u16);
                 if mul_overflow || add_overflow {
@@ -859,40 +838,42 @@ impl<'a> Parser<'a> {
                 }
                 cur = tmp;
                 found = true;
-                self.cur.next();
             } else {
                 break;
             }
         }
 
         if overflow {
-            let end = self.current_pos();
-            let overflowed_int = &self.input[start..end];
-            self.err(
-                format!(
+            let overflowed_int = &self.input[self.input_vec_index2pos(start_index)
+                ..self.input_vec_index2pos(self.input_vec_index)];
+            self.errors.push(ParseError {
+                description: format!(
                     "integer `{}` does not fit into the type `u16` whose range is `0..={}`",
                     overflowed_int,
                     u16::MAX
                 ),
-                "integer out of range for `u16`",
-                self.span(start, end),
-            );
+                note: None,
+                label: "integer out of range for `u16`".into(),
+                span: self.input_vec_index2range(start_index).start
+                    ..self.input_vec_index2range(self.input_vec_index).end,
+                secondary_label: None,
+                suggestion: Suggestion::None,
+            });
         }
 
         found.then_some(cur)
     }
 
     fn suggest_format_debug(&mut self) {
-        if let (Some(pos), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
+        if let (Some((range, _)), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
             let word = self.word();
-            let pos = self.to_span_index(pos);
             self.errors.insert(
                 0,
                 ParseError {
                     description: "expected format parameter to occur after `:`".to_owned(),
                     note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
                     label: "expected `?` to occur after `:`".to_owned(),
-                    span: pos.to(pos),
+                    span: range,
                     secondary_label: None,
                     suggestion: Suggestion::None,
                 },
@@ -901,15 +882,14 @@ impl<'a> Parser<'a> {
     }
 
     fn suggest_format_align(&mut self, alignment: char) {
-        if let Some(pos) = self.consume_pos(alignment) {
-            let pos = self.to_span_index(pos);
+        if let Some((range, _)) = self.consume_pos(alignment) {
             self.errors.insert(
                 0,
                 ParseError {
                     description: "expected format parameter to occur after `:`".to_owned(),
                     note: None,
                     label: format!("expected `{}` to occur after `:`", alignment),
-                    span: pos.to(pos),
+                    span: range,
                     secondary_label: None,
                     suggestion: Suggestion::None,
                 },
@@ -923,10 +903,8 @@ impl<'a> Parser<'a> {
             return;
         }
 
-        if let Some(end) = self.consume_pos('.') {
-            let byte_pos = self.to_span_index(end);
-            let start = InnerOffset(byte_pos.0 + 1);
-            let field = self.argument(start);
+        if let Some((_range, _pos)) = self.consume_pos('.') {
+            let field = self.argument();
             // We can only parse simple `foo.bar` field access or `foo.0` tuple index access, any
             // deeper nesting, or another type of expression, like method calls, are not supported
             if !self.consume('}') {
@@ -941,10 +919,7 @@ impl<'a> Parser<'a> {
                                 description: "field access isn't supported".to_string(),
                                 note: None,
                                 label: "not supported".to_string(),
-                                span: InnerSpan::new(
-                                    arg.position_span.start,
-                                    field.position_span.end,
-                                ),
+                                span: arg.position_span.start..field.position_span.end,
                                 secondary_label: None,
                                 suggestion: Suggestion::UsePositional,
                             },
@@ -957,10 +932,7 @@ impl<'a> Parser<'a> {
                                 description: "tuple index access isn't supported".to_string(),
                                 note: None,
                                 label: "not supported".to_string(),
-                                span: InnerSpan::new(
-                                    arg.position_span.start,
-                                    field.position_span.end,
-                                ),
+                                span: arg.position_span.start..field.position_span.end,
                                 secondary_label: None,
                                 suggestion: Suggestion::UsePositional,
                             },
@@ -971,164 +943,6 @@ impl<'a> Parser<'a> {
             }
         }
     }
-
-    fn suggest_format_parameter(&mut self, c: char) {
-        let replacement = match c {
-            '#' => "#?",
-            'x' => "x?",
-            'X' => "X?",
-            _ => return,
-        };
-        let Some(pos) = self.consume_pos(c) else {
-            return;
-        };
-
-        let span = self.span(pos - 1, pos + 1);
-        let pos = self.to_span_index(pos);
-
-        self.errors.insert(
-            0,
-            ParseError {
-                description: format!("expected `}}`, found `{c}`"),
-                note: None,
-                label: "expected `'}'`".into(),
-                span: pos.to(pos),
-                secondary_label: None,
-                suggestion: Suggestion::ReorderFormatParameter(span, format!("{replacement}")),
-            },
-        )
-    }
-}
-
-/// Finds the indices of all characters that have been processed and differ between the actual
-/// written code (code snippet) and the `InternedString` that gets processed in the `Parser`
-/// in order to properly synthesise the intra-string `Span`s for error diagnostics.
-fn find_width_map_from_snippet(
-    input: &str,
-    snippet: Option<String>,
-    str_style: Option<usize>,
-) -> InputStringKind {
-    let snippet = match snippet {
-        Some(ref s) if s.starts_with('"') || s.starts_with("r\"") || s.starts_with("r#") => s,
-        _ => return InputStringKind::NotALiteral,
-    };
-
-    if str_style.is_some() {
-        return InputStringKind::Literal { width_mappings: Vec::new() };
-    }
-
-    // Strip quotes.
-    let snippet = &snippet[1..snippet.len() - 1];
-
-    // Macros like `println` add a newline at the end. That technically doesn't make them "literals" anymore, but it's fine
-    // since we will never need to point our spans there, so we lie about it here by ignoring it.
-    // Since there might actually be newlines in the source code, we need to normalize away all trailing newlines.
-    // If we only trimmed it off the input, `format!("\n")` would cause a mismatch as here we they actually match up.
-    // Alternatively, we could just count the trailing newlines and only trim one from the input if they don't match up.
-    let input_no_nl = input.trim_end_matches('\n');
-    let Some(unescaped) = unescape_string(snippet) else {
-        return InputStringKind::NotALiteral;
-    };
-
-    let unescaped_no_nl = unescaped.trim_end_matches('\n');
-
-    if unescaped_no_nl != input_no_nl {
-        // The source string that we're pointing at isn't our input, so spans pointing at it will be incorrect.
-        // This can for example happen with proc macros that respan generated literals.
-        return InputStringKind::NotALiteral;
-    }
-
-    let mut s = snippet.char_indices();
-    let mut width_mappings = vec![];
-    while let Some((pos, c)) = s.next() {
-        match (c, s.clone().next()) {
-            // skip whitespace and empty lines ending in '\\'
-            ('\\', Some((_, '\n'))) => {
-                let _ = s.next();
-                let mut width = 2;
-
-                while let Some((_, c)) = s.clone().next() {
-                    if matches!(c, ' ' | '\n' | '\t') {
-                        width += 1;
-                        let _ = s.next();
-                    } else {
-                        break;
-                    }
-                }
-
-                width_mappings.push(InnerWidthMapping::new(pos, width, 0));
-            }
-            ('\\', Some((_, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
-                width_mappings.push(InnerWidthMapping::new(pos, 2, 1));
-                let _ = s.next();
-            }
-            ('\\', Some((_, 'x'))) => {
-                // consume `\xAB` literal
-                s.nth(2);
-                width_mappings.push(InnerWidthMapping::new(pos, 4, 1));
-            }
-            ('\\', Some((_, 'u'))) => {
-                let mut width = 2;
-                let _ = s.next();
-
-                if let Some((_, next_c)) = s.next() {
-                    if next_c == '{' {
-                        // consume up to 6 hexanumeric chars
-                        let digits_len =
-                            s.clone().take(6).take_while(|(_, c)| c.is_ascii_hexdigit()).count();
-
-                        let len_utf8 = s
-                            .as_str()
-                            .get(..digits_len)
-                            .and_then(|digits| u32::from_str_radix(digits, 16).ok())
-                            .and_then(char::from_u32)
-                            .map_or(1, char::len_utf8);
-
-                        // Skip the digits, for chars that encode to more than 1 utf-8 byte
-                        // exclude as many digits as it is greater than 1 byte
-                        //
-                        // So for a 3 byte character, exclude 2 digits
-                        let required_skips = digits_len.saturating_sub(len_utf8.saturating_sub(1));
-
-                        // skip '{' and '}' also
-                        width += required_skips + 2;
-
-                        s.nth(digits_len);
-                    } else if next_c.is_ascii_hexdigit() {
-                        width += 1;
-
-                        // We suggest adding `{` and `}` when appropriate, accept it here as if
-                        // it were correct
-                        let mut i = 0; // consume up to 6 hexanumeric chars
-                        while let (Some((_, c)), _) = (s.next(), i < 6) {
-                            if c.is_ascii_hexdigit() {
-                                width += 1;
-                            } else {
-                                break;
-                            }
-                            i += 1;
-                        }
-                    }
-                }
-
-                width_mappings.push(InnerWidthMapping::new(pos, width, 1));
-            }
-            _ => {}
-        }
-    }
-
-    InputStringKind::Literal { width_mappings }
-}
-
-fn unescape_string(string: &str) -> Option<String> {
-    let mut buf = String::new();
-    let mut ok = true;
-    unescape_unicode(string, Mode::Str, &mut |_, unescaped_char| match unescaped_char {
-        Ok(c) => buf.push(c),
-        Err(_) => ok = false,
-    });
-
-    ok.then_some(buf)
 }
 
 // Assert a reasonable size for `Piece`
diff --git a/compiler/rustc_parse_format/src/tests.rs b/compiler/rustc_parse_format/src/tests.rs
index cc8a0069c4e..e6a7f24034a 100644
--- a/compiler/rustc_parse_format/src/tests.rs
+++ b/compiler/rustc_parse_format/src/tests.rs
@@ -41,7 +41,6 @@ fn simple() {
     same("}}", &[Lit("}")]);
     same("\\}}", &[Lit("\\"), Lit("}")]);
 }
-
 #[test]
 fn invalid01() {
     musterr("{")
@@ -79,23 +78,48 @@ fn invalid_precision() {
 }
 
 #[test]
-fn format_nothing() {
+fn format_empty() {
     same(
         "{}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: fmtdflt(),
         }))],
     );
 }
 #[test]
+fn format_tab_empty() {
+    let fmt_pre = r###""\t{}""###;
+    let fmt = "\t{}";
+    let parser = Parser::new(fmt, None, Some(fmt_pre.into()), false, ParseMode::Format);
+    assert_eq!(
+        parser.collect::<Vec<Piece<'static>>>(),
+        &[
+            Lit("\t"),
+            NextArgument(Box::new(Argument {
+                position: ArgumentImplicitlyIs(0),
+                position_span: 4..4,
+                format: fmtdflt(),
+            }))
+        ],
+    );
+}
+#[test]
+fn format_open_brace_tab() {
+    let fmt_pre = r###""{\t""###;
+    let fmt = "{\t";
+    let mut parser = Parser::new(fmt, None, Some(fmt_pre.into()), false, ParseMode::Format);
+    let _ = parser.by_ref().collect::<Vec<Piece<'static>>>();
+    assert_eq!(parser.errors[0].span, 4..4);
+}
+#[test]
 fn format_position() {
     same(
         "{3}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentIs(3),
-            position_span: InnerSpan { start: 2, end: 3 },
+            position_span: 2..3,
             format: fmtdflt(),
         }))],
     );
@@ -106,7 +130,7 @@ fn format_position_nothing_else() {
         "{3:}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentIs(3),
-            position_span: InnerSpan { start: 2, end: 3 },
+            position_span: 2..3,
             format: fmtdflt(),
         }))],
     );
@@ -117,18 +141,54 @@ fn format_named() {
         "{name}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentNamed("name"),
-            position_span: InnerSpan { start: 2, end: 6 },
+            position_span: 2..6,
             format: fmtdflt(),
         }))],
     )
 }
 #[test]
+fn format_named_space_nothing() {
+    same(
+        "{name} {}",
+        &[
+            NextArgument(Box::new(Argument {
+                position: ArgumentNamed("name"),
+                position_span: 2..6,
+                format: fmtdflt(),
+            })),
+            Lit(" "),
+            NextArgument(Box::new(Argument {
+                position: ArgumentImplicitlyIs(0),
+                position_span: 9..9,
+                format: fmtdflt(),
+            })),
+        ],
+    )
+}
+#[test]
+fn format_raw() {
+    let snippet = r###"r#"assertion `left {op} right` failed"#"###.into();
+    let source = r#"assertion `left {op} right` failed"#;
+
+    let parser = Parser::new(source, Some(1), Some(snippet), true, ParseMode::Format);
+    let expected = &[
+        Lit("assertion `left "),
+        NextArgument(Box::new(Argument {
+            position: ArgumentNamed("op"),
+            position_span: 20..22,
+            format: fmtdflt(),
+        })),
+        Lit(" right` failed"),
+    ];
+    assert_eq!(parser.collect::<Vec<Piece<'static>>>(), expected);
+}
+#[test]
 fn format_type() {
     same(
         "{3:x}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentIs(3),
-            position_span: InnerSpan { start: 2, end: 3 },
+            position_span: 2..3,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -153,7 +213,7 @@ fn format_align_fill() {
         "{3:>}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentIs(3),
-            position_span: InnerSpan { start: 2, end: 3 },
+            position_span: 2..3,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -175,10 +235,10 @@ fn format_align_fill() {
         "{3:0<}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentIs(3),
-            position_span: InnerSpan { start: 2, end: 3 },
+            position_span: 2..3,
             format: FormatSpec {
                 fill: Some('0'),
-                fill_span: Some(InnerSpan::new(4, 5)),
+                fill_span: Some(4..5),
                 align: AlignLeft,
                 sign: None,
                 alternate: false,
@@ -197,10 +257,10 @@ fn format_align_fill() {
         "{3:*<abcd}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentIs(3),
-            position_span: InnerSpan { start: 2, end: 3 },
+            position_span: 2..3,
             format: FormatSpec {
                 fill: Some('*'),
-                fill_span: Some(InnerSpan::new(4, 5)),
+                fill_span: Some(4..5),
                 align: AlignLeft,
                 sign: None,
                 alternate: false,
@@ -211,7 +271,7 @@ fn format_align_fill() {
                 precision_span: None,
                 width_span: None,
                 ty: "abcd",
-                ty_span: Some(InnerSpan::new(6, 10)),
+                ty_span: Some(6..10),
             },
         }))],
     );
@@ -222,7 +282,7 @@ fn format_counts() {
         "{:10x}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -234,7 +294,7 @@ fn format_counts() {
                 precision: CountImplied,
                 precision_span: None,
                 width: CountIs(10),
-                width_span: Some(InnerSpan { start: 3, end: 5 }),
+                width_span: Some(3..5),
                 ty: "x",
                 ty_span: None,
             },
@@ -244,7 +304,7 @@ fn format_counts() {
         "{:10$.10x}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -254,9 +314,9 @@ fn format_counts() {
                 zero_pad: false,
                 debug_hex: None,
                 precision: CountIs(10),
-                precision_span: Some(InnerSpan { start: 6, end: 9 }),
+                precision_span: Some(6..9),
                 width: CountIsParam(10),
-                width_span: Some(InnerSpan { start: 3, end: 6 }),
+                width_span: Some(3..6),
                 ty: "x",
                 ty_span: None,
             },
@@ -266,7 +326,7 @@ fn format_counts() {
         "{1:0$.10x}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentIs(1),
-            position_span: InnerSpan { start: 2, end: 3 },
+            position_span: 2..3,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -276,9 +336,9 @@ fn format_counts() {
                 zero_pad: false,
                 debug_hex: None,
                 precision: CountIs(10),
-                precision_span: Some(InnerSpan { start: 6, end: 9 }),
+                precision_span: Some(6..9),
                 width: CountIsParam(0),
-                width_span: Some(InnerSpan { start: 4, end: 6 }),
+                width_span: Some(4..6),
                 ty: "x",
                 ty_span: None,
             },
@@ -288,7 +348,7 @@ fn format_counts() {
         "{:.*x}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(1),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -298,7 +358,7 @@ fn format_counts() {
                 zero_pad: false,
                 debug_hex: None,
                 precision: CountIsStar(0),
-                precision_span: Some(InnerSpan { start: 3, end: 5 }),
+                precision_span: Some(3..5),
                 width: CountImplied,
                 width_span: None,
                 ty: "x",
@@ -310,7 +370,7 @@ fn format_counts() {
         "{:.10$x}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -321,7 +381,7 @@ fn format_counts() {
                 debug_hex: None,
                 precision: CountIsParam(10),
                 width: CountImplied,
-                precision_span: Some(InnerSpan::new(3, 7)),
+                precision_span: Some(3..7),
                 width_span: None,
                 ty: "x",
                 ty_span: None,
@@ -332,7 +392,7 @@ fn format_counts() {
         "{:a$.b$?}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -341,10 +401,10 @@ fn format_counts() {
                 alternate: false,
                 zero_pad: false,
                 debug_hex: None,
-                precision: CountIsName("b", InnerSpan { start: 6, end: 7 }),
-                precision_span: Some(InnerSpan { start: 5, end: 8 }),
-                width: CountIsName("a", InnerSpan { start: 3, end: 4 }),
-                width_span: Some(InnerSpan { start: 3, end: 5 }),
+                precision: CountIsName("b", 6..7),
+                precision_span: Some(5..8),
+                width: CountIsName("a", 3..4),
+                width_span: Some(3..5),
                 ty: "?",
                 ty_span: None,
             },
@@ -354,7 +414,7 @@ fn format_counts() {
         "{:.4}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -364,7 +424,7 @@ fn format_counts() {
                 zero_pad: false,
                 debug_hex: None,
                 precision: CountIs(4),
-                precision_span: Some(InnerSpan { start: 3, end: 5 }),
+                precision_span: Some(3..5),
                 width: CountImplied,
                 width_span: None,
                 ty: "",
@@ -379,7 +439,7 @@ fn format_flags() {
         "{:-}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -401,7 +461,7 @@ fn format_flags() {
         "{:+#}",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 2 },
+            position_span: 2..2,
             format: FormatSpec {
                 fill: None,
                 fill_span: None,
@@ -428,7 +488,7 @@ fn format_mixture() {
             Lit("abcd "),
             NextArgument(Box::new(Argument {
                 position: ArgumentIs(3),
-                position_span: InnerSpan { start: 7, end: 8 },
+                position_span: 7..8,
                 format: FormatSpec {
                     fill: None,
                     fill_span: None,
@@ -455,7 +515,7 @@ fn format_whitespace() {
         "{ }",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 3 },
+            position_span: 2..3,
             format: fmtdflt(),
         }))],
     );
@@ -463,8 +523,33 @@ fn format_whitespace() {
         "{  }",
         &[NextArgument(Box::new(Argument {
             position: ArgumentImplicitlyIs(0),
-            position_span: InnerSpan { start: 2, end: 4 },
+            position_span: 2..4,
             format: fmtdflt(),
         }))],
     );
 }
+#[test]
+fn asm_linespans() {
+    let asm_pre = r###"r"
+        .intel_syntax noprefix
+        nop""###;
+    let asm = r"
+        .intel_syntax noprefix
+        nop";
+    let mut parser = Parser::new(asm, Some(0), Some(asm_pre.into()), false, ParseMode::InlineAsm);
+    assert!(parser.is_source_literal);
+    assert_eq!(
+        parser.by_ref().collect::<Vec<Piece<'static>>>(),
+        &[Lit("\n        .intel_syntax noprefix\n        nop")]
+    );
+    assert_eq!(parser.line_spans, &[2..2, 11..33, 42..45]);
+}
+#[test]
+fn asm_concat() {
+    let asm_pre = r###"concat!("invalid", "_", "instruction")"###;
+    let asm = "invalid_instruction";
+    let mut parser = Parser::new(asm, None, Some(asm_pre.into()), false, ParseMode::InlineAsm);
+    assert!(!parser.is_source_literal);
+    assert_eq!(parser.by_ref().collect::<Vec<Piece<'static>>>(), &[Lit(asm)]);
+    assert_eq!(parser.line_spans, &[]);
+}