Skip to content

Commit dab5d8e

Browse files
authored
Rollup merge of rust-lang#150765 - ua/missing-colon, r=estebank
rustc_parse_format: improve error for missing `:` before `?` in format args Detect the `{ident?}` pattern where `?` is immediately followed by `}` and emit a clearer diagnostic explaining that `:` is required for Debug formatting. This avoids falling back to a generic “invalid format string” error and adds a targeted UI test for the case.
2 parents f5edc9b + b0e65da commit dab5d8e

6 files changed

Lines changed: 55 additions & 5 deletions

File tree

compiler/rustc_builtin_macros/messages.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ builtin_macros_expected_other = expected operand, {$is_inline_asm ->
182182
183183
builtin_macros_export_macro_rules = cannot export macro_rules! macros from a `proc-macro` crate type currently
184184
185+
builtin_macros_format_add_missing_colon = add a colon before the format specifier
186+
185187
builtin_macros_format_duplicate_arg = duplicate argument named `{$ident}`
186188
.label1 = previously here
187189
.label2 = duplicate argument

compiler/rustc_builtin_macros/src/errors.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,15 @@ pub(crate) enum InvalidFormatStringSuggestion {
643643
span: Span,
644644
replacement: String,
645645
},
646+
#[suggestion(
647+
builtin_macros_format_add_missing_colon,
648+
code = ":?",
649+
applicability = "machine-applicable"
650+
)]
651+
AddMissingColon {
652+
#[primary_span]
653+
span: Span,
654+
},
646655
}
647656

648657
#[derive(Diagnostic)]

compiler/rustc_builtin_macros/src/format.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,10 @@ fn make_format_args(
329329
replacement,
330330
});
331331
}
332+
parse::Suggestion::AddMissingColon(span) => {
333+
let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
334+
e.sugg_ = Some(errors::InvalidFormatStringSuggestion::AddMissingColon { span });
335+
}
332336
}
333337
let guar = ecx.dcx().emit_err(e);
334338
return ExpandResult::Ready(Err(guar));

compiler/rustc_parse_format/src/lib.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ pub enum Suggestion {
184184
/// `format!("{foo:?x}")` -> `format!("{foo:x?}")`
185185
/// `format!("{foo:?X}")` -> `format!("{foo:X?}")`
186186
ReorderFormatParameter(Range<usize>, String),
187+
/// Add missing colon:
188+
/// `format!("{foo?}")` -> `format!("{foo:?}")`
189+
AddMissingColon(Range<usize>),
187190
}
188191

189192
/// The parser structure for interpreting the input format string. This is
@@ -453,10 +456,11 @@ impl<'input> Parser<'input> {
453456
suggestion: Suggestion::None,
454457
});
455458

456-
if let Some((_, _, c)) = self.peek() {
457-
match c {
458-
'?' => self.suggest_format_debug(),
459-
'<' | '^' | '>' => self.suggest_format_align(c),
459+
if let (Some((_, _, c)), Some((_, _, nc))) = (self.peek(), self.peek_ahead()) {
460+
match (c, nc) {
461+
('?', '}') => self.missing_colon_before_debug_formatter(),
462+
('?', _) => self.suggest_format_debug(),
463+
('<' | '^' | '>', _) => self.suggest_format_align(c),
460464
_ => self.suggest_positional_arg_instead_of_captured_arg(arg),
461465
}
462466
}
@@ -849,6 +853,23 @@ impl<'input> Parser<'input> {
849853
}
850854
}
851855

856+
fn missing_colon_before_debug_formatter(&mut self) {
857+
if let Some((range, _)) = self.consume_pos('?') {
858+
let span = range.clone();
859+
self.errors.insert(
860+
0,
861+
ParseError {
862+
description: "expected `}`, found `?`".to_owned(),
863+
note: Some(format!("to print `{{`, you can escape it using `{{{{`",)),
864+
label: "expected `:` before `?` to format with `Debug`".to_owned(),
865+
span: range,
866+
secondary_label: None,
867+
suggestion: Suggestion::AddMissingColon(span),
868+
},
869+
);
870+
}
871+
}
872+
852873
fn suggest_format_align(&mut self, alignment: char) {
853874
if let Some((range, _)) = self.consume_pos(alignment) {
854875
self.errors.insert(

tests/ui/fmt/format-string-error-2.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,7 @@ raw { \n
8383

8484
println!(r#"\x7B}\u8 {"#, 1);
8585
//~^ ERROR invalid format string: unmatched `}` found
86+
87+
println!("{x?}, world!",);
88+
//~^ ERROR invalid format string: expected `}`, found `?`
8689
}

tests/ui/fmt/format-string-error-2.stderr

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,5 +177,16 @@ LL | println!(r#"\x7B}\u8 {"#, 1);
177177
|
178178
= note: if you intended to print `}`, you can escape it using `}}`
179179

180-
error: aborting due to 18 previous errors
180+
error: invalid format string: expected `}`, found `?`
181+
--> $DIR/format-string-error-2.rs:87:17
182+
|
183+
LL | println!("{x?}, world!",);
184+
| ^
185+
| |
186+
| expected `:` before `?` to format with `Debug` in format string
187+
| help: add a colon before the format specifier: `:?`
188+
|
189+
= note: to print `{`, you can escape it using `{{`
190+
191+
error: aborting due to 19 previous errors
181192

0 commit comments

Comments
 (0)