Auto merge of #37487 - goffrie:break, r=nikomatsakis

Implement the `loop_break_value` feature.

This implements RFC 1624, tracking issue #37339.
- `FnCtxt` (in typeck) gets a stack of `LoopCtxt`s, which store the
  currently deduced type of that loop, the desired type, and a list of
  break expressions currently seen. `loop` loops get a fresh type
  variable as their initial type (this logic is stolen from that for
  arrays). `while` loops get `()`.
- `break {expr}` looks up the broken loop, and unifies the type of
  `expr` with the type of the loop.
- `break` with no expr unifies the loop's type with `()`.
- When building MIR, loops no longer construct a `()` value at
  termination of the loop; rather, the `break` expression assigns the
  result of the loop.
- ~~I have also changed the loop scoping in MIR-building so that the test
  of a while loop is not considered to be part of that loop. This makes
  the rules consistent with #37360. The new loop scopes in typeck also
  follow this rule. That means that `loop { while (break) {} }` now
  terminates instead of looping forever. This is technically a breaking
  change.~~
- ~~On that note, expressions like `while break {}` and `if break {}` no
  longer parse because `{}` is interpreted as an expression argument to
  `break`. But no code except compiler test cases should do that anyway
  because it makes no sense.~~
- The RFC did not make it clear, but I chose to make `break ()` inside
  of a `while` loop illegal, just in case we wanted to do anything with
  that design space in the future.

This is my first time dealing with this part of rustc so I'm sure
there's plenty of problems to pick on here ^_^
This commit is contained in:
bors 2016-11-22 17:51:59 -06:00 committed by GitHub
commit 1cabe21512
35 changed files with 641 additions and 216 deletions

View File

@ -223,7 +223,7 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
expr_exit
}
hir::ExprLoop(ref body, _) => {
hir::ExprLoop(ref body, _, _) => {
//
// [pred]
// |
@ -282,9 +282,10 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
self.add_unreachable_node()
}
hir::ExprBreak(label) => {
hir::ExprBreak(label, ref opt_expr) => {
let v = self.opt_expr(opt_expr, pred);
let loop_scope = self.find_scope(expr, label.map(|l| l.node));
let b = self.add_ast_node(expr.id, &[pred]);
let b = self.add_ast_node(expr.id, &[v]);
self.add_exiting_edge(expr, b,
loop_scope, loop_scope.break_index);
self.add_unreachable_node()

View File

@ -882,7 +882,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
visitor.visit_block(block);
walk_opt_sp_name(visitor, opt_sp_name);
}
ExprLoop(ref block, ref opt_sp_name) => {
ExprLoop(ref block, ref opt_sp_name, _) => {
visitor.visit_block(block);
walk_opt_sp_name(visitor, opt_sp_name);
}
@ -923,7 +923,11 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
}
visitor.visit_path(path, expression.id)
}
ExprBreak(ref opt_sp_name) | ExprAgain(ref opt_sp_name) => {
ExprBreak(ref opt_sp_name, ref opt_expr) => {
walk_opt_sp_name(visitor, opt_sp_name);
walk_list!(visitor, visit_expr, opt_expr);
}
ExprAgain(ref opt_sp_name) => {
walk_opt_sp_name(visitor, opt_sp_name);
}
ExprRet(ref optional_expression) => {

View File

@ -1136,7 +1136,9 @@ impl<'a> LoweringContext<'a> {
self.lower_opt_sp_ident(opt_ident))
}
ExprKind::Loop(ref body, opt_ident) => {
hir::ExprLoop(self.lower_block(body), self.lower_opt_sp_ident(opt_ident))
hir::ExprLoop(self.lower_block(body),
self.lower_opt_sp_ident(opt_ident),
hir::LoopSource::Loop)
}
ExprKind::Match(ref expr, ref arms) => {
hir::ExprMatch(P(self.lower_expr(expr)),
@ -1242,7 +1244,10 @@ impl<'a> LoweringContext<'a> {
});
hir::ExprPath(hir_qself, self.lower_path(path))
}
ExprKind::Break(opt_ident) => hir::ExprBreak(self.lower_opt_sp_ident(opt_ident)),
ExprKind::Break(opt_ident, ref opt_expr) => {
hir::ExprBreak(self.lower_opt_sp_ident(opt_ident),
opt_expr.as_ref().map(|x| P(self.lower_expr(x))))
}
ExprKind::Continue(opt_ident) => hir::ExprAgain(self.lower_opt_sp_ident(opt_ident)),
ExprKind::Ret(ref e) => hir::ExprRet(e.as_ref().map(|x| P(self.lower_expr(x)))),
ExprKind::InlineAsm(ref asm) => {
@ -1410,7 +1415,8 @@ impl<'a> LoweringContext<'a> {
// `[opt_ident]: loop { ... }`
let loop_block = P(self.block_expr(P(match_expr)));
let loop_expr = hir::ExprLoop(loop_block, self.lower_opt_sp_ident(opt_ident));
let loop_expr = hir::ExprLoop(loop_block, self.lower_opt_sp_ident(opt_ident),
hir::LoopSource::WhileLet);
// add attributes to the outer returned expr node
let attrs = e.attrs.clone();
return hir::Expr { id: e.id, node: loop_expr, span: e.span, attrs: attrs };
@ -1485,7 +1491,8 @@ impl<'a> LoweringContext<'a> {
// `[opt_ident]: loop { ... }`
let loop_block = P(self.block_expr(match_expr));
let loop_expr = hir::ExprLoop(loop_block, self.lower_opt_sp_ident(opt_ident));
let loop_expr = hir::ExprLoop(loop_block, self.lower_opt_sp_ident(opt_ident),
hir::LoopSource::ForLoop);
let loop_expr = P(hir::Expr {
id: e.id,
node: loop_expr,
@ -1723,7 +1730,7 @@ impl<'a> LoweringContext<'a> {
}
fn expr_break(&mut self, span: Span, attrs: ThinVec<Attribute>) -> P<hir::Expr> {
P(self.expr(span, hir::ExprBreak(None), attrs))
P(self.expr(span, hir::ExprBreak(None, None), attrs))
}
fn expr_call(&mut self, span: Span, e: P<hir::Expr>, args: hir::HirVec<hir::Expr>)

View File

@ -908,7 +908,7 @@ pub enum Expr_ {
/// Conditionless loop (can be exited with break, continue, or return)
///
/// `'label: loop { block }`
ExprLoop(P<Block>, Option<Spanned<Name>>),
ExprLoop(P<Block>, Option<Spanned<Name>>, LoopSource),
/// A `match` block, with a source that indicates whether or not it is
/// the result of a desugaring, and if so, which kind.
ExprMatch(P<Expr>, HirVec<Arm>, MatchSource),
@ -944,7 +944,7 @@ pub enum Expr_ {
/// A referencing operation (`&a` or `&mut a`)
ExprAddrOf(Mutability, P<Expr>),
/// A `break`, with an optional label to break
ExprBreak(Option<Spanned<Name>>),
ExprBreak(Option<Spanned<Name>>, Option<P<Expr>>),
/// A `continue`, with an optional label
ExprAgain(Option<Spanned<Name>>),
/// A `return`, with an optional value to be returned
@ -1002,6 +1002,18 @@ pub enum MatchSource {
TryDesugar,
}
/// The loop type that yielded an ExprLoop
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
pub enum LoopSource {
/// A `loop { .. }` loop
Loop,
/// A `while let _ = _ { .. }` loop
WhileLet,
/// A `for _ in _ { .. }` loop
ForLoop,
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
pub enum CaptureClause {
CaptureByValue,

View File

@ -1393,7 +1393,7 @@ impl<'a> State<'a> {
space(&mut self.s)?;
self.print_block(&blk)?;
}
hir::ExprLoop(ref blk, opt_sp_name) => {
hir::ExprLoop(ref blk, opt_sp_name, _) => {
if let Some(sp_name) = opt_sp_name {
self.print_name(sp_name.node)?;
self.word_space(":")?;
@ -1471,13 +1471,17 @@ impl<'a> State<'a> {
hir::ExprPath(Some(ref qself), ref path) => {
self.print_qpath(path, qself, true)?
}
hir::ExprBreak(opt_name) => {
hir::ExprBreak(opt_name, ref opt_expr) => {
word(&mut self.s, "break")?;
space(&mut self.s)?;
if let Some(name) = opt_name {
self.print_name(name.node)?;
space(&mut self.s)?;
}
if let Some(ref expr) = *opt_expr {
self.print_expr(expr)?;
space(&mut self.s)?;
}
}
hir::ExprAgain(opt_name) => {
word(&mut self.s, "continue")?;

View File

@ -472,11 +472,10 @@ impl<'a, 'gcx, 'tcx> ExprUseVisitor<'a, 'gcx, 'tcx> {
self.consume_exprs(inputs);
}
hir::ExprBreak(..) |
hir::ExprAgain(..) |
hir::ExprLit(..) => {}
hir::ExprLoop(ref blk, _) => {
hir::ExprLoop(ref blk, _, _) => {
self.walk_block(&blk);
}
@ -514,7 +513,7 @@ impl<'a, 'gcx, 'tcx> ExprUseVisitor<'a, 'gcx, 'tcx> {
self.walk_block(&blk);
}
hir::ExprRet(ref opt_expr) => {
hir::ExprBreak(_, ref opt_expr) | hir::ExprRet(ref opt_expr) => {
if let Some(ref expr) = *opt_expr {
self.consume_expr(&expr);
}

View File

@ -490,7 +490,7 @@ fn visit_expr(ir: &mut IrMaps, expr: &Expr) {
hir::ExprIndex(..) | hir::ExprField(..) | hir::ExprTupField(..) |
hir::ExprArray(..) | hir::ExprCall(..) | hir::ExprMethodCall(..) |
hir::ExprTup(..) | hir::ExprBinary(..) | hir::ExprAddrOf(..) |
hir::ExprCast(..) | hir::ExprUnary(..) | hir::ExprBreak(_) |
hir::ExprCast(..) | hir::ExprUnary(..) | hir::ExprBreak(..) |
hir::ExprAgain(_) | hir::ExprLit(_) | hir::ExprRet(..) |
hir::ExprBlock(..) | hir::ExprAssign(..) | hir::ExprAssignOp(..) |
hir::ExprStruct(..) | hir::ExprRepeat(..) |
@ -990,7 +990,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
// Note that labels have been resolved, so we don't need to look
// at the label ident
hir::ExprLoop(ref blk, _) => {
hir::ExprLoop(ref blk, _, _) => {
self.propagate_through_loop(expr, LoopLoop, &blk, succ)
}
@ -1035,7 +1035,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
self.propagate_through_opt_expr(o_e.as_ref().map(|e| &**e), exit_ln)
}
hir::ExprBreak(opt_label) => {
hir::ExprBreak(opt_label, ref opt_expr) => {
// Find which label this break jumps to
let sc = self.find_loop_scope(opt_label.map(|l| l.node), expr.id, expr.span);
@ -1043,7 +1043,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
// look it up in the break loop nodes table
match self.break_ln.get(&sc) {
Some(&b) => b,
Some(&b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b),
None => span_bug!(expr.span, "break to unknown label")
}
}
@ -1057,7 +1057,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
match self.cont_ln.get(&sc) {
Some(&b) => b,
None => span_bug!(expr.span, "loop to unknown label")
None => span_bug!(expr.span, "continue to unknown label")
}
}

View File

@ -805,7 +805,7 @@ fn resolve_expr(visitor: &mut RegionResolutionVisitor, expr: &hir::Expr) {
terminating(then.id);
}
hir::ExprLoop(ref body, _) => {
hir::ExprLoop(ref body, _, _) => {
terminating(body.id);
}

View File

@ -462,7 +462,7 @@ fn extract_labels(ctxt: &mut LifetimeContext, b: &hir::Expr) {
fn expression_label(ex: &hir::Expr) -> Option<(ast::Name, Span)> {
match ex.node {
hir::ExprWhile(.., Some(label)) |
hir::ExprLoop(_, Some(label)) => Some((label.node, label.span)),
hir::ExprLoop(_, Some(label), _) => Some((label.node, label.span)),
_ => None,
}
}

View File

@ -19,10 +19,6 @@ use std::iter::repeat;
use std::path::Path;
use std::time::{Duration, Instant};
use hir;
use hir::intravisit;
use hir::intravisit::Visitor;
// The name of the associated type for `Fn` return types
pub const FN_OUTPUT_NAME: &'static str = "Output";
@ -186,57 +182,6 @@ pub fn indenter() -> Indenter {
Indenter { _cannot_construct_outside_of_this_module: () }
}
struct LoopQueryVisitor<P> where P: FnMut(&hir::Expr_) -> bool {
p: P,
flag: bool,
}
impl<'v, P> Visitor<'v> for LoopQueryVisitor<P> where P: FnMut(&hir::Expr_) -> bool {
fn visit_expr(&mut self, e: &hir::Expr) {
self.flag |= (self.p)(&e.node);
match e.node {
// Skip inner loops, since a break in the inner loop isn't a
// break inside the outer loop
hir::ExprLoop(..) | hir::ExprWhile(..) => {}
_ => intravisit::walk_expr(self, e)
}
}
}
// Takes a predicate p, returns true iff p is true for any subexpressions
// of b -- skipping any inner loops (loop, while, loop_body)
pub fn loop_query<P>(b: &hir::Block, p: P) -> bool where P: FnMut(&hir::Expr_) -> bool {
let mut v = LoopQueryVisitor {
p: p,
flag: false,
};
intravisit::walk_block(&mut v, b);
return v.flag;
}
struct BlockQueryVisitor<P> where P: FnMut(&hir::Expr) -> bool {
p: P,
flag: bool,
}
impl<'v, P> Visitor<'v> for BlockQueryVisitor<P> where P: FnMut(&hir::Expr) -> bool {
fn visit_expr(&mut self, e: &hir::Expr) {
self.flag |= (self.p)(e);
intravisit::walk_expr(self, e)
}
}
// Takes a predicate p, returns true iff p is true for any subexpressions
// of b -- skipping any inner loops (loop, while, loop_body)
pub fn block_query<P>(b: &hir::Block, p: P) -> bool where P: FnMut(&hir::Expr) -> bool {
let mut v = BlockQueryVisitor {
p: p,
flag: false,
};
intravisit::walk_block(&mut v, &b);
return v.flag;
}
pub trait MemoizationMap {
type Key: Clone;
type Value: Clone;

View File

@ -855,7 +855,7 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session,
time(time_passes,
"loop checking",
|| loops::check_crate(sess, &hir_map));
|| loops::check_crate(sess, &resolutions.def_map, &hir_map));
time(time_passes,
"static item recursion checking",

View File

@ -322,7 +322,7 @@ fn saw_expr<'a>(node: &'a Expr_,
ExprType(..) => (SawExprType, false),
ExprIf(..) => (SawExprIf, false),
ExprWhile(..) => (SawExprWhile, false),
ExprLoop(_, id) => (SawExprLoop(id.map(|id| id.node.as_str())), false),
ExprLoop(_, id, _) => (SawExprLoop(id.map(|id| id.node.as_str())), false),
ExprMatch(..) => (SawExprMatch, false),
ExprClosure(cc, _, _, _) => (SawExprClosure(cc), false),
ExprBlock(..) => (SawExprBlock, false),
@ -335,7 +335,7 @@ fn saw_expr<'a>(node: &'a Expr_,
ExprIndex(..) => (SawExprIndex, true),
ExprPath(ref qself, _) => (SawExprPath(qself.as_ref().map(|q| q.position)), false),
ExprAddrOf(m, _) => (SawExprAddrOf(m), false),
ExprBreak(id) => (SawExprBreak(id.map(|id| id.node.as_str())), false),
ExprBreak(id, _) => (SawExprBreak(id.map(|id| id.node.as_str())), false),
ExprAgain(id) => (SawExprAgain(id.map(|id| id.node.as_str())), false),
ExprRet(..) => (SawExprRet, false),
ExprInlineAsm(ref a,..) => (SawExprInlineAsm(a), false),

View File

@ -169,41 +169,39 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
this.cfg.terminate(block, source_info,
TerminatorKind::Goto { target: loop_block });
let might_break = this.in_loop_scope(loop_block, exit_block, move |this| {
// conduct the test, if necessary
let body_block;
if let Some(cond_expr) = opt_cond_expr {
// This loop has a condition, ergo its exit_block is reachable.
this.find_loop_scope(expr_span, None).might_break = true;
this.in_loop_scope(
loop_block, exit_block, destination.clone(),
move |this| {
// conduct the test, if necessary
let body_block;
if let Some(cond_expr) = opt_cond_expr {
let loop_block_end;
let cond = unpack!(
loop_block_end = this.as_operand(loop_block, cond_expr));
body_block = this.cfg.start_new_block();
this.cfg.terminate(loop_block_end, source_info,
TerminatorKind::If {
cond: cond,
targets: (body_block, exit_block)
});
let loop_block_end;
let cond = unpack!(loop_block_end = this.as_operand(loop_block, cond_expr));
body_block = this.cfg.start_new_block();
this.cfg.terminate(loop_block_end, source_info,
TerminatorKind::If {
cond: cond,
targets: (body_block, exit_block)
});
} else {
body_block = loop_block;
// if the test is false, there's no `break` to assign `destination`, so
// we have to do it; this overwrites any `break`-assigned value but it's
// always `()` anyway
this.cfg.push_assign_unit(exit_block, source_info, destination);
} else {
body_block = loop_block;
}
// The “return” value of the loop body must always be an unit. We therefore
// introduce a unit temporary as the destination for the loop body.
let tmp = this.get_unit_temp();
// Execute the body, branching back to the test.
let body_block_end = unpack!(this.into(&tmp, body_block, body));
this.cfg.terminate(body_block_end, source_info,
TerminatorKind::Goto { target: loop_block });
}
// The “return” value of the loop body must always be an unit, but we cannot
// reuse that as a “return” value of the whole loop expressions, because some
// loops are diverging (e.g. `loop {}`). Thus, we introduce a unit temporary as
// the destination for the loop body and assign the loops own “return” value
// immediately after the iteration is finished.
let tmp = this.get_unit_temp();
// Execute the body, branching back to the test.
let body_block_end = unpack!(this.into(&tmp, body_block, body));
this.cfg.terminate(body_block_end, source_info,
TerminatorKind::Goto { target: loop_block });
});
// If the loop may reach its exit_block, we assign an empty tuple to the
// destination to keep the MIR well-formed.
if might_break {
this.cfg.push_assign_unit(exit_block, source_info, destination);
}
);
exit_block.unit()
}
ExprKind::Call { ty, fun, args } => {

View File

@ -11,9 +11,7 @@
use build::{BlockAnd, BlockAndExtension, Builder};
use build::scope::LoopScope;
use hair::*;
use rustc::middle::region::CodeExtent;
use rustc::mir::*;
use syntax_pos::Span;
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
@ -79,14 +77,28 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
block.unit()
}
ExprKind::Continue { label } => {
this.break_or_continue(expr_span, label, block,
|loop_scope| loop_scope.continue_block)
let LoopScope { continue_block, extent, .. } =
*this.find_loop_scope(expr_span, label);
this.exit_scope(expr_span, extent, block, continue_block);
this.cfg.start_new_block().unit()
}
ExprKind::Break { label } => {
this.break_or_continue(expr_span, label, block, |loop_scope| {
loop_scope.might_break = true;
loop_scope.break_block
})
ExprKind::Break { label, value } => {
let (break_block, extent, destination) = {
let LoopScope {
break_block,
extent,
ref break_destination,
..
} = *this.find_loop_scope(expr_span, label);
(break_block, extent, break_destination.clone())
};
if let Some(value) = value {
unpack!(block = this.into(&destination, block, value))
} else {
this.cfg.push_assign_unit(block, source_info, &destination)
}
this.exit_scope(expr_span, extent, block, break_block);
this.cfg.start_new_block().unit()
}
ExprKind::Return { value } => {
block = match value {
@ -115,20 +127,4 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
}
}
fn break_or_continue<F>(&mut self,
span: Span,
label: Option<CodeExtent>,
block: BasicBlock,
exit_selector: F)
-> BlockAnd<()>
where F: FnOnce(&mut LoopScope) -> BasicBlock
{
let (exit_block, extent) = {
let loop_scope = self.find_loop_scope(span, label);
(exit_selector(loop_scope), loop_scope.extent)
};
self.exit_scope(span, extent, block, exit_block);
self.cfg.start_new_block().unit()
}
}

View File

@ -38,7 +38,7 @@ pub struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
/// the current set of loops; see the `scope` module for more
/// details
loop_scopes: Vec<scope::LoopScope>,
loop_scopes: Vec<scope::LoopScope<'tcx>>,
/// the vector of all scopes that we have created thus far;
/// we track this for debuginfo later

View File

@ -177,7 +177,7 @@ struct FreeData<'tcx> {
}
#[derive(Clone, Debug)]
pub struct LoopScope {
pub struct LoopScope<'tcx> {
/// Extent of the loop
pub extent: CodeExtent,
/// Where the body of the loop begins
@ -185,8 +185,9 @@ pub struct LoopScope {
/// Block to branch into when the loop terminates (either by being `break`-en out from, or by
/// having its condition to become false)
pub break_block: BasicBlock,
/// Indicates the reachability of the break_block for this loop
pub might_break: bool
/// The destination of the loop expression itself (i.e. where to put the result of a `break`
/// expression)
pub break_destination: Lvalue<'tcx>,
}
impl<'tcx> Scope<'tcx> {
@ -246,10 +247,10 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
///
/// Returns the might_break attribute of the LoopScope used.
pub fn in_loop_scope<F>(&mut self,
loop_block: BasicBlock,
break_block: BasicBlock,
f: F)
-> bool
loop_block: BasicBlock,
break_block: BasicBlock,
break_destination: Lvalue<'tcx>,
f: F)
where F: FnOnce(&mut Builder<'a, 'gcx, 'tcx>)
{
let extent = self.extent_of_innermost_scope();
@ -257,13 +258,12 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
extent: extent.clone(),
continue_block: loop_block,
break_block: break_block,
might_break: false
break_destination: break_destination,
};
self.loop_scopes.push(loop_scope);
f(self);
let loop_scope = self.loop_scopes.pop().unwrap();
assert!(loop_scope.extent == extent);
loop_scope.might_break
}
/// Convenience wrapper that pushes a scope and then executes `f`
@ -386,7 +386,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
pub fn find_loop_scope(&mut self,
span: Span,
label: Option<CodeExtent>)
-> &mut LoopScope {
-> &mut LoopScope<'tcx> {
let loop_scopes = &mut self.loop_scopes;
match label {
None => {

View File

@ -558,8 +558,9 @@ fn make_mirror_unadjusted<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>,
},
hir::ExprRet(ref v) =>
ExprKind::Return { value: v.to_ref() },
hir::ExprBreak(label) =>
ExprKind::Break { label: label.map(|_| loop_label(cx, expr)) },
hir::ExprBreak(label, ref value) =>
ExprKind::Break { label: label.map(|_| loop_label(cx, expr)),
value: value.to_ref() },
hir::ExprAgain(label) =>
ExprKind::Continue { label: label.map(|_| loop_label(cx, expr)) },
hir::ExprMatch(ref discr, ref arms, _) =>
@ -572,7 +573,7 @@ fn make_mirror_unadjusted<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>,
hir::ExprWhile(ref cond, ref body, _) =>
ExprKind::Loop { condition: Some(cond.to_ref()),
body: block::to_expr_ref(cx, body) },
hir::ExprLoop(ref body, _) =>
hir::ExprLoop(ref body, _, _) =>
ExprKind::Loop { condition: None,
body: block::to_expr_ref(cx, body) },
hir::ExprField(ref source, name) => {

View File

@ -202,6 +202,7 @@ pub enum ExprKind<'tcx> {
},
Break {
label: Option<CodeExtent>,
value: Option<ExprRef<'tcx>>,
},
Continue {
label: Option<CodeExtent>,

View File

@ -106,7 +106,7 @@ impl<'a> Visitor for AstValidator<'a> {
ExprKind::Loop(_, Some(ident)) |
ExprKind::WhileLet(.., Some(ident)) |
ExprKind::ForLoop(.., Some(ident)) |
ExprKind::Break(Some(ident)) |
ExprKind::Break(Some(ident), _) |
ExprKind::Continue(Some(ident)) => {
self.check_label(ident.node, ident.span, expr.id);
}

View File

@ -610,7 +610,7 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Expr, node
hir::ExprLoop(..) |
// More control flow (also not very meaningful).
hir::ExprBreak(_) |
hir::ExprBreak(..) |
hir::ExprAgain(_) |
hir::ExprRet(_) |

View File

@ -228,4 +228,5 @@ pub impl Foo for Bar {
register_diagnostics! {
E0472, // asm! is unsupported on this target
E0561, // patterns aren't allowed in function pointer types
E0571, // `break` with a value in a non-`loop`-loop
}

View File

@ -12,34 +12,56 @@ use self::Context::*;
use rustc::session::Session;
use rustc::dep_graph::DepNode;
use rustc::hir::def::{Def, DefMap};
use rustc::hir::map::Map;
use rustc::hir::intravisit::{self, Visitor};
use rustc::hir;
use syntax_pos::Span;
#[derive(Clone, Copy, PartialEq)]
enum LoopKind {
Loop(hir::LoopSource),
WhileLoop,
}
impl LoopKind {
fn name(self) -> &'static str {
match self {
LoopKind::Loop(hir::LoopSource::Loop) => "loop",
LoopKind::Loop(hir::LoopSource::WhileLet) => "while let",
LoopKind::Loop(hir::LoopSource::ForLoop) => "for",
LoopKind::WhileLoop => "while",
}
}
}
#[derive(Clone, Copy, PartialEq)]
enum Context {
Normal,
Loop,
Loop(LoopKind),
Closure,
}
#[derive(Copy, Clone)]
struct CheckLoopVisitor<'a> {
struct CheckLoopVisitor<'a, 'ast: 'a> {
sess: &'a Session,
def_map: &'a DefMap,
hir_map: &'a Map<'ast>,
cx: Context,
}
pub fn check_crate(sess: &Session, map: &Map) {
pub fn check_crate(sess: &Session, def_map: &DefMap, map: &Map) {
let _task = map.dep_graph.in_task(DepNode::CheckLoops);
let krate = map.krate();
krate.visit_all_item_likes(&mut CheckLoopVisitor {
sess: sess,
def_map: def_map,
hir_map: map,
cx: Normal,
}.as_deep_visitor());
}
impl<'a, 'v> Visitor<'v> for CheckLoopVisitor<'a> {
impl<'a, 'ast, 'v> Visitor<'v> for CheckLoopVisitor<'a, 'ast> {
fn visit_item(&mut self, i: &hir::Item) {
self.with_context(Normal, |v| intravisit::walk_item(v, i));
}
@ -51,25 +73,62 @@ impl<'a, 'v> Visitor<'v> for CheckLoopVisitor<'a> {
fn visit_expr(&mut self, e: &hir::Expr) {
match e.node {
hir::ExprWhile(ref e, ref b, _) => {
self.visit_expr(&e);
self.with_context(Loop, |v| v.visit_block(&b));
self.with_context(Loop(LoopKind::WhileLoop), |v| {
v.visit_expr(&e);
v.visit_block(&b);
});
}
hir::ExprLoop(ref b, _) => {
self.with_context(Loop, |v| v.visit_block(&b));
hir::ExprLoop(ref b, _, source) => {
self.with_context(Loop(LoopKind::Loop(source)), |v| v.visit_block(&b));
}
hir::ExprClosure(.., ref b, _) => {
self.with_context(Closure, |v| v.visit_expr(&b));
}
hir::ExprBreak(_) => self.require_loop("break", e.span),
hir::ExprBreak(ref opt_label, ref opt_expr) => {
if opt_expr.is_some() {
let loop_kind = if opt_label.is_some() {
let loop_def = self.def_map.get(&e.id).unwrap().full_def();
if loop_def == Def::Err {
None
} else if let Def::Label(loop_id) = loop_def {
Some(match self.hir_map.expect_expr(loop_id).node {
hir::ExprWhile(..) => LoopKind::WhileLoop,
hir::ExprLoop(_, _, source) => LoopKind::Loop(source),
ref r => span_bug!(e.span,
"break label resolved to a non-loop: {:?}", r),
})
} else {
span_bug!(e.span, "break resolved to a non-label")
}
} else if let Loop(kind) = self.cx {
Some(kind)
} else {
// `break` outside a loop - caught below
None
};
match loop_kind {
None | Some(LoopKind::Loop(hir::LoopSource::Loop)) => (),
Some(kind) => {
struct_span_err!(self.sess, e.span, E0571,
"`break` with value from a `{}` loop",
kind.name())
.span_label(e.span,
&format!("can only break with a value inside `loop`"))
.emit();
}
}
}
self.require_loop("break", e.span);
}
hir::ExprAgain(_) => self.require_loop("continue", e.span),
_ => intravisit::walk_expr(self, e),
}
}
}
impl<'a> CheckLoopVisitor<'a> {
impl<'a, 'ast> CheckLoopVisitor<'a, 'ast> {
fn with_context<F>(&mut self, cx: Context, f: F)
where F: FnOnce(&mut CheckLoopVisitor<'a>)
where F: FnOnce(&mut CheckLoopVisitor<'a, 'ast>)
{
let old_cx = self.cx;
self.cx = cx;
@ -79,7 +138,7 @@ impl<'a> CheckLoopVisitor<'a> {
fn require_loop(&self, name: &str, span: Span) {
match self.cx {
Loop => {}
Loop(_) => {}
Closure => {
struct_span_err!(self.sess, span, E0267, "`{}` inside of a closure", name)
.span_label(span, &format!("cannot break inside of a closure"))

View File

@ -3074,22 +3074,25 @@ impl<'a> Resolver<'a> {
visit::walk_expr(self, expr);
}
ExprKind::Break(Some(label)) | ExprKind::Continue(Some(label)) => {
ExprKind::Break(Some(label), _) | ExprKind::Continue(Some(label)) => {
match self.search_label(label.node) {
None => {
self.record_def(expr.id, err_path_resolution());
resolve_error(self,
label.span,
ResolutionError::UndeclaredLabel(&label.node.name.as_str()))
ResolutionError::UndeclaredLabel(&label.node.name.as_str()));
}
Some(def @ Def::Label(_)) => {
// Since this def is a label, it is never read.
self.record_def(expr.id, PathResolution::new(def))
self.record_def(expr.id, PathResolution::new(def));
}
Some(_) => {
span_bug!(expr.span, "label wasn't mapped to a label def!")
span_bug!(expr.span, "label wasn't mapped to a label def!");
}
}
// visit `break` argument if any
visit::walk_expr(self, expr);
}
ExprKind::IfLet(ref pattern, ref subexpression, ref if_block, ref optional_else) => {

View File

@ -103,7 +103,7 @@ use session::{Session, CompileResult};
use CrateCtxt;
use TypeAndSubsts;
use lint;
use util::common::{block_query, ErrorReported, indenter, loop_query};
use util::common::{ErrorReported, indenter};
use util::nodemap::{DefIdMap, FxHashMap, FxHashSet, NodeMap};
use std::cell::{Cell, Ref, RefCell};
@ -407,6 +407,34 @@ impl Diverges {
}
}
#[derive(Clone)]
pub struct LoopCtxt<'gcx, 'tcx> {
unified: Ty<'tcx>,
coerce_to: Ty<'tcx>,
break_exprs: Vec<&'gcx hir::Expr>,
may_break: bool,
}
#[derive(Clone)]
pub struct EnclosingLoops<'gcx, 'tcx> {
stack: Vec<LoopCtxt<'gcx, 'tcx>>,
by_id: NodeMap<usize>,
}
impl<'gcx, 'tcx> EnclosingLoops<'gcx, 'tcx> {
fn find_loop(&mut self, id: Option<ast::NodeId>) -> Option<&mut LoopCtxt<'gcx, 'tcx>> {
if let Some(id) = id {
if let Some(ix) = self.by_id.get(&id).cloned() {
Some(&mut self.stack[ix])
} else {
None
}
} else {
self.stack.last_mut()
}
}
}
#[derive(Clone)]
pub struct FnCtxt<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
ast_ty_to_ty_cache: RefCell<NodeMap<Ty<'tcx>>>,
@ -433,6 +461,8 @@ pub struct FnCtxt<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
/// Whether any child nodes have any type errors.
has_errors: Cell<bool>,
enclosing_loops: RefCell<EnclosingLoops<'gcx, 'tcx>>,
inh: &'a Inherited<'a, 'gcx, 'tcx>,
}
@ -1503,6 +1533,10 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
ast::CRATE_NODE_ID)),
diverges: Cell::new(Diverges::Maybe),
has_errors: Cell::new(false),
enclosing_loops: RefCell::new(EnclosingLoops {
stack: Vec::new(),
by_id: NodeMap(),
}),
inh: inh,
}
}
@ -3584,7 +3618,74 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
}
tcx.mk_nil()
}
hir::ExprBreak(_) => { tcx.types.never }
hir::ExprBreak(ref label_opt, ref expr_opt) => {
let loop_id = if label_opt.is_some() {
let loop_def = tcx.expect_def(expr.id);
if let Def::Label(loop_id) = loop_def {
Some(Some(loop_id))
} else if loop_def == Def::Err {
// an error was already printed, so just ignore it
None
} else {
span_bug!(expr.span, "break label resolved to a non-label");
}
} else {
Some(None)
};
if let Some(loop_id) = loop_id {
let coerce_to = {
let mut enclosing_loops = self.enclosing_loops.borrow_mut();
enclosing_loops.find_loop(loop_id).map(|ctxt| ctxt.coerce_to)
};
if let Some(coerce_to) = coerce_to {
let e_ty;
let cause;
if let Some(ref e) = *expr_opt {
// Recurse without `enclosing_loops` borrowed.
e_ty = self.check_expr_with_hint(e, coerce_to);
cause = self.misc(e.span);
// Notably, the recursive call may alter coerce_to - must not keep using it!
} else {
// `break` without argument acts like `break ()`.
e_ty = tcx.mk_nil();
cause = self.misc(expr.span);
}
let mut enclosing_loops = self.enclosing_loops.borrow_mut();
let ctxt = enclosing_loops.find_loop(loop_id).unwrap();
let result = if let Some(ref e) = *expr_opt {
// Special-case the first element, as it has no "previous expressions".
let result = if !ctxt.may_break {
self.try_coerce(e, e_ty, ctxt.coerce_to)
} else {
self.try_find_coercion_lub(&cause, || ctxt.break_exprs.iter().cloned(),
ctxt.unified, e, e_ty)
};
ctxt.break_exprs.push(e);
result
} else {
self.eq_types(true, &cause, e_ty, ctxt.unified)
.map(|InferOk { obligations, .. }| {
// FIXME(#32730) propagate obligations
assert!(obligations.is_empty());
e_ty
})
};
match result {
Ok(ty) => ctxt.unified = ty,
Err(err) => {
self.report_mismatched_types(&cause, ctxt.unified, e_ty, err);
}
}
ctxt.may_break = true;
}
// Otherwise, we failed to find the enclosing loop; this can only happen if the
// `break` was not inside a loop at all, which is caught by the loop-checking pass.
}
tcx.types.never
}
hir::ExprAgain(_) => { tcx.types.never }
hir::ExprRet(ref expr_opt) => {
if let Some(ref e) = *expr_opt {
@ -3635,12 +3736,22 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
expr.span, expected)
}
hir::ExprWhile(ref cond, ref body, _) => {
self.check_expr_has_type(&cond, tcx.types.bool);
let cond_diverging = self.diverges.get();
self.check_block_no_value(&body);
let unified = self.tcx.mk_nil();
let coerce_to = unified;
let ctxt = LoopCtxt {
unified: unified,
coerce_to: coerce_to,
break_exprs: vec![],
may_break: true,
};
self.with_loop_ctxt(expr.id, ctxt, || {
self.check_expr_has_type(&cond, tcx.types.bool);
let cond_diverging = self.diverges.get();
self.check_block_no_value(&body);
// We may never reach the body so it diverging means nothing.
self.diverges.set(cond_diverging);
// We may never reach the body so it diverging means nothing.
self.diverges.set(cond_diverging);
});
if self.has_errors.get() {
tcx.types.err
@ -3648,14 +3759,25 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
tcx.mk_nil()
}
}
hir::ExprLoop(ref body, _) => {
self.check_block_no_value(&body);
if may_break(tcx, expr.id, &body) {
hir::ExprLoop(ref body, _, _) => {
let unified = self.next_ty_var();
let coerce_to = expected.only_has_type(self).unwrap_or(unified);
let ctxt = LoopCtxt {
unified: unified,
coerce_to: coerce_to,
break_exprs: vec![],
may_break: false,
};
let ctxt = self.with_loop_ctxt(expr.id, ctxt, || {
self.check_block_no_value(&body);
});
if ctxt.may_break {
// No way to know whether it's diverging because
// of a `break` or an outer `break` or `return.
self.diverges.set(Diverges::Maybe);
tcx.mk_nil()
ctxt.unified
} else {
tcx.types.never
}
@ -4531,27 +4653,24 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
self.tcx.types.err
})
}
}
// Returns true if b contains a break that can exit from b
pub fn may_break(tcx: TyCtxt, id: ast::NodeId, b: &hir::Block) -> bool {
// First: is there an unlabeled break immediately
// inside the loop?
(loop_query(&b, |e| {
match *e {
hir::ExprBreak(None) => true,
_ => false
fn with_loop_ctxt<F: FnOnce()>(&self, id: ast::NodeId, ctxt: LoopCtxt<'gcx, 'tcx>, f: F)
-> LoopCtxt<'gcx, 'tcx> {
let index;
{
let mut enclosing_loops = self.enclosing_loops.borrow_mut();
index = enclosing_loops.stack.len();
enclosing_loops.by_id.insert(id, index);
enclosing_loops.stack.push(ctxt);
}
})) ||
// Second: is there a labeled break with label
// <id> nested anywhere inside the loop?
(block_query(b, |e| {
if let hir::ExprBreak(Some(_)) = e.node {
tcx.expect_def(e.id) == Def::Label(id)
} else {
false
f();
{
let mut enclosing_loops = self.enclosing_loops.borrow_mut();
debug_assert!(enclosing_loops.stack.len() == index + 1);
enclosing_loops.by_id.remove(&id).expect("missing loop context");
(enclosing_loops.stack.pop().expect("missing loop context"))
}
}))
}
}
pub fn check_bounds_are_used<'a, 'tcx>(ccx: &CrateCtxt<'a, 'tcx>,

View File

@ -742,7 +742,7 @@ impl<'a, 'gcx, 'tcx, 'v> Visitor<'v> for RegionCtxt<'a, 'gcx, 'tcx> {
self.check_expr_fn_block(expr, &body);
}
hir::ExprLoop(ref body, _) => {
hir::ExprLoop(ref body, _, _) => {
let repeating_scope = self.set_repeating_scope(body.id);
intravisit::walk_expr(self, expr);
self.set_repeating_scope(repeating_scope);

View File

@ -999,8 +999,8 @@ pub enum ExprKind {
/// A referencing operation (`&a` or `&mut a`)
AddrOf(Mutability, P<Expr>),
/// A `break`, with an optional label to break
Break(Option<SpannedIdent>),
/// A `break`, with an optional label to break, and an optional expression
Break(Option<SpannedIdent>, Option<P<Expr>>),
/// A `continue`, with an optional label
Continue(Option<SpannedIdent>),
/// A `return`, with an optional value to be returned

View File

@ -777,7 +777,7 @@ impl<'a> AstBuilder for ExtCtxt<'a> {
fn expr_break(&self, sp: Span) -> P<ast::Expr> {
self.expr(sp, ast::ExprKind::Break(None))
self.expr(sp, ast::ExprKind::Break(None, None))
}

View File

@ -313,6 +313,9 @@ declare_features! (
(active, link_cfg, "1.14.0", Some(37406)),
(active, use_extern_macros, "1.15.0", Some(35896)),
// Allows `break {expr}` with a value inside `loop`s.
(active, loop_break_value, "1.14.0", Some(37339)),
);
declare_features! (
@ -1189,6 +1192,10 @@ impl<'a> Visitor for PostExpansionVisitor<'a> {
}
}
}
ast::ExprKind::Break(_, Some(_)) => {
gate_feature_post!(&self, loop_break_value, e.span,
"`break` with a value is experimental");
}
_ => {}
}
visit::walk_expr(self, e);

View File

@ -1238,10 +1238,11 @@ pub fn noop_fold_expr<T: Folder>(Expr {id, node, span, attrs}: Expr, folder: &mu
});
ExprKind::Path(qself, folder.fold_path(path))
}
ExprKind::Break(opt_ident) => ExprKind::Break(opt_ident.map(|label|
respan(folder.new_span(label.span),
folder.fold_ident(label.node)))
),
ExprKind::Break(opt_ident, opt_expr) => {
ExprKind::Break(opt_ident.map(|label| respan(folder.new_span(label.span),
folder.fold_ident(label.node))),
opt_expr.map(|e| folder.fold_expr(e)))
}
ExprKind::Continue(opt_ident) => ExprKind::Continue(opt_ident.map(|label|
respan(folder.new_span(label.span),
folder.fold_ident(label.node)))

View File

@ -2256,15 +2256,25 @@ impl<'a> Parser<'a> {
ex = ExprKind::Ret(None);
}
} else if self.eat_keyword(keywords::Break) {
if self.token.is_lifetime() {
ex = ExprKind::Break(Some(Spanned {
let lt = if self.token.is_lifetime() {
let spanned_lt = Spanned {
node: self.get_lifetime(),
span: self.span
}));
};
self.bump();
Some(spanned_lt)
} else {
ex = ExprKind::Break(None);
}
None
};
let e = if self.token.can_begin_expr()
&& !(self.token == token::OpenDelim(token::Brace)
&& self.restrictions.contains(
Restrictions::RESTRICTION_NO_STRUCT_LITERAL)) {
Some(self.parse_expr()?)
} else {
None
};
ex = ExprKind::Break(lt, e);
hi = self.prev_span.hi;
} else if self.token.is_keyword(keywords::Let) {
// Catch this syntax error here, instead of in `check_strict_keywords`, so

View File

@ -2191,13 +2191,17 @@ impl<'a> State<'a> {
ast::ExprKind::Path(Some(ref qself), ref path) => {
try!(self.print_qpath(path, qself, true))
}
ast::ExprKind::Break(opt_ident) => {
ast::ExprKind::Break(opt_ident, ref opt_expr) => {
try!(word(&mut self.s, "break"));
try!(space(&mut self.s));
if let Some(ident) = opt_ident {
try!(self.print_ident(ident.node));
try!(space(&mut self.s));
}
if let Some(ref expr) = *opt_expr {
try!(self.print_expr(expr));
try!(space(&mut self.s));
}
}
ast::ExprKind::Continue(opt_ident) => {
try!(word(&mut self.s, "continue"));

View File

@ -746,7 +746,11 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expression: &Expr) {
}
visitor.visit_path(path, expression.id)
}
ExprKind::Break(ref opt_sp_ident) | ExprKind::Continue(ref opt_sp_ident) => {
ExprKind::Break(ref opt_sp_ident, ref opt_expr) => {
walk_opt_sp_ident(visitor, opt_sp_ident);
walk_list!(visitor, visit_expr, opt_expr);
}
ExprKind::Continue(ref opt_sp_ident) => {
walk_opt_sp_ident(visitor, opt_sp_ident);
}
ExprKind::Ret(ref optional_expression) => {

View File

@ -0,0 +1,15 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
fn main() {
loop {
break 123; //~ ERROR `break` with a value is experimental
}
}

View File

@ -0,0 +1,101 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(loop_break_value)]
#![feature(never_type)]
fn main() {
let val: ! = loop { break break; };
//~^ ERROR mismatched types
loop {
if true {
break "asdf";
} else {
break 123; //~ ERROR mismatched types
}
};
let _: i32 = loop {
break "asdf"; //~ ERROR mismatched types
};
let _: i32 = 'outer_loop: loop {
loop {
break 'outer_loop "nope"; //~ ERROR mismatched types
break "ok";
};
};
'while_loop: while true {
break;
break (); //~ ERROR `break` with value from a `while` loop
loop {
break 'while_loop 123;
//~^ ERROR `break` with value from a `while` loop
//~| ERROR mismatched types
break 456;
break 789;
};
}
'while_let_loop: while let Some(_) = Some(()) {
if break () { //~ ERROR `break` with value from a `while let` loop
break;
break None;
//~^ ERROR `break` with value from a `while let` loop
//~| ERROR mismatched types
}
loop {
break 'while_let_loop "nope";
//~^ ERROR `break` with value from a `while let` loop
//~| ERROR mismatched types
break 33;
};
}
'for_loop: for _ in &[1,2,3] {
break (); //~ ERROR `break` with value from a `for` loop
break [()];
//~^ ERROR `break` with value from a `for` loop
//~| ERROR mismatched types
loop {
break Some(3);
break 'for_loop Some(17);
//~^ ERROR `break` with value from a `for` loop
//~| ERROR mismatched types
};
}
let _: i32 = 'a: loop {
let _: () = 'b: loop {
break ('c: loop {
break;
break 'c 123; //~ ERROR mismatched types
});
break 'a 123;
};
};
loop {
break (break, break); //~ ERROR mismatched types
};
loop {
break;
break 2; //~ ERROR mismatched types
};
loop {
break 2;
break; //~ ERROR mismatched types
break 4;
};
}

View File

@ -0,0 +1,133 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(loop_break_value)]
#![feature(never_type)]
#[allow(unused)]
fn never_returns() {
loop {
break loop {};
}
}
pub fn main() {
let value = 'outer: loop {
if 1 == 1 {
break 13;
} else {
let _never: ! = loop {
break loop {
break 'outer panic!();
}
};
}
};
assert_eq!(value, 13);
let x = [1, 3u32, 5];
let y = [17];
let z = [];
let coerced: &[_] = loop {
match 2 {
1 => break &x,
2 => break &y,
3 => break &z,
_ => (),
}
};
assert_eq!(coerced, &[17u32]);
let trait_unified = loop {
break if true {
break Default::default()
} else {
break [13, 14]
};
};
assert_eq!(trait_unified, [0, 0]);
let trait_unified_2 = loop {
if false {
break [String::from("Hello")]
} else {
break Default::default()
};
};
assert_eq!(trait_unified_2, [""]);
let trait_unified_3 = loop {
break if false {
break [String::from("Hello")]
} else {
["Yes".into()]
};
};
assert_eq!(trait_unified_3, ["Yes"]);
let regular_break = loop {
if true {
break;
} else {
break break Default::default();
}
};
assert_eq!(regular_break, ());
let regular_break_2 = loop {
if true {
break Default::default();
} else {
break;
}
};
assert_eq!(regular_break_2, ());
let regular_break_3 = loop {
break if true {
Default::default()
} else {
break;
}
};
assert_eq!(regular_break_3, ());
let regular_break_4 = loop {
break ();
break;
};
assert_eq!(regular_break_4, ());
let regular_break_5 = loop {
break;
break ();
};
assert_eq!(regular_break_5, ());
let nested_break_value = 'outer2: loop {
let _a: u32 = 'inner: loop {
if true {
break 'outer2 "hello";
} else {
break 'inner 17;
}
};
panic!();
};
assert_eq!(nested_break_value, "hello");
let break_from_while_cond = loop {
while break {
panic!();
}
break 123;
};
assert_eq!(break_from_while_cond, 123);
}