Skip to content

Commit 8684a4d

Browse files
authored
Rollup merge of rust-lang#88729 - estebank:struct-literal-using-parens, r=oli-obk
Recover from `Foo(a: 1, b: 2)` Detect likely `struct` literal using parentheses as delimiters and emit targeted suggestion instead of type ascription parse error. Fix rust-lang#61326.
2 parents 800baa0 + ffc623a commit 8684a4d

File tree

7 files changed

+133
-34
lines changed

7 files changed

+133
-34
lines changed

compiler/rustc_parse/src/parser/expr.rs

+91-10
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,12 @@ impl<'a> Parser<'a> {
907907
}
908908
}
909909

910+
fn look_ahead_type_ascription_as_field(&mut self) -> bool {
911+
self.look_ahead(1, |t| t.is_ident())
912+
&& self.look_ahead(2, |t| t == &token::Colon)
913+
&& self.look_ahead(3, |t| t.can_begin_expr())
914+
}
915+
910916
fn parse_dot_suffix_expr(&mut self, lo: Span, base: P<Expr>) -> PResult<'a, P<Expr>> {
911917
match self.token.uninterpolate().kind {
912918
token::Ident(..) => self.parse_dot_suffix(base, lo),
@@ -1056,12 +1062,76 @@ impl<'a> Parser<'a> {
10561062

10571063
/// Parse a function call expression, `expr(...)`.
10581064
fn parse_fn_call_expr(&mut self, lo: Span, fun: P<Expr>) -> P<Expr> {
1059-
let seq = self.parse_paren_expr_seq().map(|args| {
1065+
let snapshot = if self.token.kind == token::OpenDelim(token::Paren)
1066+
&& self.look_ahead_type_ascription_as_field()
1067+
{
1068+
Some((self.clone(), fun.kind.clone()))
1069+
} else {
1070+
None
1071+
};
1072+
let open_paren = self.token.span;
1073+
1074+
let mut seq = self.parse_paren_expr_seq().map(|args| {
10601075
self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args), AttrVec::new())
10611076
});
1077+
if let Some(expr) =
1078+
self.maybe_recover_struct_lit_bad_delims(lo, open_paren, &mut seq, snapshot)
1079+
{
1080+
return expr;
1081+
}
10621082
self.recover_seq_parse_error(token::Paren, lo, seq)
10631083
}
10641084

1085+
/// If we encounter a parser state that looks like the user has written a `struct` literal with
1086+
/// parentheses instead of braces, recover the parser state and provide suggestions.
1087+
fn maybe_recover_struct_lit_bad_delims(
1088+
&mut self,
1089+
lo: Span,
1090+
open_paren: Span,
1091+
seq: &mut PResult<'a, P<Expr>>,
1092+
snapshot: Option<(Self, ExprKind)>,
1093+
) -> Option<P<Expr>> {
1094+
match (seq.as_mut(), snapshot) {
1095+
(Err(ref mut err), Some((mut snapshot, ExprKind::Path(None, path)))) => {
1096+
let name = pprust::path_to_string(&path);
1097+
snapshot.bump(); // `(`
1098+
match snapshot.parse_struct_fields(path.clone(), false, token::Paren) {
1099+
Ok((fields, ..)) if snapshot.eat(&token::CloseDelim(token::Paren)) => {
1100+
// We have are certain we have `Enum::Foo(a: 3, b: 4)`, suggest
1101+
// `Enum::Foo { a: 3, b: 4 }` or `Enum::Foo(3, 4)`.
1102+
*self = snapshot;
1103+
let close_paren = self.prev_token.span;
1104+
let span = lo.to(self.prev_token.span);
1105+
err.cancel();
1106+
self.struct_span_err(
1107+
span,
1108+
"invalid `struct` delimiters or `fn` call arguments",
1109+
)
1110+
.multipart_suggestion(
1111+
&format!("if `{}` is a struct, use braces as delimiters", name),
1112+
vec![(open_paren, " { ".to_string()), (close_paren, " }".to_string())],
1113+
Applicability::MaybeIncorrect,
1114+
)
1115+
.multipart_suggestion(
1116+
&format!("if `{}` is a function, use the arguments directly", name),
1117+
fields
1118+
.into_iter()
1119+
.map(|field| (field.span.until(field.expr.span), String::new()))
1120+
.collect(),
1121+
Applicability::MaybeIncorrect,
1122+
)
1123+
.emit();
1124+
return Some(self.mk_expr_err(span));
1125+
}
1126+
Ok(_) => {}
1127+
Err(mut err) => err.emit(),
1128+
}
1129+
}
1130+
_ => {}
1131+
}
1132+
None
1133+
}
1134+
10651135
/// Parse an indexing expression `expr[...]`.
10661136
fn parse_index_expr(&mut self, lo: Span, base: P<Expr>) -> PResult<'a, P<Expr>> {
10671137
self.bump(); // `[`
@@ -2374,14 +2444,12 @@ impl<'a> Parser<'a> {
23742444
.emit();
23752445
}
23762446

2377-
/// Precondition: already parsed the '{'.
2378-
pub(super) fn parse_struct_expr(
2447+
pub(super) fn parse_struct_fields(
23792448
&mut self,
2380-
qself: Option<ast::QSelf>,
23812449
pth: ast::Path,
2382-
attrs: AttrVec,
23832450
recover: bool,
2384-
) -> PResult<'a, P<Expr>> {
2451+
close_delim: token::DelimToken,
2452+
) -> PResult<'a, (Vec<ExprField>, ast::StructRest, bool)> {
23852453
let mut fields = Vec::new();
23862454
let mut base = ast::StructRest::None;
23872455
let mut recover_async = false;
@@ -2393,11 +2461,11 @@ impl<'a> Parser<'a> {
23932461
e.note("for more on editions, read https://doc.rust-lang.org/edition-guide");
23942462
};
23952463

2396-
while self.token != token::CloseDelim(token::Brace) {
2464+
while self.token != token::CloseDelim(close_delim) {
23972465
if self.eat(&token::DotDot) {
23982466
let exp_span = self.prev_token.span;
23992467
// We permit `.. }` on the left-hand side of a destructuring assignment.
2400-
if self.check(&token::CloseDelim(token::Brace)) {
2468+
if self.check(&token::CloseDelim(close_delim)) {
24012469
self.sess.gated_spans.gate(sym::destructuring_assignment, self.prev_token.span);
24022470
base = ast::StructRest::Rest(self.prev_token.span.shrink_to_hi());
24032471
break;
@@ -2438,7 +2506,7 @@ impl<'a> Parser<'a> {
24382506
}
24392507
};
24402508

2441-
match self.expect_one_of(&[token::Comma], &[token::CloseDelim(token::Brace)]) {
2509+
match self.expect_one_of(&[token::Comma], &[token::CloseDelim(close_delim)]) {
24422510
Ok(_) => {
24432511
if let Some(f) = parsed_field.or(recovery_field) {
24442512
// Only include the field if there's no parse error for the field name.
@@ -2469,8 +2537,21 @@ impl<'a> Parser<'a> {
24692537
}
24702538
}
24712539
}
2540+
Ok((fields, base, recover_async))
2541+
}
24722542

2473-
let span = pth.span.to(self.token.span);
2543+
/// Precondition: already parsed the '{'.
2544+
pub(super) fn parse_struct_expr(
2545+
&mut self,
2546+
qself: Option<ast::QSelf>,
2547+
pth: ast::Path,
2548+
attrs: AttrVec,
2549+
recover: bool,
2550+
) -> PResult<'a, P<Expr>> {
2551+
let lo = pth.span;
2552+
let (fields, base, recover_async) =
2553+
self.parse_struct_fields(pth.clone(), recover, token::Brace)?;
2554+
let span = lo.to(self.token.span);
24742555
self.expect(&token::CloseDelim(token::Brace))?;
24752556
let expr = if recover_async {
24762557
ExprKind::Err

src/test/ui/issues/issue-34255-1.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ enum Test {
66

77
fn main() {
88
Test::Drill(field: 42);
9-
//~^ ERROR expected type, found
9+
//~^ ERROR invalid `struct` delimiters or `fn` call arguments
1010
}
+12-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
error: expected type, found `42`
2-
--> $DIR/issue-34255-1.rs:8:24
1+
error: invalid `struct` delimiters or `fn` call arguments
2+
--> $DIR/issue-34255-1.rs:8:5
33
|
44
LL | Test::Drill(field: 42);
5-
| - ^^ expected type
6-
| |
7-
| tried to parse a type due to this type ascription
5+
| ^^^^^^^^^^^^^^^^^^^^^^
86
|
9-
= note: `#![feature(type_ascription)]` lets you annotate an expression with a type: `<expr>: <type>`
10-
= note: see issue #23416 <https://github.com/rust-lang/rust/issues/23416> for more information
7+
help: if `Test::Drill` is a struct, use braces as delimiters
8+
|
9+
LL | Test::Drill { field: 42 };
10+
| ~ ~
11+
help: if `Test::Drill` is a function, use the arguments directly
12+
|
13+
LL - Test::Drill(field: 42);
14+
LL + Test::Drill(42);
15+
|
1116

1217
error: aborting due to previous error
1318

src/test/ui/parser/issue-44406.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
macro_rules! foo {
22
($rest: tt) => {
3-
bar(baz: $rest)
3+
bar(baz: $rest) //~ ERROR invalid `struct` delimiters or `fn` call arguments
44
}
55
}
66

77
fn main() {
8-
foo!(true); //~ ERROR expected type, found keyword
8+
foo!(true);
99
//~^ ERROR expected identifier, found keyword
1010
}

src/test/ui/parser/issue-44406.stderr

+14-6
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,25 @@ help: you can escape reserved keywords to use them as identifiers
99
LL | foo!(r#true);
1010
| ~~~~~~
1111

12-
error: expected type, found keyword `true`
13-
--> $DIR/issue-44406.rs:8:10
12+
error: invalid `struct` delimiters or `fn` call arguments
13+
--> $DIR/issue-44406.rs:3:9
1414
|
1515
LL | bar(baz: $rest)
16-
| - help: try using a semicolon: `;`
16+
| ^^^^^^^^^^^^^^^
1717
...
1818
LL | foo!(true);
19-
| ^^^^ expected type
19+
| ----------- in this macro invocation
20+
|
21+
= note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
22+
help: if `bar` is a struct, use braces as delimiters
23+
|
24+
LL | bar { }
25+
| ~
26+
help: if `bar` is a function, use the arguments directly
2027
|
21-
= note: `#![feature(type_ascription)]` lets you annotate an expression with a type: `<expr>: <type>`
22-
= note: see issue #23416 <https://github.com/rust-lang/rust/issues/23416> for more information
28+
LL - bar(baz: $rest)
29+
LL + bar(true);
30+
|
2331

2432
error: aborting due to 2 previous errors
2533

src/test/ui/parser/recover-from-bad-variant.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ enum Enum {
55

66
fn main() {
77
let x = Enum::Foo(a: 3, b: 4);
8-
//~^ ERROR expected type, found `3`
8+
//~^ ERROR invalid `struct` delimiters or `fn` call arguments
99
match x {
1010
Enum::Foo(a, b) => {}
1111
//~^ ERROR expected tuple struct or tuple variant, found struct variant `Enum::Foo`

src/test/ui/parser/recover-from-bad-variant.stderr

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
error: expected type, found `3`
2-
--> $DIR/recover-from-bad-variant.rs:7:26
1+
error: invalid `struct` delimiters or `fn` call arguments
2+
--> $DIR/recover-from-bad-variant.rs:7:13
33
|
44
LL | let x = Enum::Foo(a: 3, b: 4);
5-
| - ^ expected type
6-
| |
7-
| tried to parse a type due to this type ascription
5+
| ^^^^^^^^^^^^^^^^^^^^^
86
|
9-
= note: `#![feature(type_ascription)]` lets you annotate an expression with a type: `<expr>: <type>`
10-
= note: see issue #23416 <https://github.com/rust-lang/rust/issues/23416> for more information
7+
help: if `Enum::Foo` is a struct, use braces as delimiters
8+
|
9+
LL | let x = Enum::Foo { a: 3, b: 4 };
10+
| ~ ~
11+
help: if `Enum::Foo` is a function, use the arguments directly
12+
|
13+
LL - let x = Enum::Foo(a: 3, b: 4);
14+
LL + let x = Enum::Foo(3, 4);
15+
|
1116

1217
error[E0532]: expected tuple struct or tuple variant, found struct variant `Enum::Foo`
1318
--> $DIR/recover-from-bad-variant.rs:10:9

0 commit comments

Comments
 (0)