Skip to content

Commit 75bd7cc

Browse files
authored
Rollup merge of rust-lang#108427 - y21:for-else-diagnostic, r=compiler-errors
Recover from for-else and while-else This recovers from attempts at writing for-else or while-else loops, which might help users coming from e.g. Python. ```rs for _ in 0..0 { // ... } else { // ... } ``` Combined with trying to store it in a let binding, the current diagnostic can be a bit confusing. It mentions let-else and suggests wrapping the loop in parentheses, which the user probably doesn't want. let-else doesn't make sense for `for` and `while` loops, as they are of type `()` (which already is an irrefutable pattern and doesn't need let-else). <details> <summary>Current diagnostic</summary> ```rs error: right curly brace `}` before `else` in a `let...else` statement not allowed --> src/main.rs:4:5 | 4 | } else { | ^ | help: wrap the expression in parentheses | 2 ~ let _x = (for _ in 0..0 { 3 | 4 ~ }) else { | ``` </details> Some questions: - Can the wording for the error message be improved? Would "for...else loops are not allowed" fit better? - Should we be more "conservative" in case we want to support this in the future (i.e. say "for...else loops are **currently** not allowed/supported")? - Is there a better way than storing a `&'static str` for the loop type? It is used for substituting the placeholder in the locale file (since it can emit either `for...else` or `while...else`). Maybe there is an enum I could use that I couldn't find
2 parents 4ebf78a + 13b8497 commit 75bd7cc

20 files changed

+218
-22
lines changed

compiler/rustc_parse/locales/en-US.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ parse_missing_in_in_for_loop = missing `in` in `for` loop
151151
parse_missing_expression_in_for_loop = missing expression to iterate on in `for` loop
152152
.suggestion = try adding an expression to the `for` loop
153153
154+
parse_loop_else = `{$loop_kind}...else` loops are not supported
155+
.note = consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
156+
.loop_keyword = `else` is attached to this loop
157+
154158
parse_missing_comma_after_match_arm = expected `,` following `match` arm
155159
.suggestion = missing a comma here to end this `match` arm
156160

compiler/rustc_parse/src/errors.rs

+11
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,17 @@ pub(crate) struct MissingExpressionInForLoop {
451451
pub span: Span,
452452
}
453453

454+
#[derive(Diagnostic)]
455+
#[diag(parse_loop_else)]
456+
#[note]
457+
pub(crate) struct LoopElseNotSupported {
458+
#[primary_span]
459+
pub span: Span,
460+
pub loop_kind: &'static str,
461+
#[label(parse_loop_keyword)]
462+
pub loop_kw: Span,
463+
}
464+
454465
#[derive(Diagnostic)]
455466
#[diag(parse_missing_comma_after_match_arm)]
456467
pub(crate) struct MissingCommaAfterMatchArm {

compiler/rustc_parse/src/parser/expr.rs

+22
Original file line numberDiff line numberDiff line change
@@ -2503,9 +2503,27 @@ impl<'a> Parser<'a> {
25032503
let (attrs, loop_block) = self.parse_inner_attrs_and_block()?;
25042504

25052505
let kind = ExprKind::ForLoop(pat, expr, loop_block, opt_label);
2506+
2507+
self.recover_loop_else("for", lo)?;
2508+
25062509
Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs))
25072510
}
25082511

2512+
/// Recovers from an `else` clause after a loop (`for...else`, `while...else`)
2513+
fn recover_loop_else(&mut self, loop_kind: &'static str, loop_kw: Span) -> PResult<'a, ()> {
2514+
if self.token.is_keyword(kw::Else) && self.may_recover() {
2515+
let else_span = self.token.span;
2516+
self.bump();
2517+
let else_clause = self.parse_else_expr()?;
2518+
self.sess.emit_err(errors::LoopElseNotSupported {
2519+
span: else_span.to(else_clause.span),
2520+
loop_kind,
2521+
loop_kw,
2522+
});
2523+
}
2524+
Ok(())
2525+
}
2526+
25092527
fn error_missing_in_for_loop(&mut self) {
25102528
let (span, sub): (_, fn(_) -> _) = if self.token.is_ident_named(sym::of) {
25112529
// Possibly using JS syntax (#75311).
@@ -2530,6 +2548,9 @@ impl<'a> Parser<'a> {
25302548
err.span_label(cond.span, "this `while` condition successfully parsed");
25312549
err
25322550
})?;
2551+
2552+
self.recover_loop_else("while", lo)?;
2553+
25332554
Ok(self.mk_expr_with_attrs(
25342555
lo.to(self.prev_token.span),
25352556
ExprKind::While(cond, body, opt_label),
@@ -2541,6 +2562,7 @@ impl<'a> Parser<'a> {
25412562
fn parse_expr_loop(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
25422563
let loop_span = self.prev_token.span;
25432564
let (attrs, body) = self.parse_inner_attrs_and_block()?;
2565+
self.recover_loop_else("loop", lo)?;
25442566
Ok(self.mk_expr_with_attrs(
25452567
lo.to(self.prev_token.span),
25462568
ExprKind::Loop(body, opt_label, loop_span),

tests/ui/for/for-else-err.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
for _ in 0..1 {
3+
//~^ NOTE `else` is attached to this loop
4+
} else {
5+
//~^ ERROR `for...else` loops are not supported
6+
//~| NOTE consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
7+
}
8+
}

tests/ui/for/for-else-err.stderr

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: `for...else` loops are not supported
2+
--> $DIR/for-else-err.rs:4:7
3+
|
4+
LL | for _ in 0..1 {
5+
| --- `else` is attached to this loop
6+
LL |
7+
LL | } else {
8+
| _______^
9+
LL | |
10+
LL | |
11+
LL | | }
12+
| |_____^
13+
|
14+
= note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
15+
16+
error: aborting due to previous error
17+

tests/ui/for/for-else-let-else-err.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
let _ = for _ in 0..1 {
3+
//~^ NOTE `else` is attached to this loop
4+
} else {
5+
//~^ ERROR `for...else` loops are not supported
6+
//~| NOTE consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
7+
};
8+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: `for...else` loops are not supported
2+
--> $DIR/for-else-let-else-err.rs:4:7
3+
|
4+
LL | let _ = for _ in 0..1 {
5+
| --- `else` is attached to this loop
6+
LL |
7+
LL | } else {
8+
| _______^
9+
LL | |
10+
LL | |
11+
LL | | };
12+
| |_____^
13+
|
14+
= note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
15+
16+
error: aborting due to previous error
17+

tests/ui/let-else/let-else-brace-before-else.fixed

-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ fn main() {
77
//~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
88
return;
99
};
10-
let Some(1) = (loop { break Some(1) }) else {
11-
//~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
12-
return;
13-
};
1410
let 2 = 1 + (match 1 { n => n }) else {
1511
//~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
1612
return;

tests/ui/let-else/let-else-brace-before-else.rs

-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ fn main() {
77
//~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
88
return;
99
};
10-
let Some(1) = loop { break Some(1) } else {
11-
//~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
12-
return;
13-
};
1410
let 2 = 1 + match 1 { n => n } else {
1511
//~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
1612
return;

tests/ui/let-else/let-else-brace-before-else.stderr

+3-14
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,7 @@ LL | let Some(1) = ({ Some(1) }) else {
1010
| + +
1111

1212
error: right curly brace `}` before `else` in a `let...else` statement not allowed
13-
--> $DIR/let-else-brace-before-else.rs:10:40
14-
|
15-
LL | let Some(1) = loop { break Some(1) } else {
16-
| ^
17-
|
18-
help: wrap the expression in parentheses
19-
|
20-
LL | let Some(1) = (loop { break Some(1) }) else {
21-
| + +
22-
23-
error: right curly brace `}` before `else` in a `let...else` statement not allowed
24-
--> $DIR/let-else-brace-before-else.rs:14:34
13+
--> $DIR/let-else-brace-before-else.rs:10:34
2514
|
2615
LL | let 2 = 1 + match 1 { n => n } else {
2716
| ^
@@ -32,7 +21,7 @@ LL | let 2 = 1 + (match 1 { n => n }) else {
3221
| + +
3322

3423
error: right curly brace `}` before `else` in a `let...else` statement not allowed
35-
--> $DIR/let-else-brace-before-else.rs:18:40
24+
--> $DIR/let-else-brace-before-else.rs:14:40
3625
|
3726
LL | let Some(1) = unsafe { unsafe_fn() } else {
3827
| ^
@@ -42,5 +31,5 @@ help: wrap the expression in parentheses
4231
LL | let Some(1) = (unsafe { unsafe_fn() }) else {
4332
| + +
4433

45-
error: aborting due to 4 previous errors
34+
error: aborting due to 3 previous errors
4635

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fn main() {
2+
let Some(1) = loop {
3+
//~^ NOTE `else` is attached to this loop
4+
break Some(1)
5+
} else {
6+
//~^ ERROR `loop...else` loops are not supported
7+
//~| NOTE consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
8+
return;
9+
};
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: `loop...else` loops are not supported
2+
--> $DIR/loop-else-break-with-value.rs:5:7
3+
|
4+
LL | let Some(1) = loop {
5+
| ---- `else` is attached to this loop
6+
...
7+
LL | } else {
8+
| _______^
9+
LL | |
10+
LL | |
11+
LL | | return;
12+
LL | | };
13+
| |_____^
14+
|
15+
= note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
16+
17+
error: aborting due to previous error
18+

tests/ui/loops/loop-else-err.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
loop {
3+
//~^ NOTE `else` is attached to this loop
4+
} else {
5+
//~^ ERROR `loop...else` loops are not supported
6+
//~| NOTE consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
7+
}
8+
}

tests/ui/loops/loop-else-err.stderr

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: `loop...else` loops are not supported
2+
--> $DIR/loop-else-err.rs:4:7
3+
|
4+
LL | loop {
5+
| ---- `else` is attached to this loop
6+
LL |
7+
LL | } else {
8+
| _______^
9+
LL | |
10+
LL | |
11+
LL | | }
12+
| |_____^
13+
|
14+
= note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
15+
16+
error: aborting due to previous error
17+
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
let _ = loop {
3+
//~^ NOTE `else` is attached to this loop
4+
} else {
5+
//~^ ERROR `loop...else` loops are not supported
6+
//~| NOTE consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
7+
};
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: `loop...else` loops are not supported
2+
--> $DIR/loop-else-let-else-err.rs:4:7
3+
|
4+
LL | let _ = loop {
5+
| ---- `else` is attached to this loop
6+
LL |
7+
LL | } else {
8+
| _______^
9+
LL | |
10+
LL | |
11+
LL | | };
12+
| |_____^
13+
|
14+
= note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
15+
16+
error: aborting due to previous error
17+

tests/ui/while/while-else-err.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
while false {
3+
//~^ NOTE `else` is attached to this loop
4+
} else {
5+
//~^ ERROR `while...else` loops are not supported
6+
//~| NOTE consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
7+
};
8+
}

tests/ui/while/while-else-err.stderr

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: `while...else` loops are not supported
2+
--> $DIR/while-else-err.rs:4:7
3+
|
4+
LL | while false {
5+
| ----- `else` is attached to this loop
6+
LL |
7+
LL | } else {
8+
| _______^
9+
LL | |
10+
LL | |
11+
LL | | };
12+
| |_____^
13+
|
14+
= note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
15+
16+
error: aborting due to previous error
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
let _ = while false {
3+
//~^ NOTE `else` is attached to this loop
4+
} else {
5+
//~^ ERROR `while...else` loops are not supported
6+
//~| NOTE consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
7+
};
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: `while...else` loops are not supported
2+
--> $DIR/while-else-let-else-err.rs:4:7
3+
|
4+
LL | let _ = while false {
5+
| ----- `else` is attached to this loop
6+
LL |
7+
LL | } else {
8+
| _______^
9+
LL | |
10+
LL | |
11+
LL | | };
12+
| |_____^
13+
|
14+
= note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
15+
16+
error: aborting due to previous error
17+

0 commit comments

Comments
 (0)