Skip to content

Commit a1bad57

Browse files
committed
Auto merge of #57069 - estebank:str-err, r=@cramertj
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 ``` error: invalid format string: expected `'}'`, found `'t'` --> $DIR/ifmt-bad-arg.rs:85:1 | LL | ninth number: { | - because of this opening brace LL | tenth number: {}", | ^ expected `}` in format string | = note: if you intended to print `{`, you can escape it using `{{` ``` Fix #53837.
2 parents 79bbce4 + 862ebc4 commit a1bad57

File tree

9 files changed

+415
-60
lines changed

9 files changed

+415
-60
lines changed

src/libfmt_macros/lib.rs

+104-40
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,9 @@ pub struct ParseError {
123123
pub description: string::String,
124124
pub note: Option<string::String>,
125125
pub label: string::String,
126-
pub start: usize,
127-
pub end: usize,
126+
pub start: SpanIndex,
127+
pub end: SpanIndex,
128+
pub secondary_label: Option<(string::String, SpanIndex, SpanIndex)>,
128129
}
129130

130131
/// The parser structure for interpreting the input format string. This is
@@ -142,22 +143,39 @@ pub struct Parser<'a> {
142143
curarg: usize,
143144
/// `Some(raw count)` when the string is "raw", used to position spans correctly
144145
style: Option<usize>,
145-
/// How many newlines have been seen in the string so far, to adjust the error spans
146-
seen_newlines: usize,
147146
/// Start and end byte offset of every successfully parsed argument
148147
pub arg_places: Vec<(usize, usize)>,
148+
/// Characters that need to be shifted
149+
skips: Vec<usize>,
150+
/// Span offset of the last opening brace seen, used for error reporting
151+
last_opening_brace_pos: Option<SpanIndex>,
152+
/// Wether the source string is comes from `println!` as opposed to `format!` or `print!`
153+
append_newline: bool,
154+
}
155+
156+
#[derive(Clone, Copy, Debug)]
157+
pub struct SpanIndex(usize);
158+
159+
impl SpanIndex {
160+
pub fn unwrap(self) -> usize {
161+
self.0
162+
}
149163
}
150164

151165
impl<'a> Iterator for Parser<'a> {
152166
type Item = Piece<'a>;
153167

154168
fn next(&mut self) -> Option<Piece<'a>> {
155-
let raw = self.style.map(|raw| raw + self.seen_newlines).unwrap_or(0);
169+
let raw = self.raw();
156170
if let Some(&(pos, c)) = self.cur.peek() {
157171
match c {
158172
'{' => {
173+
let curr_last_brace = self.last_opening_brace_pos;
174+
self.last_opening_brace_pos = Some(self.to_span_index(pos));
159175
self.cur.next();
160176
if self.consume('{') {
177+
self.last_opening_brace_pos = curr_last_brace;
178+
161179
Some(String(self.string(pos + 1)))
162180
} else {
163181
let arg = self.argument();
@@ -174,7 +192,7 @@ impl<'a> Iterator for Parser<'a> {
174192
if self.consume('}') {
175193
Some(String(self.string(pos + 1)))
176194
} else {
177-
let err_pos = pos + raw + 1;
195+
let err_pos = self.to_span_index(pos);
178196
self.err_with_note(
179197
"unmatched `}` found",
180198
"unmatched `}`",
@@ -186,7 +204,6 @@ impl<'a> Iterator for Parser<'a> {
186204
}
187205
}
188206
'\n' => {
189-
self.seen_newlines += 1;
190207
Some(String(self.string(pos)))
191208
}
192209
_ => Some(String(self.string(pos))),
@@ -199,15 +216,22 @@ impl<'a> Iterator for Parser<'a> {
199216

200217
impl<'a> Parser<'a> {
201218
/// Creates a new parser for the given format string
202-
pub fn new(s: &'a str, style: Option<usize>) -> Parser<'a> {
219+
pub fn new(
220+
s: &'a str,
221+
style: Option<usize>,
222+
skips: Vec<usize>,
223+
append_newline: bool,
224+
) -> Parser<'a> {
203225
Parser {
204226
input: s,
205227
cur: s.char_indices().peekable(),
206228
errors: vec![],
207229
curarg: 0,
208230
style,
209-
seen_newlines: 0,
210231
arg_places: vec![],
232+
skips,
233+
last_opening_brace_pos: None,
234+
append_newline,
211235
}
212236
}
213237

@@ -218,15 +242,16 @@ impl<'a> Parser<'a> {
218242
&mut self,
219243
description: S1,
220244
label: S2,
221-
start: usize,
222-
end: usize,
245+
start: SpanIndex,
246+
end: SpanIndex,
223247
) {
224248
self.errors.push(ParseError {
225249
description: description.into(),
226250
note: None,
227251
label: label.into(),
228252
start,
229253
end,
254+
secondary_label: None,
230255
});
231256
}
232257

@@ -238,15 +263,16 @@ impl<'a> Parser<'a> {
238263
description: S1,
239264
label: S2,
240265
note: S3,
241-
start: usize,
242-
end: usize,
266+
start: SpanIndex,
267+
end: SpanIndex,
243268
) {
244269
self.errors.push(ParseError {
245270
description: description.into(),
246271
note: Some(note.into()),
247272
label: label.into(),
248273
start,
249274
end,
275+
secondary_label: None,
250276
});
251277
}
252278

@@ -266,47 +292,86 @@ impl<'a> Parser<'a> {
266292
}
267293
}
268294

295+
fn raw(&self) -> usize {
296+
self.style.map(|raw| raw + 1).unwrap_or(0)
297+
}
298+
299+
fn to_span_index(&self, pos: usize) -> SpanIndex {
300+
let mut pos = pos;
301+
for skip in &self.skips {
302+
if pos > *skip {
303+
pos += 1;
304+
} else if pos == *skip && self.raw() == 0 {
305+
pos += 1;
306+
} else {
307+
break;
308+
}
309+
}
310+
SpanIndex(self.raw() + pos + 1)
311+
}
312+
269313
/// Forces consumption of the specified character. If the character is not
270314
/// found, an error is emitted.
271315
fn must_consume(&mut self, c: char) -> Option<usize> {
272316
self.ws();
273-
let raw = self.style.unwrap_or(0);
274317

275-
let padding = raw + self.seen_newlines;
276318
if let Some(&(pos, maybe)) = self.cur.peek() {
277319
if c == maybe {
278320
self.cur.next();
279321
Some(pos)
280322
} else {
281-
let pos = pos + raw + 1;
282-
self.err(format!("expected `{:?}`, found `{:?}`", c, maybe),
283-
format!("expected `{}`", c),
284-
pos,
285-
pos);
323+
let pos = self.to_span_index(pos);
324+
let description = format!("expected `'}}'`, found `{:?}`", maybe);
325+
let label = "expected `}`".to_owned();
326+
let (note, secondary_label) = if c == '}' {
327+
(Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
328+
self.last_opening_brace_pos.map(|pos| {
329+
("because of this opening brace".to_owned(), pos, pos)
330+
}))
331+
} else {
332+
(None, None)
333+
};
334+
self.errors.push(ParseError {
335+
description,
336+
note,
337+
label,
338+
start: pos,
339+
end: pos,
340+
secondary_label,
341+
});
286342
None
287343
}
288344
} else {
289-
let msg = format!("expected `{:?}` but string was terminated", c);
290-
// point at closing `"`, unless the last char is `\n` to account for `println`
291-
let pos = match self.input.chars().last() {
292-
Some('\n') => self.input.len(),
293-
_ => self.input.len() + 1,
294-
};
345+
let description = format!("expected `{:?}` but string was terminated", c);
346+
// point at closing `"`
347+
let pos = self.input.len() - if self.append_newline { 1 } else { 0 };
348+
let pos = self.to_span_index(pos);
295349
if c == '}' {
296-
self.err_with_note(msg,
297-
format!("expected `{:?}`", c),
298-
"if you intended to print `{`, you can escape it using `{{`",
299-
pos + padding,
300-
pos + padding);
350+
let label = format!("expected `{:?}`", c);
351+
let (note, secondary_label) = if c == '}' {
352+
(Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
353+
self.last_opening_brace_pos.map(|pos| {
354+
("because of this opening brace".to_owned(), pos, pos)
355+
}))
356+
} else {
357+
(None, None)
358+
};
359+
self.errors.push(ParseError {
360+
description,
361+
note,
362+
label,
363+
start: pos,
364+
end: pos,
365+
secondary_label,
366+
});
301367
} else {
302-
self.err(msg, format!("expected `{:?}`", c), pos, pos);
368+
self.err(description, format!("expected `{:?}`", c), pos, pos);
303369
}
304370
None
305371
}
306372
}
307373

308-
/// Consumes all whitespace characters until the first non-whitespace
309-
/// character
374+
/// Consumes all whitespace characters until the first non-whitespace character
310375
fn ws(&mut self) {
311376
while let Some(&(_, c)) = self.cur.peek() {
312377
if c.is_whitespace() {
@@ -334,8 +399,7 @@ impl<'a> Parser<'a> {
334399
&self.input[start..self.input.len()]
335400
}
336401

337-
/// Parses an Argument structure, or what's contained within braces inside
338-
/// the format string
402+
/// Parses an Argument structure, or what's contained within braces inside the format string
339403
fn argument(&mut self) -> Argument<'a> {
340404
let pos = self.position();
341405
let format = self.format();
@@ -371,8 +435,8 @@ impl<'a> Parser<'a> {
371435
self.err_with_note(format!("invalid argument name `{}`", invalid_name),
372436
"invalid argument name",
373437
"argument names cannot start with an underscore",
374-
pos + 1, // add 1 to account for leading `{`
375-
pos + 1 + invalid_name.len());
438+
self.to_span_index(pos),
439+
self.to_span_index(pos + invalid_name.len()));
376440
Some(ArgumentNamed(invalid_name))
377441
},
378442

@@ -553,7 +617,7 @@ mod tests {
553617
use super::*;
554618

555619
fn same(fmt: &'static str, p: &[Piece<'static>]) {
556-
let parser = Parser::new(fmt, None);
620+
let parser = Parser::new(fmt, None, vec![], false);
557621
assert!(parser.collect::<Vec<Piece<'static>>>() == p);
558622
}
559623

@@ -569,7 +633,7 @@ mod tests {
569633
}
570634

571635
fn musterr(s: &str) {
572-
let mut p = Parser::new(s, None);
636+
let mut p = Parser::new(s, None, vec![], false);
573637
p.next();
574638
assert!(!p.errors.is_empty());
575639
}

src/librustc/traits/on_unimplemented.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ impl<'a, 'gcx, 'tcx> OnUnimplementedFormatString {
234234
{
235235
let name = tcx.item_name(trait_def_id);
236236
let generics = tcx.generics_of(trait_def_id);
237-
let parser = Parser::new(&self.0, None);
237+
let parser = Parser::new(&self.0, None, vec![], false);
238238
let mut result = Ok(());
239239
for token in parser {
240240
match token {
@@ -293,7 +293,7 @@ impl<'a, 'gcx, 'tcx> OnUnimplementedFormatString {
293293
}).collect::<FxHashMap<String, String>>();
294294
let empty_string = String::new();
295295

296-
let parser = Parser::new(&self.0, None);
296+
let parser = Parser::new(&self.0, None, vec![], false);
297297
parser.map(|p|
298298
match p {
299299
Piece::String(s) => s,

0 commit comments

Comments
 (0)