about summary refs log tree commit diff
path: root/src/libfmt_macros
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2018-12-20 22:33:16 -0800
committerEsteban Küber <esteban@kuber.com.ar>2018-12-26 14:29:43 -0800
commit862ebc4c38958b8ba5e30dd5eb1203e6109c40a2 (patch)
tree35e8170000c41269ce99e828330ce3efe7ac65d2 /src/libfmt_macros
parent79d8a0fcefa5134db2a94739b1d18daa01fc6e9f (diff)
Various changes to string format diagnostics
- Point at opening mismatched formatting brace
- Account for differences between raw and regular strings
- Account for differences between the code snippet and `InternedString`
- Add more tests
Diffstat (limited to 'src/libfmt_macros')
-rw-r--r--src/libfmt_macros/lib.rs144
1 files changed, 104 insertions, 40 deletions
diff --git a/src/libfmt_macros/lib.rs b/src/libfmt_macros/lib.rs
index 74b55673470..fc8325e5915 100644
--- a/src/libfmt_macros/lib.rs
+++ b/src/libfmt_macros/lib.rs
@@ -123,8 +123,9 @@ pub struct ParseError {
     pub description: string::String,
     pub note: Option<string::String>,
     pub label: string::String,
-    pub start: usize,
-    pub end: usize,
+    pub start: SpanIndex,
+    pub end: SpanIndex,
+    pub secondary_label: Option<(string::String, SpanIndex, SpanIndex)>,
 }
 
 /// The parser structure for interpreting the input format string. This is
@@ -142,22 +143,39 @@ pub struct Parser<'a> {
     curarg: usize,
     /// `Some(raw count)` when the string is "raw", used to position spans correctly
     style: Option<usize>,
-    /// How many newlines have been seen in the string so far, to adjust the error spans
-    seen_newlines: usize,
     /// Start and end byte offset of every successfully parsed argument
     pub arg_places: Vec<(usize, usize)>,
+    /// Characters that need to be shifted
+    skips: Vec<usize>,
+    /// Span offset of the last opening brace seen, used for error reporting
+    last_opening_brace_pos: Option<SpanIndex>,
+    /// Wether the source string is comes from `println!` as opposed to `format!` or `print!`
+    append_newline: bool,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct SpanIndex(usize);
+
+impl SpanIndex {
+    pub fn unwrap(self) -> usize {
+        self.0
+    }
 }
 
 impl<'a> Iterator for Parser<'a> {
     type Item = Piece<'a>;
 
     fn next(&mut self) -> Option<Piece<'a>> {
-        let raw = self.style.map(|raw| raw + self.seen_newlines).unwrap_or(0);
+        let raw = self.raw();
         if let Some(&(pos, c)) = self.cur.peek() {
             match c {
                 '{' => {
+                    let curr_last_brace = self.last_opening_brace_pos;
+                    self.last_opening_brace_pos = Some(self.to_span_index(pos));
                     self.cur.next();
                     if self.consume('{') {
+                        self.last_opening_brace_pos = curr_last_brace;
+
                         Some(String(self.string(pos + 1)))
                     } else {
                         let arg = self.argument();
@@ -174,7 +192,7 @@ impl<'a> Iterator for Parser<'a> {
                     if self.consume('}') {
                         Some(String(self.string(pos + 1)))
                     } else {
-                        let err_pos = pos + raw + 1;
+                        let err_pos = self.to_span_index(pos);
                         self.err_with_note(
                             "unmatched `}` found",
                             "unmatched `}`",
@@ -186,7 +204,6 @@ impl<'a> Iterator for Parser<'a> {
                     }
                 }
                 '\n' => {
-                    self.seen_newlines += 1;
                     Some(String(self.string(pos)))
                 }
                 _ => Some(String(self.string(pos))),
@@ -199,15 +216,22 @@ impl<'a> Iterator for Parser<'a> {
 
 impl<'a> Parser<'a> {
     /// Creates a new parser for the given format string
-    pub fn new(s: &'a str, style: Option<usize>) -> Parser<'a> {
+    pub fn new(
+        s: &'a str,
+        style: Option<usize>,
+        skips: Vec<usize>,
+        append_newline: bool,
+    ) -> Parser<'a> {
         Parser {
             input: s,
             cur: s.char_indices().peekable(),
             errors: vec![],
             curarg: 0,
             style,
-            seen_newlines: 0,
             arg_places: vec![],
+            skips,
+            last_opening_brace_pos: None,
+            append_newline,
         }
     }
 
@@ -218,8 +242,8 @@ impl<'a> Parser<'a> {
         &mut self,
         description: S1,
         label: S2,
-        start: usize,
-        end: usize,
+        start: SpanIndex,
+        end: SpanIndex,
     ) {
         self.errors.push(ParseError {
             description: description.into(),
@@ -227,6 +251,7 @@ impl<'a> Parser<'a> {
             label: label.into(),
             start,
             end,
+            secondary_label: None,
         });
     }
 
@@ -238,8 +263,8 @@ impl<'a> Parser<'a> {
         description: S1,
         label: S2,
         note: S3,
-        start: usize,
-        end: usize,
+        start: SpanIndex,
+        end: SpanIndex,
     ) {
         self.errors.push(ParseError {
             description: description.into(),
@@ -247,6 +272,7 @@ impl<'a> Parser<'a> {
             label: label.into(),
             start,
             end,
+            secondary_label: None,
         });
     }
 
@@ -266,47 +292,86 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn raw(&self) -> usize {
+        self.style.map(|raw| raw + 1).unwrap_or(0)
+    }
+
+    fn to_span_index(&self, pos: usize) -> SpanIndex {
+        let mut pos = pos;
+        for skip in &self.skips {
+            if pos > *skip {
+                pos += 1;
+            } else if pos == *skip && self.raw() == 0 {
+                pos += 1;
+            } else {
+                break;
+            }
+        }
+        SpanIndex(self.raw() + pos + 1)
+    }
+
     /// Forces consumption of the specified character. If the character is not
     /// found, an error is emitted.
     fn must_consume(&mut self, c: char) -> Option<usize> {
         self.ws();
-        let raw = self.style.unwrap_or(0);
 
-        let padding = raw + self.seen_newlines;
         if let Some(&(pos, maybe)) = self.cur.peek() {
             if c == maybe {
                 self.cur.next();
                 Some(pos)
             } else {
-                let pos = pos + raw + 1;
-                self.err(format!("expected `{:?}`, found `{:?}`", c, maybe),
-                         format!("expected `{}`", c),
-                         pos,
-                         pos);
+                let pos = self.to_span_index(pos);
+                let description = format!("expected `'}}'`, found `{:?}`", maybe);
+                let label = "expected `}`".to_owned();
+                let (note, secondary_label) = if c == '}' {
+                    (Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
+                     self.last_opening_brace_pos.map(|pos| {
+                        ("because of this opening brace".to_owned(), pos, pos)
+                     }))
+                } else {
+                    (None, None)
+                };
+                self.errors.push(ParseError {
+                    description,
+                    note,
+                    label,
+                    start: pos,
+                    end: pos,
+                    secondary_label,
+                });
                 None
             }
         } else {
-            let msg = format!("expected `{:?}` but string was terminated", c);
-            // point at closing `"`, unless the last char is `\n` to account for `println`
-            let pos = match self.input.chars().last() {
-                Some('\n') => self.input.len(),
-                _ => self.input.len() + 1,
-            };
+            let description = format!("expected `{:?}` but string was terminated", c);
+            // point at closing `"`
+            let pos = self.input.len() - if self.append_newline { 1 } else { 0 };
+            let pos = self.to_span_index(pos);
             if c == '}' {
-                self.err_with_note(msg,
-                                   format!("expected `{:?}`", c),
-                                   "if you intended to print `{`, you can escape it using `{{`",
-                                   pos + padding,
-                                   pos + padding);
+                let label = format!("expected `{:?}`", c);
+                let (note, secondary_label) = if c == '}' {
+                    (Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
+                     self.last_opening_brace_pos.map(|pos| {
+                        ("because of this opening brace".to_owned(), pos, pos)
+                     }))
+                } else {
+                    (None, None)
+                };
+                self.errors.push(ParseError {
+                    description,
+                    note,
+                    label,
+                    start: pos,
+                    end: pos,
+                    secondary_label,
+                });
             } else {
-                self.err(msg, format!("expected `{:?}`", c), pos, pos);
+                self.err(description, format!("expected `{:?}`", c), pos, pos);
             }
             None
         }
     }
 
-    /// Consumes all whitespace characters until the first non-whitespace
-    /// character
+    /// Consumes all whitespace characters until the first non-whitespace character
     fn ws(&mut self) {
         while let Some(&(_, c)) = self.cur.peek() {
             if c.is_whitespace() {
@@ -334,8 +399,7 @@ impl<'a> Parser<'a> {
         &self.input[start..self.input.len()]
     }
 
-    /// Parses an Argument structure, or what's contained within braces inside
-    /// the format string
+    /// Parses an Argument structure, or what's contained within braces inside the format string
     fn argument(&mut self) -> Argument<'a> {
         let pos = self.position();
         let format = self.format();
@@ -371,8 +435,8 @@ impl<'a> Parser<'a> {
                     self.err_with_note(format!("invalid argument name `{}`", invalid_name),
                                        "invalid argument name",
                                        "argument names cannot start with an underscore",
-                                       pos + 1, // add 1 to account for leading `{`
-                                       pos + 1 + invalid_name.len());
+                                       self.to_span_index(pos),
+                                       self.to_span_index(pos + invalid_name.len()));
                     Some(ArgumentNamed(invalid_name))
                 },
 
@@ -553,7 +617,7 @@ mod tests {
     use super::*;
 
     fn same(fmt: &'static str, p: &[Piece<'static>]) {
-        let parser = Parser::new(fmt, None);
+        let parser = Parser::new(fmt, None, vec![], false);
         assert!(parser.collect::<Vec<Piece<'static>>>() == p);
     }
 
@@ -569,7 +633,7 @@ mod tests {
     }
 
     fn musterr(s: &str) {
-        let mut p = Parser::new(s, None);
+        let mut p = Parser::new(s, None, vec![], false);
         p.next();
         assert!(!p.errors.is_empty());
     }