Skip to content

Commit a872762

Browse files
Improve error message when closing bracket interpreted as formatting fill character
1 parent fe4d1f9 commit a872762

File tree

3 files changed

+65
-58
lines changed

3 files changed

+65
-58
lines changed

compiler/rustc_parse_format/src/lib.rs

+45-58
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ pub struct Argument<'a> {
109109
pub struct FormatSpec<'a> {
110110
/// Optionally specified character to fill alignment with.
111111
pub fill: Option<char>,
112+
/// Span of the optionally specified fill character.
113+
pub fill_span: Option<InnerSpan>,
112114
/// Optionally specified alignment.
113115
pub align: Alignment,
114116
/// The `+` or `-` flag.
@@ -264,7 +266,7 @@ impl<'a> Iterator for Parser<'a> {
264266
Some(String(self.string(pos + 1)))
265267
} else {
266268
let arg = self.argument(lbrace_end);
267-
if let Some(rbrace_pos) = self.must_consume('}') {
269+
if let Some(rbrace_pos) = self.consume_closing_brace(&arg) {
268270
if self.is_source_literal {
269271
let lbrace_byte_pos = self.to_span_index(pos);
270272
let rbrace_byte_pos = self.to_span_index(rbrace_pos);
@@ -450,69 +452,51 @@ impl<'a> Parser<'a> {
450452

451453
/// Forces consumption of the specified character. If the character is not
452454
/// found, an error is emitted.
453-
fn must_consume(&mut self, c: char) -> Option<usize> {
455+
fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<usize> {
454456
self.ws();
455457

456-
if let Some(&(pos, maybe)) = self.cur.peek() {
457-
if c == maybe {
458+
let pos;
459+
let description;
460+
461+
if let Some(&(peek_pos, maybe)) = self.cur.peek() {
462+
if maybe == '}' {
458463
self.cur.next();
459-
Some(pos)
460-
} else {
461-
let pos = self.to_span_index(pos);
462-
let description = format!("expected `'}}'`, found `{maybe:?}`");
463-
let label = "expected `'}'`".to_owned();
464-
let (note, secondary_label) = if c == '}' {
465-
(
466-
Some(
467-
"if you intended to print `{`, you can escape it using `{{`".to_owned(),
468-
),
469-
self.last_opening_brace
470-
.map(|sp| ("because of this opening brace".to_owned(), sp)),
471-
)
472-
} else {
473-
(None, None)
474-
};
475-
self.errors.push(ParseError {
476-
description,
477-
note,
478-
label,
479-
span: pos.to(pos),
480-
secondary_label,
481-
should_be_replaced_with_positional_argument: false,
482-
});
483-
None
464+
return Some(peek_pos);
484465
}
466+
467+
pos = peek_pos;
468+
description = format!("expected `'}}'`, found `{maybe:?}`");
485469
} else {
486-
let description = format!("expected `{c:?}` but string was terminated");
470+
description = "expected `'}'` but string was terminated".to_owned();
487471
// point at closing `"`
488-
let pos = self.input.len() - if self.append_newline { 1 } else { 0 };
489-
let pos = self.to_span_index(pos);
490-
if c == '}' {
491-
let label = format!("expected `{c:?}`");
492-
let (note, secondary_label) = if c == '}' {
493-
(
494-
Some(
495-
"if you intended to print `{`, you can escape it using `{{`".to_owned(),
496-
),
497-
self.last_opening_brace
498-
.map(|sp| ("because of this opening brace".to_owned(), sp)),
499-
)
500-
} else {
501-
(None, None)
502-
};
503-
self.errors.push(ParseError {
504-
description,
505-
note,
506-
label,
507-
span: pos.to(pos),
508-
secondary_label,
509-
should_be_replaced_with_positional_argument: false,
510-
});
511-
} else {
512-
self.err(description, format!("expected `{c:?}`"), pos.to(pos));
513-
}
514-
None
472+
pos = self.input.len() - if self.append_newline { 1 } else { 0 };
515473
}
474+
475+
let pos = self.to_span_index(pos);
476+
477+
let label = "expected `'}'`".to_owned();
478+
let (note, secondary_label) = if arg.format.fill == Some('}') {
479+
(
480+
Some("the character `'}'` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
481+
arg.format.fill_span.map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
482+
)
483+
} else {
484+
(
485+
Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
486+
self.last_opening_brace.map(|sp| ("because of this opening brace".to_owned(), sp)),
487+
)
488+
};
489+
490+
self.errors.push(ParseError {
491+
description,
492+
note,
493+
label,
494+
span: pos.to(pos),
495+
secondary_label,
496+
should_be_replaced_with_positional_argument: false,
497+
});
498+
499+
None
516500
}
517501

518502
/// Consumes all whitespace characters until the first non-whitespace character
@@ -608,6 +592,7 @@ impl<'a> Parser<'a> {
608592
fn format(&mut self) -> FormatSpec<'a> {
609593
let mut spec = FormatSpec {
610594
fill: None,
595+
fill_span: None,
611596
align: AlignUnknown,
612597
sign: None,
613598
alternate: false,
@@ -625,9 +610,10 @@ impl<'a> Parser<'a> {
625610
}
626611

627612
// fill character
628-
if let Some(&(_, c)) = self.cur.peek() {
613+
if let Some(&(idx, c)) = self.cur.peek() {
629614
if let Some((_, '>' | '<' | '^')) = self.cur.clone().nth(1) {
630615
spec.fill = Some(c);
616+
spec.fill_span = Some(self.span(idx, idx + 1));
631617
self.cur.next();
632618
}
633619
}
@@ -722,6 +708,7 @@ impl<'a> Parser<'a> {
722708
fn inline_asm(&mut self) -> FormatSpec<'a> {
723709
let mut spec = FormatSpec {
724710
fill: None,
711+
fill_span: None,
725712
align: AlignUnknown,
726713
sign: None,
727714
alternate: false,

tests/ui/fmt/closing-brace-as-fill.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// issue: 112732
2+
3+
// `}` is typoed since it is interpreted as a fill character rather than a closing bracket
4+
5+
fn main() {
6+
println!("Hello, world! {0:}<3", 2);
7+
//~^ ERROR invalid format string: expected `'}'` but string was terminated
8+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error: invalid format string: expected `'}'` but string was terminated
2+
--> $DIR/closing-brace-as-fill.rs:6:35
3+
|
4+
LL | println!("Hello, world! {0:}<3", 2);
5+
| - ^ expected `'}'` in format string
6+
| |
7+
| this is not interpreted as a formatting closing brace
8+
|
9+
= note: the character `'}'` is interpreted as a fill character because of the `:` that precedes it
10+
11+
error: aborting due to previous error
12+

0 commit comments

Comments
 (0)