Skip to content

Commit dfad725

Browse files
committed
Recover 'for ( $pat in $expr ) $block'.
1 parent 023525d commit dfad725

File tree

4 files changed

+97
-0
lines changed

4 files changed

+97
-0
lines changed

src/libsyntax/parse/diagnostics.rs

+44
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,50 @@ impl<'a> Parser<'a> {
923923
}
924924
}
925925

926+
/// Recover a situation like `for ( $pat in $expr )`
927+
/// and suggest writing `for $pat in $expr` instead.
928+
///
929+
/// This should be called before parsing the `$block`.
930+
crate fn recover_parens_around_for_head(
931+
&mut self,
932+
pat: P<Pat>,
933+
expr: &Expr,
934+
begin_paren: Option<Span>,
935+
) -> P<Pat> {
936+
match (&self.token.kind, begin_paren) {
937+
(token::CloseDelim(token::Paren), Some(begin_par_sp)) => {
938+
self.bump();
939+
940+
let pat_str = self
941+
.sess
942+
.source_map()
943+
// Remove the `(` from the span of the pattern:
944+
.span_to_snippet(pat.span.trim_start(begin_par_sp).unwrap())
945+
.unwrap_or_else(|_| pprust::pat_to_string(&pat));
946+
947+
self.struct_span_err(self.prev_span, "unexpected closing `)`")
948+
.span_label(begin_par_sp, "opening `(`")
949+
.span_suggestion(
950+
begin_par_sp.to(self.prev_span),
951+
"remove parenthesis in `for` loop",
952+
format!("{} in {}", pat_str, pprust::expr_to_string(&expr)),
953+
// With e.g. `for (x) in y)` this would replace `(x) in y)`
954+
// with `x) in y)` which is syntactically invalid.
955+
// However, this is prevented before we get here.
956+
Applicability::MachineApplicable,
957+
)
958+
.emit();
959+
960+
// Unwrap `(pat)` into `pat` to avoid the `unused_parens` lint.
961+
pat.and_then(|pat| match pat.node {
962+
PatKind::Paren(pat) => pat,
963+
_ => P(pat),
964+
})
965+
}
966+
_ => pat,
967+
}
968+
}
969+
926970
crate fn could_ascription_be_path(&self, node: &ast::ExprKind) -> bool {
927971
self.token.is_ident() &&
928972
if let ast::ExprKind::Path(..) = node { true } else { false } &&

src/libsyntax/parse/parser.rs

+11
Original file line numberDiff line numberDiff line change
@@ -3275,6 +3275,14 @@ impl<'a> Parser<'a> {
32753275
mut attrs: ThinVec<Attribute>) -> PResult<'a, P<Expr>> {
32763276
// Parse: `for <src_pat> in <src_expr> <src_loop_block>`
32773277

3278+
// Record whether we are about to parse `for (`.
3279+
// This is used below for recovery in case of `for ( $stuff ) $block`
3280+
// in which case we will suggest `for $stuff $block`.
3281+
let begin_paren = match self.token.kind {
3282+
token::OpenDelim(token::Paren) => Some(self.token.span),
3283+
_ => None,
3284+
};
3285+
32783286
let pat = self.parse_top_level_pat()?;
32793287
if !self.eat_keyword(kw::In) {
32803288
let in_span = self.prev_span.between(self.token.span);
@@ -3290,6 +3298,9 @@ impl<'a> Parser<'a> {
32903298
let in_span = self.prev_span;
32913299
self.check_for_for_in_in_typo(in_span);
32923300
let expr = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
3301+
3302+
let pat = self.recover_parens_around_for_head(pat, &expr, begin_paren);
3303+
32933304
let (iattrs, loop_block) = self.parse_inner_attrs_and_block()?;
32943305
attrs.extend(iattrs);
32953306

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Here we test that the parser is able to recover in a situation like
2+
// `for ( $pat in $expr )` since that is familiar syntax in other languages.
3+
// Instead we suggest that the user writes `for $pat in $expr`.
4+
5+
#![deny(unused)] // Make sure we don't trigger `unused_parens`.
6+
7+
fn main() {
8+
let vec = vec![1, 2, 3];
9+
10+
for ( elem in vec ) {
11+
//~^ ERROR expected one of `)`, `,`, or `@`, found `in`
12+
//~| ERROR unexpected closing `)`
13+
const RECOVERY_WITNESS: () = 0; //~ ERROR mismatched types
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error: expected one of `)`, `,`, or `@`, found `in`
2+
--> $DIR/recover-for-loop-parens-around-head.rs:10:16
3+
|
4+
LL | for ( elem in vec ) {
5+
| ^^ expected one of `)`, `,`, or `@` here
6+
7+
error: unexpected closing `)`
8+
--> $DIR/recover-for-loop-parens-around-head.rs:10:23
9+
|
10+
LL | for ( elem in vec ) {
11+
| --------------^
12+
| |
13+
| opening `(`
14+
| help: remove parenthesis in `for` loop: `elem in vec`
15+
16+
error[E0308]: mismatched types
17+
--> $DIR/recover-for-loop-parens-around-head.rs:13:38
18+
|
19+
LL | const RECOVERY_WITNESS: () = 0;
20+
| ^ expected (), found integer
21+
|
22+
= note: expected type `()`
23+
found type `{integer}`
24+
25+
error: aborting due to 3 previous errors
26+
27+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)