Skip to content

Commit f79232e

Browse files
committed
Detect ruby-style closure in parser
When parsing a closure without a body that is surrounded by a block, suggest moving the opening brace after the closure head. Fix #116608.
1 parent cdddcd3 commit f79232e

12 files changed

+227
-6
lines changed

compiler/rustc_parse/src/parser/expr.rs

+67-3
Original file line numberDiff line numberDiff line change
@@ -2209,6 +2209,7 @@ impl<'a> Parser<'a> {
22092209
fn parse_expr_closure(&mut self) -> PResult<'a, P<Expr>> {
22102210
let lo = self.token.span;
22112211

2212+
let before = self.prev_token.clone();
22122213
let binder = if self.check_keyword(kw::For) {
22132214
let lo = self.token.span;
22142215
let lifetime_defs = self.parse_late_bound_lifetime_defs()?;
@@ -2239,7 +2240,64 @@ impl<'a> Parser<'a> {
22392240
FnRetTy::Default(_) => {
22402241
let restrictions =
22412242
self.restrictions - Restrictions::STMT_EXPR - Restrictions::ALLOW_LET;
2242-
self.parse_expr_res(restrictions, None)?
2243+
let prev = self.prev_token.clone();
2244+
let token = self.token.clone();
2245+
match self.parse_expr_res(restrictions, None) {
2246+
Ok(expr) => expr,
2247+
Err(mut err) => {
2248+
err.span_label(lo.to(decl_hi), "while parsing the body of this closure");
2249+
match before.kind {
2250+
token::OpenDelim(Delimiter::Brace)
2251+
if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) =>
2252+
{
2253+
// `{ || () }` should have been `|| { () }`
2254+
err.multipart_suggestion(
2255+
"you might have meant to open the body of the closure, instead \
2256+
of enclosing the closure in a block",
2257+
vec![
2258+
(before.span, String::new()),
2259+
(prev.span.shrink_to_hi(), " {".to_string()),
2260+
],
2261+
Applicability::MaybeIncorrect,
2262+
);
2263+
err.emit();
2264+
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Brace)]);
2265+
}
2266+
token::OpenDelim(Delimiter::Parenthesis)
2267+
if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) =>
2268+
{
2269+
// We are within a function call or tuple, we can emit the error
2270+
// and recover.
2271+
self.eat_to_tokens(&[
2272+
&token::CloseDelim(Delimiter::Parenthesis),
2273+
&token::Comma,
2274+
]);
2275+
2276+
err.multipart_suggestion_verbose(
2277+
"you might have meant to open the body of the closure",
2278+
vec![
2279+
(prev.span.shrink_to_hi(), " {".to_string()),
2280+
(self.token.span.shrink_to_lo(), "}".to_string()),
2281+
],
2282+
Applicability::MaybeIncorrect,
2283+
);
2284+
err.emit();
2285+
}
2286+
_ if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) => {
2287+
// We don't have a heuristic to correctly identify where the block
2288+
// should be closed.
2289+
err.multipart_suggestion_verbose(
2290+
"you might have meant to open the body of the closure",
2291+
vec![(prev.span.shrink_to_hi(), " {".to_string())],
2292+
Applicability::HasPlaceholders,
2293+
);
2294+
return Err(err);
2295+
}
2296+
_ => return Err(err),
2297+
}
2298+
self.mk_expr_err(lo.to(self.token.span))
2299+
}
2300+
}
22432301
}
22442302
_ => {
22452303
// If an explicit return type is given, require a block to appear (RFC 968).
@@ -2459,10 +2517,16 @@ impl<'a> Parser<'a> {
24592517
/// Parses a `let $pat = $expr` pseudo-expression.
24602518
fn parse_expr_let(&mut self, restrictions: Restrictions) -> PResult<'a, P<Expr>> {
24612519
let is_recovered = if !restrictions.contains(Restrictions::ALLOW_LET) {
2462-
Some(self.sess.emit_err(errors::ExpectedExpressionFoundLet {
2520+
let err = errors::ExpectedExpressionFoundLet {
24632521
span: self.token.span,
24642522
reason: ForbiddenLetReason::OtherForbidden,
2465-
}))
2523+
};
2524+
if self.prev_token.kind == token::BinOp(token::Or) {
2525+
// This was part of a closure, the that part of the parser recover.
2526+
return Err(err.into_diagnostic(&self.sess.span_diagnostic));
2527+
} else {
2528+
Some(self.sess.emit_err(err))
2529+
}
24662530
} else {
24672531
None
24682532
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// run-rustfix
2+
fn main() {
3+
let _ = vec![1, 2, 3].into_iter().map(|x| {
4+
let y = x; //~ ERROR expected expression, found `let` statement
5+
y
6+
});
7+
let _: () = foo(); //~ ERROR mismatched types
8+
}
9+
10+
fn foo() {}
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// run-rustfix
2+
fn main() {
3+
let _ = vec![1, 2, 3].into_iter().map(|x|
4+
let y = x; //~ ERROR expected expression, found `let` statement
5+
y
6+
);
7+
let _: () = foo; //~ ERROR mismatched types
8+
}
9+
10+
fn foo() {}
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error: expected expression, found `let` statement
2+
--> $DIR/missing_block_in_fn_call.rs:4:9
3+
|
4+
LL | let _ = vec![1, 2, 3].into_iter().map(|x|
5+
| --- while parsing the body of this closure
6+
LL | let y = x;
7+
| ^^^
8+
|
9+
= note: only supported directly in conditions of `if` and `while` expressions
10+
help: you might have meant to open the body of the closure
11+
|
12+
LL ~ let _ = vec![1, 2, 3].into_iter().map(|x| {
13+
LL | let y = x;
14+
LL | y
15+
LL ~ });
16+
|
17+
18+
error[E0308]: mismatched types
19+
--> $DIR/missing_block_in_fn_call.rs:7:17
20+
|
21+
LL | let _: () = foo;
22+
| -- ^^^ expected `()`, found fn item
23+
| |
24+
| expected due to this
25+
...
26+
LL | fn foo() {}
27+
| -------- function `foo` defined here
28+
|
29+
= note: expected unit type `()`
30+
found fn item `fn() {foo}`
31+
help: use parentheses to call this function
32+
|
33+
LL | let _: () = foo();
34+
| ++
35+
36+
error: aborting due to 2 previous errors
37+
38+
For more information about this error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fn main() {
2+
let x = |x|
3+
let y = x; //~ ERROR expected expression, found `let` statement
4+
let _ = () + ();
5+
y
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: expected expression, found `let` statement
2+
--> $DIR/missing_block_in_let_binding.rs:3:9
3+
|
4+
LL | let x = |x|
5+
| --- while parsing the body of this closure
6+
LL | let y = x;
7+
| ^^^
8+
|
9+
= note: only supported directly in conditions of `if` and `while` expressions
10+
help: you might have meant to open the body of the closure
11+
|
12+
LL | let x = |x| {
13+
| +
14+
15+
error: aborting due to previous error
16+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// run-rustfix
2+
fn main() {
3+
let _ = vec![1, 2, 3].into_iter().map(|x| {
4+
let y = x; //~ ERROR expected expression, found `let` statement
5+
y
6+
});
7+
let _: () = foo(); //~ ERROR mismatched types
8+
}
9+
fn foo() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// run-rustfix
2+
fn main() {
3+
let _ = vec![1, 2, 3].into_iter().map({|x|
4+
let y = x; //~ ERROR expected expression, found `let` statement
5+
y
6+
});
7+
let _: () = foo; //~ ERROR mismatched types
8+
}
9+
fn foo() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error: expected expression, found `let` statement
2+
--> $DIR/ruby_style_closure_parse_error.rs:4:9
3+
|
4+
LL | let _ = vec![1, 2, 3].into_iter().map({|x|
5+
| --- while parsing the body of this closure
6+
LL | let y = x;
7+
| ^^^
8+
|
9+
= note: only supported directly in conditions of `if` and `while` expressions
10+
help: you might have meant to open the body of the closure, instead of enclosing the closure in a block
11+
|
12+
LL - let _ = vec![1, 2, 3].into_iter().map({|x|
13+
LL + let _ = vec![1, 2, 3].into_iter().map(|x| {
14+
|
15+
16+
error[E0308]: mismatched types
17+
--> $DIR/ruby_style_closure_parse_error.rs:7:17
18+
|
19+
LL | let _: () = foo;
20+
| -- ^^^ expected `()`, found fn item
21+
| |
22+
| expected due to this
23+
LL | }
24+
LL | fn foo() {}
25+
| -------- function `foo` defined here
26+
|
27+
= note: expected unit type `()`
28+
found fn item `fn() {foo}`
29+
help: use parentheses to call this function
30+
|
31+
LL | let _: () = foo();
32+
| ++
33+
34+
error: aborting due to 2 previous errors
35+
36+
For more information about this error, try `rustc --explain E0308`.

tests/ui/or-patterns/or-patterns-syntactic-fail.stderr

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ error: expected identifier, found `:`
22
--> $DIR/or-patterns-syntactic-fail.rs:11:19
33
|
44
LL | let _ = |A | B: E| ();
5-
| ^ expected identifier
5+
| ---- ^ expected identifier
6+
| |
7+
| while parsing the body of this closure
68
|
79
= note: type ascription syntax has been removed, see issue #101728 <https://github.com/rust-lang/rust/issues/101728>
10+
help: you might have meant to open the body of the closure
11+
|
12+
LL | let _ = |A | { B: E| ();
13+
| +
814

915
error: top-level or-patterns are not allowed in function parameters
1016
--> $DIR/or-patterns-syntactic-fail.rs:18:13

tests/ui/parser/issues/issue-32505.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub fn test() {
22
foo(|_|) //~ ERROR expected expression, found `)`
3+
//~^ ERROR cannot find function `foo` in this scope
34
}
45

56
fn main() { }

tests/ui/parser/issues/issue-32505.stderr

+16-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@ error: expected expression, found `)`
22
--> $DIR/issue-32505.rs:2:12
33
|
44
LL | foo(|_|)
5-
| ^ expected expression
5+
| ---^ expected expression
6+
| |
7+
| while parsing the body of this closure
8+
|
9+
help: you might have meant to open the body of the closure
10+
|
11+
LL | foo(|_| {})
12+
| ++
13+
14+
error[E0425]: cannot find function `foo` in this scope
15+
--> $DIR/issue-32505.rs:2:5
16+
|
17+
LL | foo(|_|)
18+
| ^^^ not found in this scope
619

7-
error: aborting due to previous error
20+
error: aborting due to 2 previous errors
821

22+
For more information about this error, try `rustc --explain E0425`.

0 commit comments

Comments
 (0)