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:
commit
1cabe21512
@ -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()
|
||||
|
@ -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) => {
|
||||
|
@ -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>)
|
||||
|
@ -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,
|
||||
|
@ -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")?;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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),
|
||||
|
@ -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 loop’s 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 } => {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 => {
|
||||
|
@ -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) => {
|
||||
|
@ -202,6 +202,7 @@ pub enum ExprKind<'tcx> {
|
||||
},
|
||||
Break {
|
||||
label: Option<CodeExtent>,
|
||||
value: Option<ExprRef<'tcx>>,
|
||||
},
|
||||
Continue {
|
||||
label: Option<CodeExtent>,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(_) |
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"))
|
||||
|
@ -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) => {
|
||||
|
@ -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>,
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
|
@ -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"));
|
||||
|
@ -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) => {
|
||||
|
15
src/test/compile-fail/feature-gate-loop-break-value.rs
Normal file
15
src/test/compile-fail/feature-gate-loop-break-value.rs
Normal 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
|
||||
}
|
||||
}
|
101
src/test/compile-fail/loop-break-value.rs
Normal file
101
src/test/compile-fail/loop-break-value.rs
Normal 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;
|
||||
};
|
||||
}
|
133
src/test/run-pass/loop-break-value.rs
Normal file
133
src/test/run-pass/loop-break-value.rs
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user