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.

Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
This commit is contained in:
Usman Akinyemi 2026-01-07 19:01:19 +05:30
parent d9617c8d9a
commit b0e65da2af
6 changed files with 55 additions and 5 deletions

View file

@ -182,6 +182,8 @@ builtin_macros_expected_other = expected operand, {$is_inline_asm ->
builtin_macros_export_macro_rules = cannot export macro_rules! macros from a `proc-macro` crate type currently
builtin_macros_format_add_missing_colon = add a colon before the format specifier
builtin_macros_format_duplicate_arg = duplicate argument named `{$ident}`
.label1 = previously here
.label2 = duplicate argument

View file

@ -643,6 +643,15 @@ pub(crate) enum InvalidFormatStringSuggestion {
span: Span,
replacement: String,
},
#[suggestion(
builtin_macros_format_add_missing_colon,
code = ":?",
applicability = "machine-applicable"
)]
AddMissingColon {
#[primary_span]
span: Span,
},
}
#[derive(Diagnostic)]

View file

@ -329,6 +329,10 @@ fn make_format_args(
replacement,
});
}
parse::Suggestion::AddMissingColon(span) => {
let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
e.sugg_ = Some(errors::InvalidFormatStringSuggestion::AddMissingColon { span });
}
}
let guar = ecx.dcx().emit_err(e);
return ExpandResult::Ready(Err(guar));

View file

@ -184,6 +184,9 @@ pub enum Suggestion {
/// `format!("{foo:?x}")` -> `format!("{foo:x?}")`
/// `format!("{foo:?X}")` -> `format!("{foo:X?}")`
ReorderFormatParameter(Range<usize>, String),
/// Add missing colon:
/// `format!("{foo?}")` -> `format!("{foo:?}")`
AddMissingColon(Range<usize>),
}
/// The parser structure for interpreting the input format string. This is
@ -453,10 +456,11 @@ impl<'input> Parser<'input> {
suggestion: Suggestion::None,
});
if let Some((_, _, c)) = self.peek() {
match c {
'?' => self.suggest_format_debug(),
'<' | '^' | '>' => self.suggest_format_align(c),
if let (Some((_, _, c)), Some((_, _, nc))) = (self.peek(), self.peek_ahead()) {
match (c, nc) {
('?', '}') => self.missing_colon_before_debug_formatter(),
('?', _) => self.suggest_format_debug(),
('<' | '^' | '>', _) => self.suggest_format_align(c),
_ => self.suggest_positional_arg_instead_of_captured_arg(arg),
}
}
@ -849,6 +853,23 @@ impl<'input> Parser<'input> {
}
}
fn missing_colon_before_debug_formatter(&mut self) {
if let Some((range, _)) = self.consume_pos('?') {
let span = range.clone();
self.errors.insert(
0,
ParseError {
description: "expected `}`, found `?`".to_owned(),
note: Some(format!("to print `{{`, you can escape it using `{{{{`",)),
label: "expected `:` before `?` to format with `Debug`".to_owned(),
span: range,
secondary_label: None,
suggestion: Suggestion::AddMissingColon(span),
},
);
}
}
fn suggest_format_align(&mut self, alignment: char) {
if let Some((range, _)) = self.consume_pos(alignment) {
self.errors.insert(

View file

@ -83,4 +83,7 @@ raw { \n
println!(r#"\x7B}\u8 {"#, 1);
//~^ ERROR invalid format string: unmatched `}` found
println!("{x?}, world!",);
//~^ ERROR invalid format string: expected `}`, found `?`
}

View file

@ -177,5 +177,16 @@ LL | println!(r#"\x7B}\u8 {"#, 1);
|
= note: if you intended to print `}`, you can escape it using `}}`
error: aborting due to 18 previous errors
error: invalid format string: expected `}`, found `?`
--> $DIR/format-string-error-2.rs:87:17
|
LL | println!("{x?}, world!",);
| ^
| |
| expected `:` before `?` to format with `Debug` in format string
| help: add a colon before the format specifier: `:?`
|
= note: to print `{`, you can escape it using `{{`
error: aborting due to 19 previous errors