Skip to content

Commit f6d18db

Browse files
committed
Use string literal directly when available in format
Previous implementation used the `Parser::parse_expr` function in order to extract the format expression. If the first comma following the format expression was mistakenly replaced with a dot, then the next format expression was eaten by the function, because it looked as a syntactically valid expression, which resulted in incorrectly spanned error messages. The way the format expression is exctracted is changed: we first look at the first available token in the first argument supplied to the `format!` macro call. If it is a string literal, then it is promoted as a format expression immediatly, otherwise we fall back to the original `parse_expr`-related method. This allows us to ensure that the parser won't consume too much tokens when a typo is made. A test has been created so that it is ensured that the issue is properly fixed.
1 parent 85fbf49 commit f6d18db

File tree

4 files changed

+75
-2
lines changed

4 files changed

+75
-2
lines changed

compiler/rustc_builtin_macros/src/format.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,26 @@ fn parse_args<'a>(
135135
return Err(ecx.struct_span_err(sp, "requires at least a format string argument"));
136136
}
137137

138-
let fmtstr = p.parse_expr()?;
138+
let first_token = &p.token;
139+
let fmtstr = match first_token.kind {
140+
token::TokenKind::Literal(token::Lit {
141+
kind: token::LitKind::Str | token::LitKind::StrRaw(_),
142+
..
143+
}) => {
144+
// If the first token is a string literal, then a format expression
145+
// is constructed from it.
146+
//
147+
// This allows us to properly handle cases when the first comma
148+
// after the format string is mistakenly replaced with any operator,
149+
// which cause the expression parser to eat too much tokens.
150+
p.parse_literal_maybe_minus()?
151+
}
152+
_ => {
153+
// Otherwise, we fall back to the expression parser.
154+
p.parse_expr()?
155+
}
156+
};
157+
139158
let mut first = true;
140159
let mut named = false;
141160

compiler/rustc_parse/src/parser/expr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,7 @@ impl<'a> Parser<'a> {
14801480

14811481
/// Matches `'-' lit | lit` (cf. `ast_validation::AstValidator::check_expr_within_pat`).
14821482
/// Keep this in sync with `Token::can_begin_literal_maybe_minus`.
1483-
pub(super) fn parse_literal_maybe_minus(&mut self) -> PResult<'a, P<Expr>> {
1483+
pub fn parse_literal_maybe_minus(&mut self) -> PResult<'a, P<Expr>> {
14841484
maybe_whole_expr!(self);
14851485

14861486
let lo = self.token.span;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Allows to track issue #75492:
2+
// https://github.com/rust-lang/rust/issues/75492
3+
4+
use std::iter;
5+
6+
fn main() {
7+
format!("A number: {}". iter::once(42).next().unwrap());
8+
//~^ ERROR expected token: `,`
9+
10+
// Other kind of types are also checked:
11+
12+
format!("A number: {}" / iter::once(42).next().unwrap());
13+
//~^ ERROR expected token: `,`
14+
15+
format!("A number: {}"; iter::once(42).next().unwrap());
16+
//~^ ERROR expected token: `,`
17+
18+
// Note: this character is an COMBINING COMMA BELOW unicode char
19+
format!("A number: {}" ̦ iter::once(42).next().unwrap());
20+
//~^ ERROR expected token: `,`
21+
//~^^ ERROR unknown start of token: \u{326}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: unknown start of token: \u{326}
2+
--> $DIR/incorrect-first-separator.rs:19:28
3+
|
4+
LL | format!("A number: {}" ̦ iter::once(42).next().unwrap());
5+
| ^
6+
7+
error: expected token: `,`
8+
--> $DIR/incorrect-first-separator.rs:7:27
9+
|
10+
LL | format!("A number: {}". iter::once(42).next().unwrap());
11+
| ^ expected `,`
12+
13+
error: expected token: `,`
14+
--> $DIR/incorrect-first-separator.rs:12:28
15+
|
16+
LL | format!("A number: {}" / iter::once(42).next().unwrap());
17+
| ^ expected `,`
18+
19+
error: expected token: `,`
20+
--> $DIR/incorrect-first-separator.rs:15:27
21+
|
22+
LL | format!("A number: {}"; iter::once(42).next().unwrap());
23+
| ^ expected `,`
24+
25+
error: expected token: `,`
26+
--> $DIR/incorrect-first-separator.rs:19:30
27+
|
28+
LL | format!("A number: {}" ̦ iter::once(42).next().unwrap());
29+
| ^^^^ expected `,`
30+
31+
error: aborting due to 5 previous errors
32+

0 commit comments

Comments
 (0)