Skip to content

Commit f4306ff

Browse files
author
Esteban Küber
committed
Use correct spans for format string errors
When encountering format string errors in a raw string, or regular string literal with embedded newlines, account for the positional change to use correct spans. :drive by fix: 🚗
1 parent 154dee2 commit f4306ff

File tree

7 files changed

+123
-21
lines changed

7 files changed

+123
-21
lines changed

src/Cargo.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ dependencies = [
715715
[[package]]
716716
name = "fmt_macros"
717717
version = "0.0.0"
718+
dependencies = [
719+
"syntax 0.0.0",
720+
]
718721

719722
[[package]]
720723
name = "fnv"

src/libfmt_macros/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ version = "0.0.0"
77
name = "fmt_macros"
88
path = "lib.rs"
99
crate-type = ["dylib"]
10+
11+
[dependencies]
12+
syntax = { path = "../libsyntax" }

src/libfmt_macros/lib.rs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub use self::Alignment::*;
2828
pub use self::Flag::*;
2929
pub use self::Count::*;
3030

31+
extern crate syntax;
32+
3133
use std::str;
3234
use std::string;
3335
use std::iter;
@@ -150,27 +152,36 @@ pub struct Parser<'a> {
150152
pub errors: Vec<ParseError>,
151153
/// Current position of implicit positional argument pointer
152154
curarg: usize,
155+
/// The style of the string (raw or not), used to position spans correctly
156+
style: syntax::ast::StrStyle,
157+
/// How many newlines have been seen in the string so far, to adjust the error spans
158+
seen_newlines: usize,
153159
}
154160

155161
impl<'a> Iterator for Parser<'a> {
156162
type Item = Piece<'a>;
157163

158164
fn next(&mut self) -> Option<Piece<'a>> {
165+
let raw = match self.style {
166+
syntax::ast::StrStyle::Raw(raw) => raw as usize + self.seen_newlines,
167+
_ => 0,
168+
};
159169
if let Some(&(pos, c)) = self.cur.peek() {
160170
match c {
161171
'{' => {
172+
let pos = pos + raw + 1;
162173
self.cur.next();
163174
if self.consume('{') {
164-
Some(String(self.string(pos + 1)))
175+
Some(String(self.string(pos)))
165176
} else {
166177
let ret = Some(NextArgument(self.argument()));
167178
self.must_consume('}');
168179
ret
169180
}
170181
}
171182
'}' => {
183+
let pos = pos + raw + 1;
172184
self.cur.next();
173-
let pos = pos + 1;
174185
if self.consume('}') {
175186
Some(String(self.string(pos)))
176187
} else {
@@ -184,6 +195,10 @@ impl<'a> Iterator for Parser<'a> {
184195
None
185196
}
186197
}
198+
'\n' => {
199+
self.seen_newlines += 1;
200+
Some(String(self.string(pos)))
201+
}
187202
_ => Some(String(self.string(pos))),
188203
}
189204
} else {
@@ -194,12 +209,14 @@ impl<'a> Iterator for Parser<'a> {
194209

195210
impl<'a> Parser<'a> {
196211
/// Creates a new parser for the given format string
197-
pub fn new(s: &'a str) -> Parser<'a> {
212+
pub fn new(s: &'a str, style: syntax::ast::StrStyle) -> Parser<'a> {
198213
Parser {
199214
input: s,
200215
cur: s.char_indices().peekable(),
201216
errors: vec![],
202217
curarg: 0,
218+
style,
219+
seen_newlines: 0,
203220
}
204221
}
205222

@@ -262,14 +279,19 @@ impl<'a> Parser<'a> {
262279
/// found, an error is emitted.
263280
fn must_consume(&mut self, c: char) {
264281
self.ws();
282+
let raw = match self.style {
283+
syntax::ast::StrStyle::Raw(raw) => raw as usize,
284+
_ => 0,
285+
};
286+
let padding = raw + self.seen_newlines;
265287
if let Some(&(pos, maybe)) = self.cur.peek() {
266288
if c == maybe {
267289
self.cur.next();
268290
} else {
269291
self.err(format!("expected `{:?}`, found `{:?}`", c, maybe),
270292
format!("expected `{}`", c),
271-
pos + 1,
272-
pos + 1);
293+
pos + padding + 1,
294+
pos + padding + 1);
273295
}
274296
} else {
275297
let msg = format!("expected `{:?}` but string was terminated", c);
@@ -282,8 +304,8 @@ impl<'a> Parser<'a> {
282304
self.err_with_note(msg,
283305
format!("expected `{:?}`", c),
284306
"if you intended to print `{`, you can escape it using `{{`",
285-
pos,
286-
pos);
307+
pos + padding,
308+
pos + padding);
287309
} else {
288310
self.err(msg, format!("expected `{:?}`", c), pos, pos);
289311
}
@@ -540,7 +562,7 @@ mod tests {
540562
use super::*;
541563

542564
fn same(fmt: &'static str, p: &[Piece<'static>]) {
543-
let parser = Parser::new(fmt);
565+
let parser = Parser::new(fmt, syntax::ast::StrStyle::Cooked);
544566
assert!(parser.collect::<Vec<Piece<'static>>>() == p);
545567
}
546568

@@ -556,7 +578,7 @@ mod tests {
556578
}
557579

558580
fn musterr(s: &str) {
559-
let mut p = Parser::new(s);
581+
let mut p = Parser::new(fmt, syntax::ast::StrStyle::Cooked);
560582
p.next();
561583
assert!(!p.errors.is_empty());
562584
}

src/librustc/traits/on_unimplemented.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ty::{self, TyCtxt, GenericParamDefKind};
1515
use util::common::ErrorReported;
1616
use util::nodemap::FxHashMap;
1717

18-
use syntax::ast::{MetaItem, NestedMetaItem};
18+
use syntax::ast::{self, MetaItem, NestedMetaItem};
1919
use syntax::attr;
2020
use syntax_pos::Span;
2121
use syntax_pos::symbol::LocalInternedString;
@@ -242,7 +242,7 @@ impl<'a, 'gcx, 'tcx> OnUnimplementedFormatString {
242242
{
243243
let name = tcx.item_name(trait_def_id);
244244
let generics = tcx.generics_of(trait_def_id);
245-
let parser = Parser::new(&self.0);
245+
let parser = Parser::new(&self.0, ast::StrStyle::Cooked);
246246
let mut result = Ok(());
247247
for token in parser {
248248
match token {
@@ -298,7 +298,7 @@ impl<'a, 'gcx, 'tcx> OnUnimplementedFormatString {
298298
Some((name, value))
299299
}).collect::<FxHashMap<String, String>>();
300300

301-
let parser = Parser::new(&self.0);
301+
let parser = Parser::new(&self.0, ast::StrStyle::Cooked);
302302
parser.map(|p| {
303303
match p {
304304
Piece::String(s) => s,

src/libsyntax_ext/format.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
763763
};
764764

765765
let fmt_str = &*fmt.node.0.as_str();
766-
let mut parser = parse::Parser::new(fmt_str);
766+
let mut parser = parse::Parser::new(fmt_str, fmt.node.1);
767767
let mut pieces = vec![];
768768

769769
while let Some(mut piece) = parser.next() {

src/test/ui/fmt/format-string-error.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
// ignore-tidy-tab
12+
1113
fn main() {
1214
println!("{");
1315
//~^ ERROR invalid format string: expected `'}'` but string was terminated
@@ -24,4 +26,36 @@ fn main() {
2426
//~^ ERROR invalid format string: unmatched `}` found
2527
let _ = format!("{\\}");
2628
//~^ ERROR invalid format string: expected `'}'`, found `'\\'`
29+
let _ = format!("\n\n\n{\n\n\n");
30+
//~^ ERROR invalid format string
31+
let _ = format!(r###"
32+
33+
34+
35+
{"###);
36+
//~^ ERROR invalid format string
37+
let _ = format!(r###"
38+
39+
40+
41+
{
42+
43+
"###);
44+
//~^^ ERROR invalid format string
45+
let _ = format!(r###"
46+
47+
48+
49+
}
50+
51+
"###);
52+
//~^^^ ERROR invalid format string
53+
let _ = format!(r###"
54+
55+
56+
57+
}
58+
59+
"###);
60+
//~^^^ ERROR invalid format string: unmatched `}` found
2761
}
Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,96 @@
11
error: invalid format string: expected `'}'` but string was terminated
2-
--> $DIR/format-string-error.rs:12:16
2+
--> $DIR/format-string-error.rs:14:16
33
|
44
LL | println!("{");
55
| ^ expected `'}'` in format string
66
|
77
= note: if you intended to print `{`, you can escape it using `{{`
88

99
error: invalid format string: unmatched `}` found
10-
--> $DIR/format-string-error.rs:15:15
10+
--> $DIR/format-string-error.rs:17:15
1111
|
1212
LL | println!("}");
1313
| ^ unmatched `}` in format string
1414
|
1515
= note: if you intended to print `}`, you can escape it using `}}`
1616

1717
error: invalid format string: invalid argument name `_foo`
18-
--> $DIR/format-string-error.rs:17:23
18+
--> $DIR/format-string-error.rs:19:23
1919
|
2020
LL | let _ = format!("{_foo}", _foo = 6usize);
2121
| ^^^^ invalid argument name in format string
2222
|
2323
= note: argument names cannot start with an underscore
2424

2525
error: invalid format string: invalid argument name `_`
26-
--> $DIR/format-string-error.rs:19:23
26+
--> $DIR/format-string-error.rs:21:23
2727
|
2828
LL | let _ = format!("{_}", _ = 6usize);
2929
| ^ invalid argument name in format string
3030
|
3131
= note: argument names cannot start with an underscore
3232

3333
error: invalid format string: expected `'}'` but string was terminated
34-
--> $DIR/format-string-error.rs:21:23
34+
--> $DIR/format-string-error.rs:23:23
3535
|
3636
LL | let _ = format!("{");
3737
| ^ expected `'}'` in format string
3838
|
3939
= note: if you intended to print `{`, you can escape it using `{{`
4040

4141
error: invalid format string: unmatched `}` found
42-
--> $DIR/format-string-error.rs:23:22
42+
--> $DIR/format-string-error.rs:25:22
4343
|
4444
LL | let _ = format!("}");
4545
| ^ unmatched `}` in format string
4646
|
4747
= note: if you intended to print `}`, you can escape it using `}}`
4848

4949
error: invalid format string: expected `'}'`, found `'/'`
50-
--> $DIR/format-string-error.rs:25:23
50+
--> $DIR/format-string-error.rs:27:23
5151
|
5252
LL | let _ = format!("{/}");
5353
| ^ expected `}` in format string
5454

55-
error: aborting due to 7 previous errors
55+
error: invalid format string: expected `'}'` but string was terminated
56+
--> $DIR/format-string-error.rs:29:29
57+
|
58+
LL | let _ = format!("/n/n/n{/n/n/n");
59+
| ^ expected `'}'` in format string
60+
|
61+
= note: if you intended to print `{`, you can escape it using `{{`
62+
63+
error: invalid format string: expected `'}'` but string was terminated
64+
--> $DIR/format-string-error.rs:35:3
65+
|
66+
LL | {"###);
67+
| ^ expected `'}'` in format string
68+
|
69+
= note: if you intended to print `{`, you can escape it using `{{`
70+
71+
error: invalid format string: expected `'}'` but string was terminated
72+
--> $DIR/format-string-error.rs:42:1
73+
|
74+
LL |
75+
| ^ expected `'}'` in format string
76+
|
77+
= note: if you intended to print `{`, you can escape it using `{{`
78+
79+
error: invalid format string: unmatched `}` found
80+
--> $DIR/format-string-error.rs:49:2
81+
|
82+
LL | }
83+
| ^ unmatched `}` in format string
84+
|
85+
= note: if you intended to print `}`, you can escape it using `}}`
86+
87+
error: invalid format string: unmatched `}` found
88+
--> $DIR/format-string-error.rs:57:9
89+
|
90+
LL | }
91+
| ^ unmatched `}` in format string
92+
|
93+
= note: if you intended to print `}`, you can escape it using `}}`
94+
95+
error: aborting due to 12 previous errors
5696

0 commit comments

Comments
 (0)