Skip to content

Commit b82f450

Browse files
committed
Auto merge of rust-lang#116645 - estebank:issue-116608, r=oli-obk
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 rust-lang#116608.
2 parents 2763ca5 + 6b2c6c7 commit b82f450

13 files changed

+232
-6
lines changed

compiler/rustc_parse/src/parser/diagnostics.rs

+59
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,65 @@ impl<'a> Parser<'a> {
827827
None
828828
}
829829

830+
pub(super) fn recover_closure_body(
831+
&mut self,
832+
mut err: DiagnosticBuilder<'a, ErrorGuaranteed>,
833+
before: token::Token,
834+
prev: token::Token,
835+
token: token::Token,
836+
lo: Span,
837+
decl_hi: Span,
838+
) -> PResult<'a, P<Expr>> {
839+
err.span_label(lo.to(decl_hi), "while parsing the body of this closure");
840+
match before.kind {
841+
token::OpenDelim(Delimiter::Brace)
842+
if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) =>
843+
{
844+
// `{ || () }` should have been `|| { () }`
845+
err.multipart_suggestion(
846+
"you might have meant to open the body of the closure, instead of enclosing \
847+
the closure in a block",
848+
vec![
849+
(before.span, String::new()),
850+
(prev.span.shrink_to_hi(), " {".to_string()),
851+
],
852+
Applicability::MaybeIncorrect,
853+
);
854+
err.emit();
855+
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Brace)]);
856+
}
857+
token::OpenDelim(Delimiter::Parenthesis)
858+
if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) =>
859+
{
860+
// We are within a function call or tuple, we can emit the error
861+
// and recover.
862+
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis), &token::Comma]);
863+
864+
err.multipart_suggestion_verbose(
865+
"you might have meant to open the body of the closure",
866+
vec![
867+
(prev.span.shrink_to_hi(), " {".to_string()),
868+
(self.token.span.shrink_to_lo(), "}".to_string()),
869+
],
870+
Applicability::MaybeIncorrect,
871+
);
872+
err.emit();
873+
}
874+
_ if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) => {
875+
// We don't have a heuristic to correctly identify where the block
876+
// should be closed.
877+
err.multipart_suggestion_verbose(
878+
"you might have meant to open the body of the closure",
879+
vec![(prev.span.shrink_to_hi(), " {".to_string())],
880+
Applicability::HasPlaceholders,
881+
);
882+
return Err(err);
883+
}
884+
_ => return Err(err),
885+
}
886+
Ok(self.mk_expr_err(lo.to(self.token.span)))
887+
}
888+
830889
/// Eats and discards tokens until one of `kets` is encountered. Respects token trees,
831890
/// passes through any errors encountered. Used for error recovery.
832891
pub(super) fn eat_to_tokens(&mut self, kets: &[&TokenKind]) {

compiler/rustc_parse/src/parser/expr.rs

+15-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,12 @@ 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(err) => self.recover_closure_body(err, before, prev, token, lo, decl_hi)?,
2248+
}
22432249
}
22442250
_ => {
22452251
// If an explicit return type is given, require a block to appear (RFC 968).
@@ -2459,10 +2465,16 @@ impl<'a> Parser<'a> {
24592465
/// Parses a `let $pat = $expr` pseudo-expression.
24602466
fn parse_expr_let(&mut self, restrictions: Restrictions) -> PResult<'a, P<Expr>> {
24612467
let is_recovered = if !restrictions.contains(Restrictions::ALLOW_LET) {
2462-
Some(self.sess.emit_err(errors::ExpectedExpressionFoundLet {
2468+
let err = errors::ExpectedExpressionFoundLet {
24632469
span: self.token.span,
24642470
reason: ForbiddenLetReason::OtherForbidden,
2465-
}))
2471+
};
2472+
if self.prev_token.kind == token::BinOp(token::Or) {
2473+
// This was part of a closure, the that part of the parser recover.
2474+
return Err(err.into_diagnostic(&self.sess.span_diagnostic));
2475+
} else {
2476+
Some(self.sess.emit_err(err))
2477+
}
24662478
} else {
24672479
None
24682480
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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() {}
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)