Skip to content

Commit 16a71cc

Browse files
committed
rework how we handle the type of loops
First, we keep a `CoerceMany` now to find the LUB of all the break expressions. Second, this `CoerceMany` is actually an `Option<CoerceMany>`, and we store `None` for loops where "break with an expression" is disallowed. This avoids silly duplicate errors about a type mismatch, since the loops pass already reports an error that the break cannot have an expression. Finally, since we now detect an invalid break target during HIR lowering, refactor `find_loop` to be infallible. Adjust tests as needed: - some spans from breaks are slightly different - break up a single loop into multiple since `CoerceMany` silences redundant and derived errors - add a ui test that we only give on error for loop-break-value
1 parent 1ae620b commit 16a71cc

File tree

5 files changed

+161
-110
lines changed

5 files changed

+161
-110
lines changed

src/librustc_typeck/check/mod.rs

Lines changed: 111 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -415,15 +415,16 @@ impl Diverges {
415415
}
416416

417417
#[derive(Clone)]
418-
pub struct BreakableCtxt<'gcx, 'tcx> {
419-
unified: Ty<'tcx>,
420-
coerce_to: Ty<'tcx>,
421-
break_exprs: Vec<&'gcx hir::Expr>,
418+
pub struct BreakableCtxt<'gcx: 'tcx, 'tcx> {
422419
may_break: bool,
420+
421+
// this is `null` for loops where break with a value is illegal,
422+
// such as `while`, `for`, and `while let`
423+
coerce: Option<CoerceMany<'gcx, 'tcx>>,
423424
}
424425

425426
#[derive(Clone)]
426-
pub struct EnclosingBreakables<'gcx, 'tcx> {
427+
pub struct EnclosingBreakables<'gcx: 'tcx, 'tcx> {
427428
stack: Vec<BreakableCtxt<'gcx, 'tcx>>,
428429
by_id: NodeMap<usize>,
429430
}
@@ -3547,60 +3548,66 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
35473548
tcx.mk_nil()
35483549
}
35493550
hir::ExprBreak(destination, ref expr_opt) => {
3550-
if let Some(target_id) = destination.target_id.opt_id() {
3551-
let coerce_to = {
3552-
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
3553-
enclosing_breakables.find_breakable(target_id).coerce_to
3554-
};
3555-
3556-
let e_ty;
3557-
let cause;
3558-
if let Some(ref e) = *expr_opt {
3559-
// Recurse without `enclosing_loops` borrowed.
3560-
e_ty = self.check_expr_with_hint(e, coerce_to);
3561-
cause = self.misc(e.span);
3562-
// Notably, the recursive call may alter coerce_to - must not keep using it!
3563-
} else {
3564-
// `break` without argument acts like `break ()`.
3565-
e_ty = tcx.mk_nil();
3566-
cause = self.misc(expr.span);
3567-
}
3568-
3569-
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
3570-
let ctxt = enclosing_breakables.find_breakable(target_id);
3551+
if let Some(target_id) = destination.target_id.opt_id() {
3552+
let (e_ty, cause);
3553+
if let Some(ref e) = *expr_opt {
3554+
// If this is a break with a value, we need to type-check
3555+
// the expression. Get an expected type from the loop context.
3556+
let opt_coerce_to = {
3557+
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
3558+
enclosing_breakables.find_breakable(target_id)
3559+
.coerce
3560+
.as_ref()
3561+
.map(|coerce| coerce.expected_ty())
3562+
};
3563+
3564+
// If the loop context is not a `loop { }`, then break with
3565+
// a value is illegal, and `opt_coerce_to` will be `None`.
3566+
// Just set expectation to error in that case.
3567+
let coerce_to = opt_coerce_to.unwrap_or(tcx.types.err);
3568+
3569+
// Recurse without `enclosing_breakables` borrowed.
3570+
e_ty = self.check_expr_with_hint(e, coerce_to);
3571+
cause = self.misc(e.span);
3572+
} else {
3573+
// Otherwise, this is a break *without* a value. That's
3574+
// always legal, and is equivalent to `break ()`.
3575+
e_ty = tcx.mk_nil();
3576+
cause = self.misc(expr.span);
3577+
}
35713578

3572-
let result = if let Some(ref e) = *expr_opt {
3573-
// Special-case the first element, as it has no "previous expressions".
3574-
let result = if !ctxt.may_break {
3575-
self.try_coerce(e, e_ty, ctxt.coerce_to)
3576-
} else {
3577-
self.try_find_coercion_lub(&cause, || ctxt.break_exprs.iter().cloned(),
3578-
ctxt.unified, e, e_ty)
3579-
};
3579+
// Now that we have type-checked `expr_opt`, borrow
3580+
// the `enclosing_loops` field and let's coerce the
3581+
// type of `expr_opt` into what is expected.
3582+
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
3583+
let ctxt = enclosing_breakables.find_breakable(target_id);
3584+
if let Some(ref mut coerce) = ctxt.coerce {
3585+
if let Some(ref e) = *expr_opt {
3586+
coerce.coerce(self, &cause, e, e_ty);
3587+
} else {
3588+
assert!(e_ty.is_nil());
3589+
coerce.coerce_forced_unit(self, &cause);
3590+
}
3591+
} else {
3592+
// If `ctxt.coerce` is `None`, we can just ignore
3593+
// the type of the expresison. This is because
3594+
// either this was a break *without* a value, in
3595+
// which case it is always a legal type (`()`), or
3596+
// else an error would have been flagged by the
3597+
// `loops` pass for using break with an expression
3598+
// where you are not supposed to.
3599+
assert!(expr_opt.is_none() || self.tcx.sess.err_count() > 0);
3600+
}
35803601

3581-
ctxt.break_exprs.push(e);
3582-
result
3583-
} else {
3584-
self.eq_types(true, &cause, e_ty, ctxt.unified)
3585-
.map(|InferOk { obligations, .. }| {
3586-
// FIXME(#32730) propagate obligations
3587-
assert!(obligations.is_empty());
3588-
e_ty
3589-
})
3590-
};
3591-
match result {
3592-
Ok(ty) => ctxt.unified = ty,
3593-
Err(err) => {
3594-
self.report_mismatched_types(&cause, ctxt.unified, e_ty, err).emit();
3595-
}
3596-
}
3602+
ctxt.may_break = true;
3603+
} else {
3604+
// Otherwise, we failed to find the enclosing loop; this can only happen if the
3605+
// `break` was not inside a loop at all, which is caught by the loop-checking pass.
3606+
assert!(self.tcx.sess.err_count() > 0);
3607+
}
35973608

3598-
ctxt.may_break = true;
3599-
}
3600-
// Otherwise, we failed to find the enclosing breakable; this can only happen if the
3601-
// `break` target was not found, which is caught in HIR lowering and reported by the
3602-
// loop-checking pass.
3603-
tcx.types.never
3609+
// the type of a `break` is always `!`, since it diverges
3610+
tcx.types.never
36043611
}
36053612
hir::ExprAgain(_) => { tcx.types.never }
36063613
hir::ExprRet(ref expr_opt) => {
@@ -3645,51 +3652,59 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
36453652
expr.span, expected)
36463653
}
36473654
hir::ExprWhile(ref cond, ref body, _) => {
3648-
let unified = self.tcx.mk_nil();
3649-
let coerce_to = unified;
3650-
let ctxt = BreakableCtxt {
3651-
unified: unified,
3652-
coerce_to: coerce_to,
3653-
break_exprs: vec![],
3654-
may_break: true,
3655-
};
3656-
self.with_breakable_ctxt(expr.id, ctxt, || {
3657-
self.check_expr_has_type(&cond, tcx.types.bool);
3658-
let cond_diverging = self.diverges.get();
3659-
self.check_block_no_value(&body);
3655+
let ctxt = BreakableCtxt {
3656+
// cannot use break with a value from a while loop
3657+
coerce: None,
3658+
may_break: true,
3659+
};
36603660

3661-
// We may never reach the body so it diverging means nothing.
3662-
self.diverges.set(cond_diverging);
3663-
});
3661+
self.with_breakable_ctxt(expr.id, ctxt, || {
3662+
self.check_expr_has_type(&cond, tcx.types.bool);
3663+
let cond_diverging = self.diverges.get();
3664+
self.check_block_no_value(&body);
36643665

3665-
if self.has_errors.get() {
3666-
tcx.types.err
3667-
} else {
3668-
tcx.mk_nil()
3669-
}
3666+
// We may never reach the body so it diverging means nothing.
3667+
self.diverges.set(cond_diverging);
3668+
});
3669+
3670+
self.tcx.mk_nil()
36703671
}
3671-
hir::ExprLoop(ref body, _, _) => {
3672-
let unified = self.next_ty_var(TypeVariableOrigin::TypeInference(body.span));
3673-
let coerce_to = expected.only_has_type(self).unwrap_or(unified);
3674-
let ctxt = BreakableCtxt {
3675-
unified: unified,
3676-
coerce_to: coerce_to,
3677-
break_exprs: vec![],
3678-
may_break: false,
3679-
};
3672+
hir::ExprLoop(ref body, _, source) => {
3673+
let coerce = match source {
3674+
// you can only use break with a value from a normal `loop { }`
3675+
hir::LoopSource::Loop => {
3676+
let coerce_to = expected.only_has_type_or_fresh_var(self, body.span);
3677+
Some(CoerceMany::new(coerce_to))
3678+
}
36803679

3681-
let (ctxt, ()) = self.with_breakable_ctxt(expr.id, ctxt, || {
3682-
self.check_block_no_value(&body);
3683-
});
3684-
if ctxt.may_break {
3685-
// No way to know whether it's diverging because
3686-
// of a `break` or an outer `break` or `return.
3687-
self.diverges.set(Diverges::Maybe);
3680+
hir::LoopSource::WhileLet |
3681+
hir::LoopSource::ForLoop => {
3682+
None
3683+
}
3684+
};
36883685

3689-
ctxt.unified
3690-
} else {
3691-
tcx.types.never
3692-
}
3686+
let ctxt = BreakableCtxt {
3687+
coerce: coerce,
3688+
may_break: false, // will get updated if/when we find a `break`
3689+
};
3690+
3691+
let (ctxt, ()) = self.with_breakable_ctxt(expr.id, ctxt, || {
3692+
self.check_block_no_value(&body);
3693+
});
3694+
3695+
if ctxt.may_break {
3696+
// No way to know whether it's diverging because
3697+
// of a `break` or an outer `break` or `return.
3698+
self.diverges.set(Diverges::Maybe);
3699+
}
3700+
3701+
// If we permit break with a value, then result type is
3702+
// the LUB of the breaks (possibly ! if none); else, it
3703+
// is nil. This makes sense because infinite loops
3704+
// (which would have type !) are only possible iff we
3705+
// permit break with a value [1].
3706+
assert!(ctxt.coerce.is_some() || ctxt.may_break); // [1]
3707+
ctxt.coerce.map(|c| c.complete(self)).unwrap_or(self.tcx.mk_nil())
36933708
}
36943709
hir::ExprMatch(ref discrim, ref arms, match_src) => {
36953710
self.check_match(expr, &discrim, arms, expected, match_src)

src/test/compile-fail/issue-27042.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212

1313
fn main() {
1414
let _: i32 =
15-
'a: //~ ERROR mismatched types
16-
loop { break };
15+
'a: // in this case, the citation is just the `break`:
16+
loop { break }; //~ ERROR mismatched types
1717
let _: i32 =
1818
'b: //~ ERROR mismatched types
19-
while true { break };
19+
while true { break }; // but here we cite the whole loop
2020
let _: i32 =
2121
'c: //~ ERROR mismatched types
22-
for _ in None { break };
22+
for _ in None { break }; // but here we cite the whole loop
2323
let _: i32 =
2424
'd: //~ ERROR mismatched types
2525
while let Some(_) = None { break };

src/test/compile-fail/loop-break-value.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,37 +40,40 @@ fn main() {
4040
loop {
4141
break 'while_loop 123;
4242
//~^ ERROR `break` with value from a `while` loop
43-
//~| ERROR mismatched types
4443
break 456;
4544
break 789;
4645
};
4746
}
4847

49-
'while_let_loop: while let Some(_) = Some(()) {
48+
while let Some(_) = Some(()) {
5049
if break () { //~ ERROR `break` with value from a `while let` loop
51-
break;
52-
break None;
53-
//~^ ERROR `break` with value from a `while let` loop
54-
//~| ERROR mismatched types
5550
}
51+
}
52+
53+
while let Some(_) = Some(()) {
54+
break None;
55+
//~^ ERROR `break` with value from a `while let` loop
56+
}
57+
58+
'while_let_loop: while let Some(_) = Some(()) {
5659
loop {
5760
break 'while_let_loop "nope";
5861
//~^ ERROR `break` with value from a `while let` loop
59-
//~| ERROR mismatched types
6062
break 33;
6163
};
6264
}
6365

64-
'for_loop: for _ in &[1,2,3] {
66+
for _ in &[1,2,3] {
6567
break (); //~ ERROR `break` with value from a `for` loop
6668
break [()];
6769
//~^ ERROR `break` with value from a `for` loop
68-
//~| ERROR mismatched types
70+
}
71+
72+
'for_loop: for _ in &[1,2,3] {
6973
loop {
7074
break Some(3);
7175
break 'for_loop Some(17);
7276
//~^ ERROR `break` with value from a `for` loop
73-
//~| ERROR mismatched types
7477
};
7578
}
7679

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(loop_break_value)]
12+
#![allow(unused_variables)]
13+
14+
use std::ptr;
15+
16+
// Test that we only report **one** error here and that is that
17+
// `break` with an expression is illegal in this context. In
18+
// particular, we don't report any mismatched types error, which is
19+
// besides the point.
20+
21+
fn main() {
22+
for _ in &[1,2,3] {
23+
break 22
24+
}
25+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error[E0571]: `break` with value from a `for` loop
2+
--> $DIR/loop-break-value-no-repeat.rs:23:9
3+
|
4+
23 | break 22
5+
| ^^^^^^^^ can only break with a value inside `loop`
6+
7+
error: aborting due to previous error
8+

0 commit comments

Comments
 (0)