Rollup merge of #40229 - cramertj:break-to-blocks, r=nikomatsakis

Implement `?` in catch expressions

Builds on #39921. Final part of #39849.

r? @nikomatsakis
This commit is contained in:
Corey Farwell 2017-03-20 23:44:55 -04:00 committed by GitHub
commit fcc2d25794
17 changed files with 642 additions and 273 deletions

View File

@ -22,13 +22,20 @@ struct CFGBuilder<'a, 'tcx: 'a> {
graph: CFGGraph,
fn_exit: CFGIndex,
loop_scopes: Vec<LoopScope>,
breakable_block_scopes: Vec<BlockScope>,
}
#[derive(Copy, Clone)]
struct BlockScope {
block_expr_id: ast::NodeId, // id of breakable block expr node
break_index: CFGIndex, // where to go on `break`
}
#[derive(Copy, Clone)]
struct LoopScope {
loop_id: ast::NodeId, // id of loop/while node
continue_index: CFGIndex, // where to go on a `loop`
break_index: CFGIndex, // where to go on a `break
break_index: CFGIndex, // where to go on a `break`
}
pub fn construct<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
@ -53,6 +60,7 @@ pub fn construct<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
graph: graph,
fn_exit: fn_exit,
loop_scopes: Vec::new(),
breakable_block_scopes: Vec::new(),
};
body_exit = cfg_builder.expr(&body.value, entry);
cfg_builder.add_contained_edge(body_exit, fn_exit);
@ -66,14 +74,34 @@ pub fn construct<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
fn block(&mut self, blk: &hir::Block, pred: CFGIndex) -> CFGIndex {
let mut stmts_exit = pred;
for stmt in &blk.stmts {
stmts_exit = self.stmt(stmt, stmts_exit);
if let Some(break_to_expr_id) = blk.break_to_expr_id {
let expr_exit = self.add_ast_node(blk.id, &[]);
self.breakable_block_scopes.push(BlockScope {
block_expr_id: break_to_expr_id,
break_index: expr_exit,
});
let mut stmts_exit = pred;
for stmt in &blk.stmts {
stmts_exit = self.stmt(stmt, stmts_exit);
}
let blk_expr_exit = self.opt_expr(&blk.expr, stmts_exit);
self.add_contained_edge(blk_expr_exit, expr_exit);
self.breakable_block_scopes.pop();
expr_exit
} else {
let mut stmts_exit = pred;
for stmt in &blk.stmts {
stmts_exit = self.stmt(stmt, stmts_exit);
}
let expr_exit = self.opt_expr(&blk.expr, stmts_exit);
self.add_ast_node(blk.id, &[expr_exit])
}
let expr_exit = self.opt_expr(&blk.expr, stmts_exit);
self.add_ast_node(blk.id, &[expr_exit])
}
fn stmt(&mut self, stmt: &hir::Stmt, pred: CFGIndex) -> CFGIndex {
@ -295,18 +323,18 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
hir::ExprBreak(destination, ref opt_expr) => {
let v = self.opt_expr(opt_expr, pred);
let loop_scope = self.find_scope(expr, destination);
let (scope_id, break_dest) =
self.find_scope_edge(expr, destination, ScopeCfKind::Break);
let b = self.add_ast_node(expr.id, &[v]);
self.add_exiting_edge(expr, b,
loop_scope, loop_scope.break_index);
self.add_exiting_edge(expr, b, scope_id, break_dest);
self.add_unreachable_node()
}
hir::ExprAgain(destination) => {
let loop_scope = self.find_scope(expr, destination);
let (scope_id, cont_dest) =
self.find_scope_edge(expr, destination, ScopeCfKind::Continue);
let a = self.add_ast_node(expr.id, &[pred]);
self.add_exiting_edge(expr, a,
loop_scope, loop_scope.continue_index);
self.add_exiting_edge(expr, a, scope_id, cont_dest);
self.add_unreachable_node()
}
@ -552,11 +580,11 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
fn add_exiting_edge(&mut self,
from_expr: &hir::Expr,
from_index: CFGIndex,
to_loop: LoopScope,
scope_id: ast::NodeId,
to_index: CFGIndex) {
let mut data = CFGEdgeData { exiting_scopes: vec![] };
let mut scope = self.tcx.region_maps.node_extent(from_expr.id);
let target_scope = self.tcx.region_maps.node_extent(to_loop.loop_id);
let target_scope = self.tcx.region_maps.node_extent(scope_id);
while scope != target_scope {
data.exiting_scopes.push(scope.node_id(&self.tcx.region_maps));
scope = self.tcx.region_maps.encl_scope(scope);
@ -576,20 +604,42 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
self.graph.add_edge(from_index, self.fn_exit, data);
}
fn find_scope(&self,
fn find_scope_edge(&self,
expr: &hir::Expr,
destination: hir::Destination) -> LoopScope {
destination: hir::Destination,
scope_cf_kind: ScopeCfKind) -> (ast::NodeId, CFGIndex) {
match destination.loop_id.into() {
Ok(loop_id) => {
match destination.target_id {
hir::ScopeTarget::Block(block_expr_id) => {
for b in &self.breakable_block_scopes {
if b.block_expr_id == block_expr_id {
return (block_expr_id, match scope_cf_kind {
ScopeCfKind::Break => b.break_index,
ScopeCfKind::Continue => bug!("can't continue to block"),
});
}
}
span_bug!(expr.span, "no block expr for id {}", block_expr_id);
}
hir::ScopeTarget::Loop(hir::LoopIdResult::Ok(loop_id)) => {
for l in &self.loop_scopes {
if l.loop_id == loop_id {
return *l;
return (loop_id, match scope_cf_kind {
ScopeCfKind::Break => l.break_index,
ScopeCfKind::Continue => l.continue_index,
});
}
}
span_bug!(expr.span, "no loop scope for id {}", loop_id);
}
Err(err) => span_bug!(expr.span, "loop scope error: {}", err),
hir::ScopeTarget::Loop(hir::LoopIdResult::Err(err)) =>
span_bug!(expr.span, "loop scope error: {}", err),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum ScopeCfKind {
Break,
Continue,
}

View File

@ -1008,18 +1008,24 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
}
ExprBreak(label, ref opt_expr) => {
label.ident.map(|ident| {
if let Ok(loop_id) = label.loop_id.into() {
visitor.visit_def_mention(Def::Label(loop_id));
}
match label.target_id {
ScopeTarget::Block(node_id) |
ScopeTarget::Loop(LoopIdResult::Ok(node_id)) =>
visitor.visit_def_mention(Def::Label(node_id)),
ScopeTarget::Loop(LoopIdResult::Err(_)) => {},
};
visitor.visit_name(ident.span, ident.node.name);
});
walk_list!(visitor, visit_expr, opt_expr);
}
ExprAgain(label) => {
label.ident.map(|ident| {
if let Ok(loop_id) = label.loop_id.into() {
visitor.visit_def_mention(Def::Label(loop_id));
}
match label.target_id {
ScopeTarget::Block(_) => bug!("can't `continue` to a non-loop block"),
ScopeTarget::Loop(LoopIdResult::Ok(node_id)) =>
visitor.visit_def_mention(Def::Label(node_id)),
ScopeTarget::Loop(LoopIdResult::Err(_)) => {},
};
visitor.visit_name(ident.span, ident.node.name);
});
}

View File

@ -356,22 +356,26 @@ impl<'a> LoweringContext<'a> {
o_id.map(|sp_ident| respan(sp_ident.span, sp_ident.node.name))
}
fn lower_destination(&mut self, destination: Option<(NodeId, Spanned<Ident>)>)
fn lower_loop_destination(&mut self, destination: Option<(NodeId, Spanned<Ident>)>)
-> hir::Destination
{
match destination {
Some((id, label_ident)) => hir::Destination {
ident: Some(label_ident),
loop_id: if let Def::Label(loop_id) = self.expect_full_def(id) {
Some((id, label_ident)) => {
let target = if let Def::Label(loop_id) = self.expect_full_def(id) {
hir::LoopIdResult::Ok(loop_id)
} else {
hir::LoopIdResult::Err(hir::LoopIdError::UnresolvedLabel)
};
hir::Destination {
ident: Some(label_ident),
target_id: hir::ScopeTarget::Loop(target),
}
},
None => hir::Destination {
ident: None,
loop_id: self.loop_scopes.last().map(|innermost_loop_id| Ok(*innermost_loop_id))
.unwrap_or(Err(hir::LoopIdError::OutsideLoopScope)).into()
target_id: hir::ScopeTarget::Loop(
self.loop_scopes.last().map(|innermost_loop_id| Ok(*innermost_loop_id))
.unwrap_or(Err(hir::LoopIdError::OutsideLoopScope)).into())
}
}
}
@ -990,7 +994,7 @@ impl<'a> LoweringContext<'a> {
bounds.iter().map(|bound| self.lower_ty_param_bound(bound)).collect()
}
fn lower_block(&mut self, b: &Block) -> P<hir::Block> {
fn lower_block(&mut self, b: &Block, break_to: Option<NodeId>) -> P<hir::Block> {
let mut expr = None;
let mut stmts = b.stmts.iter().flat_map(|s| self.lower_stmt(s)).collect::<Vec<_>>();
@ -1008,6 +1012,7 @@ impl<'a> LoweringContext<'a> {
expr: expr,
rules: self.lower_block_check_mode(&b.rules),
span: b.span,
break_to_expr_id: break_to,
})
}
@ -1085,7 +1090,7 @@ impl<'a> LoweringContext<'a> {
}
ItemKind::Fn(ref decl, unsafety, constness, abi, ref generics, ref body) => {
self.with_new_scopes(|this| {
let body = this.lower_block(body);
let body = this.lower_block(body, None);
let body = this.expr_block(body, ThinVec::new());
let body_id = this.record_body(body, Some(decl));
hir::ItemFn(this.lower_fn_decl(decl),
@ -1179,7 +1184,7 @@ impl<'a> LoweringContext<'a> {
hir::TraitMethod::Required(names))
}
TraitItemKind::Method(ref sig, Some(ref body)) => {
let body = this.lower_block(body);
let body = this.lower_block(body, None);
let expr = this.expr_block(body, ThinVec::new());
let body_id = this.record_body(expr, Some(&sig.decl));
hir::TraitItemKind::Method(this.lower_method_sig(sig),
@ -1235,7 +1240,7 @@ impl<'a> LoweringContext<'a> {
hir::ImplItemKind::Const(this.lower_ty(ty), body_id)
}
ImplItemKind::Method(ref sig, ref body) => {
let body = this.lower_block(body);
let body = this.lower_block(body, None);
let expr = this.expr_block(body, ThinVec::new());
let body_id = this.record_body(expr, Some(&sig.decl));
hir::ImplItemKind::Method(this.lower_method_sig(sig), body_id)
@ -1662,6 +1667,7 @@ impl<'a> LoweringContext<'a> {
id: id,
rules: hir::DefaultBlock,
span: span,
break_to_expr_id: None,
});
P(self.expr_block(blk, ThinVec::new()))
}
@ -1669,24 +1675,24 @@ impl<'a> LoweringContext<'a> {
}
});
hir::ExprIf(P(self.lower_expr(cond)), self.lower_block(blk), else_opt)
hir::ExprIf(P(self.lower_expr(cond)), self.lower_block(blk, None), else_opt)
}
ExprKind::While(ref cond, ref body, opt_ident) => {
self.with_loop_scope(e.id, |this|
hir::ExprWhile(
this.with_loop_condition_scope(|this| P(this.lower_expr(cond))),
this.lower_block(body),
this.lower_block(body, None),
this.lower_opt_sp_ident(opt_ident)))
}
ExprKind::Loop(ref body, opt_ident) => {
self.with_loop_scope(e.id, |this|
hir::ExprLoop(this.lower_block(body),
hir::ExprLoop(this.lower_block(body, None),
this.lower_opt_sp_ident(opt_ident),
hir::LoopSource::Loop))
}
ExprKind::Catch(ref body) => {
// FIXME(cramertj): Add catch to HIR
self.with_catch_scope(e.id, |this| hir::ExprBlock(this.lower_block(body)))
self.with_catch_scope(e.id, |this|
hir::ExprBlock(this.lower_block(body, Some(e.id))))
}
ExprKind::Match(ref expr, ref arms) => {
hir::ExprMatch(P(self.lower_expr(expr)),
@ -1704,7 +1710,7 @@ impl<'a> LoweringContext<'a> {
})
})
}
ExprKind::Block(ref blk) => hir::ExprBlock(self.lower_block(blk)),
ExprKind::Block(ref blk) => hir::ExprBlock(self.lower_block(blk, None)),
ExprKind::Assign(ref el, ref er) => {
hir::ExprAssign(P(self.lower_expr(el)), P(self.lower_expr(er)))
}
@ -1783,10 +1789,11 @@ impl<'a> LoweringContext<'a> {
let label_result = if self.is_in_loop_condition && opt_ident.is_none() {
hir::Destination {
ident: opt_ident,
loop_id: Err(hir::LoopIdError::UnlabeledCfInWhileCondition).into(),
target_id: hir::ScopeTarget::Loop(
Err(hir::LoopIdError::UnlabeledCfInWhileCondition).into()),
}
} else {
self.lower_destination(opt_ident.map(|ident| (e.id, ident)))
self.lower_loop_destination(opt_ident.map(|ident| (e.id, ident)))
};
hir::ExprBreak(
label_result,
@ -1797,11 +1804,11 @@ impl<'a> LoweringContext<'a> {
if self.is_in_loop_condition && opt_ident.is_none() {
hir::Destination {
ident: opt_ident,
loop_id: Err(
hir::LoopIdError::UnlabeledCfInWhileCondition).into(),
target_id: hir::ScopeTarget::Loop(Err(
hir::LoopIdError::UnlabeledCfInWhileCondition).into()),
}
} else {
self.lower_destination(opt_ident.map( |ident| (e.id, ident)))
self.lower_loop_destination(opt_ident.map( |ident| (e.id, ident)))
}),
ExprKind::Ret(ref e) => hir::ExprRet(e.as_ref().map(|x| P(self.lower_expr(x)))),
ExprKind::InlineAsm(ref asm) => {
@ -1859,7 +1866,7 @@ impl<'a> LoweringContext<'a> {
// `<pat> => <body>`
let pat_arm = {
let body = self.lower_block(body);
let body = self.lower_block(body, None);
let body_expr = P(self.expr_block(body, ThinVec::new()));
let pat = self.lower_pat(pat);
self.arm(hir_vec![pat], body_expr)
@ -1946,7 +1953,7 @@ impl<'a> LoweringContext<'a> {
// Note that the block AND the condition are evaluated in the loop scope.
// This is done to allow `break` from inside the condition of the loop.
let (body, break_expr, sub_expr) = self.with_loop_scope(e.id, |this| (
this.lower_block(body),
this.lower_block(body, None),
this.expr_break(e.span, ThinVec::new()),
this.with_loop_condition_scope(|this| P(this.lower_expr(sub_expr))),
));
@ -2007,7 +2014,8 @@ impl<'a> LoweringContext<'a> {
// `::std::option::Option::Some(<pat>) => <body>`
let pat_arm = {
let body_block = self.with_loop_scope(e.id, |this| this.lower_block(body));
let body_block = self.with_loop_scope(e.id,
|this| this.lower_block(body, None));
let body_expr = P(self.expr_block(body_block, ThinVec::new()));
let pat = self.lower_pat(pat);
let some_pat = self.pat_some(e.span, pat);
@ -2090,14 +2098,12 @@ impl<'a> LoweringContext<'a> {
// match Carrier::translate(<expr>) {
// Ok(val) => #[allow(unreachable_code)] val,
// Err(err) => #[allow(unreachable_code)]
// // If there is an enclosing `catch {...}`
// break 'catch_target Carrier::from_error(From::from(err)),
// // Otherwise
// return Carrier::from_error(From::from(err)),
// }
// FIXME(cramertj): implement breaking to catch
if !self.catch_scopes.is_empty() {
bug!("`?` in catch scopes is unimplemented")
}
let unstable_span = self.allow_internal_unstable("?", e.span);
// Carrier::translate(<expr>)
@ -2157,9 +2163,24 @@ impl<'a> LoweringContext<'a> {
P(self.expr_call(e.span, from_err, hir_vec![from_expr]))
};
let ret_expr = P(self.expr(e.span,
hir::Expr_::ExprRet(Some(from_err_expr)),
ThinVec::from(attrs)));
let thin_attrs = ThinVec::from(attrs);
let catch_scope = self.catch_scopes.last().map(|x| *x);
let ret_expr = if let Some(catch_node) = catch_scope {
P(self.expr(
e.span,
hir::ExprBreak(
hir::Destination {
ident: None,
target_id: hir::ScopeTarget::Block(catch_node),
},
Some(from_err_expr)
),
thin_attrs))
} else {
P(self.expr(e.span,
hir::Expr_::ExprRet(Some(from_err_expr)),
thin_attrs))
};
let err_pat = self.pat_err(e.span, err_local);
self.arm(hir_vec![err_pat], ret_expr)
@ -2302,7 +2323,7 @@ impl<'a> LoweringContext<'a> {
}
fn expr_break(&mut self, span: Span, attrs: ThinVec<Attribute>) -> P<hir::Expr> {
let expr_break = hir::ExprBreak(self.lower_destination(None), None);
let expr_break = hir::ExprBreak(self.lower_loop_destination(None), None);
P(self.expr(span, expr_break, attrs))
}
@ -2415,6 +2436,7 @@ impl<'a> LoweringContext<'a> {
id: self.next_id(),
rules: hir::DefaultBlock,
span: span,
break_to_expr_id: None,
}
}
@ -2519,6 +2541,7 @@ impl<'a> LoweringContext<'a> {
id: id,
stmts: stmts,
expr: Some(expr),
break_to_expr_id: None,
});
self.expr_block(block, attrs)
}

View File

@ -485,6 +485,9 @@ pub struct Block {
/// Distinguishes between `unsafe { ... }` and `{ ... }`
pub rules: BlockCheckMode,
pub span: Span,
/// The id of the expression that `break` breaks to if the block can be broken out of.
/// Currently only `Some(_)` for `catch {}` blocks
pub break_to_expr_id: Option<NodeId>,
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash)]
@ -1080,6 +1083,22 @@ impl From<Result<NodeId, LoopIdError>> for LoopIdResult {
}
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
pub enum ScopeTarget {
Block(NodeId),
Loop(LoopIdResult),
}
impl ScopeTarget {
pub fn opt_id(self) -> Option<NodeId> {
match self {
ScopeTarget::Block(node_id) |
ScopeTarget::Loop(LoopIdResult::Ok(node_id)) => Some(node_id),
ScopeTarget::Loop(LoopIdResult::Err(_)) => None,
}
}
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
pub struct Destination {
// This is `Some(_)` iff there is an explicit user-specified `label
@ -1087,7 +1106,7 @@ pub struct Destination {
// These errors are caught and then reported during the diagnostics pass in
// librustc_passes/loops.rs
pub loop_id: LoopIdResult,
pub target_id: ScopeTarget,
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]

View File

@ -516,14 +516,15 @@ struct Liveness<'a, 'tcx: 'a> {
s: Specials,
successors: Vec<LiveNode>,
users: Vec<Users>,
// The list of node IDs for the nested loop scopes
// we're in.
loop_scope: Vec<NodeId>,
// mappings from loop node ID to LiveNode
// ("break" label should map to loop node ID,
// it probably doesn't now)
break_ln: NodeMap<LiveNode>,
cont_ln: NodeMap<LiveNode>
cont_ln: NodeMap<LiveNode>,
// mappings from node ID to LiveNode for "breakable" blocks-- currently only `catch {...}`
breakable_block_ln: NodeMap<LiveNode>,
}
impl<'a, 'tcx> Liveness<'a, 'tcx> {
@ -550,9 +551,9 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
s: specials,
successors: vec![invalid_node(); num_live_nodes],
users: vec![invalid_users(); num_live_nodes * num_vars],
loop_scope: Vec::new(),
break_ln: NodeMap(),
cont_ln: NodeMap(),
breakable_block_ln: NodeMap(),
}
}
@ -793,15 +794,17 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
debug!("compute: using id for body, {}", self.ir.tcx.hir.node_to_pretty_string(body.id));
let exit_ln = self.s.exit_ln;
let entry_ln: LiveNode = self.with_loop_nodes(body.id, exit_ln, exit_ln, |this| {
// the fallthrough exit is only for those cases where we do not
// explicitly return:
let s = this.s;
this.init_from_succ(s.fallthrough_ln, s.exit_ln);
this.acc(s.fallthrough_ln, s.clean_exit_var, ACC_READ);
this.propagate_through_expr(body, s.fallthrough_ln)
});
self.break_ln.insert(body.id, exit_ln);
self.cont_ln.insert(body.id, exit_ln);
// the fallthrough exit is only for those cases where we do not
// explicitly return:
let s = self.s;
self.init_from_succ(s.fallthrough_ln, s.exit_ln);
self.acc(s.fallthrough_ln, s.clean_exit_var, ACC_READ);
let entry_ln = self.propagate_through_expr(body, s.fallthrough_ln);
// hack to skip the loop unless debug! is enabled:
debug!("^^ liveness computation results for body {} (entry={:?})",
@ -818,6 +821,9 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
fn propagate_through_block(&mut self, blk: &hir::Block, succ: LiveNode)
-> LiveNode {
if let Some(break_to_expr_id) = blk.break_to_expr_id {
self.breakable_block_ln.insert(break_to_expr_id, succ);
}
let succ = self.propagate_through_opt_expr(blk.expr.as_ref().map(|e| &**e), succ);
blk.stmts.iter().rev().fold(succ, |succ, stmt| {
self.propagate_through_stmt(stmt, succ)
@ -901,30 +907,32 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
}
hir::ExprClosure(.., blk_id, _) => {
debug!("{} is an ExprClosure",
self.ir.tcx.hir.node_to_pretty_string(expr.id));
debug!("{} is an ExprClosure", self.ir.tcx.hir.node_to_pretty_string(expr.id));
/*
The next-node for a break is the successor of the entire
loop. The next-node for a continue is the top of this loop.
*/
let node = self.live_node(expr.id, expr.span);
self.with_loop_nodes(blk_id.node_id, succ, node, |this| {
// the construction of a closure itself is not important,
// but we have to consider the closed over variables.
let caps = match this.ir.capture_info_map.get(&expr.id) {
Some(caps) => caps.clone(),
None => {
span_bug!(expr.span, "no registered caps");
}
};
caps.iter().rev().fold(succ, |succ, cap| {
this.init_from_succ(cap.ln, succ);
let var = this.variable(cap.var_nid, expr.span);
this.acc(cap.ln, var, ACC_READ | ACC_USE);
cap.ln
})
let break_ln = succ;
let cont_ln = node;
self.break_ln.insert(blk_id.node_id, break_ln);
self.cont_ln.insert(blk_id.node_id, cont_ln);
// the construction of a closure itself is not important,
// but we have to consider the closed over variables.
let caps = match self.ir.capture_info_map.get(&expr.id) {
Some(caps) => caps.clone(),
None => {
span_bug!(expr.span, "no registered caps");
}
};
caps.iter().rev().fold(succ, |succ, cap| {
self.init_from_succ(cap.ln, succ);
let var = self.variable(cap.var_nid, expr.span);
self.acc(cap.ln, var, ACC_READ | ACC_USE);
cap.ln
})
}
@ -1003,28 +1011,33 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
hir::ExprBreak(label, ref opt_expr) => {
// Find which label this break jumps to
let sc = match label.loop_id.into() {
Ok(loop_id) => loop_id,
Err(err) => span_bug!(expr.span, "loop scope error: {}", err),
};
let target = match label.target_id {
hir::ScopeTarget::Block(node_id) =>
self.breakable_block_ln.get(&node_id),
hir::ScopeTarget::Loop(hir::LoopIdResult::Ok(node_id)) =>
self.break_ln.get(&node_id),
hir::ScopeTarget::Loop(hir::LoopIdResult::Err(err)) =>
span_bug!(expr.span, "loop scope error: {}", err),
}.map(|x| *x);
// Now that we know the label we're going to,
// look it up in the break loop nodes table
match self.break_ln.get(&sc) {
Some(&b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b),
match target {
Some(b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b),
None => span_bug!(expr.span, "break to unknown label")
}
}
hir::ExprAgain(label) => {
// Find which label this expr continues to
let sc = match label.loop_id.into() {
Ok(loop_id) => loop_id,
Err(err) => span_bug!(expr.span, "loop scope error: {}", err),
let sc = match label.target_id {
hir::ScopeTarget::Block(_) => bug!("can't `continue` to a non-loop block"),
hir::ScopeTarget::Loop(hir::LoopIdResult::Ok(node_id)) => node_id,
hir::ScopeTarget::Loop(hir::LoopIdResult::Err(err)) =>
span_bug!(expr.span, "loop scope error: {}", err),
};
// Now that we know the label we're going to,
// look it up in the continue loop nodes table
@ -1287,14 +1300,16 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
debug!("propagate_through_loop: using id for loop body {} {}",
expr.id, self.ir.tcx.hir.node_to_pretty_string(body.id));
let (cond_ln, body_ln) = self.with_loop_nodes(expr.id, succ, ln, |this| {
let cond_ln = match kind {
LoopLoop => ln,
WhileLoop(ref cond) => this.propagate_through_expr(&cond, ln),
};
let body_ln = this.propagate_through_block(body, cond_ln);
(cond_ln, body_ln)
});
let break_ln = succ;
let cont_ln = ln;
self.break_ln.insert(expr.id, break_ln);
self.cont_ln.insert(expr.id, cont_ln);
let cond_ln = match kind {
LoopLoop => ln,
WhileLoop(ref cond) => self.propagate_through_expr(&cond, ln),
};
let body_ln = self.propagate_through_block(body, cond_ln);
// repeat until fixed point is reached:
while self.merge_from_succ(ln, body_ln, first_merge) {
@ -1307,29 +1322,11 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
}
};
assert!(cond_ln == new_cond_ln);
assert!(body_ln == self.with_loop_nodes(expr.id, succ, ln,
|this| this.propagate_through_block(body, cond_ln)));
assert!(body_ln == self.propagate_through_block(body, cond_ln));
}
cond_ln
}
fn with_loop_nodes<R, F>(&mut self,
loop_node_id: NodeId,
break_ln: LiveNode,
cont_ln: LiveNode,
f: F)
-> R where
F: FnOnce(&mut Liveness<'a, 'tcx>) -> R,
{
debug!("with_loop_nodes: {} {}", loop_node_id, break_ln.get());
self.loop_scope.push(loop_node_id);
self.break_ln.insert(loop_node_id, break_ln);
self.cont_ln.insert(loop_node_id, cont_ln);
let r = f(self);
self.loop_scope.pop();
r
}
}
// _______________________________________________________________________

View File

@ -40,7 +40,19 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
this.in_scope(extent, block, |this| this.into(destination, block, value))
}
ExprKind::Block { body: ast_block } => {
this.ast_block(destination, block, ast_block)
if let Some(_) = ast_block.break_to_expr_id {
// This is a `break`-able block (currently only `catch { ... }`)
let exit_block = this.cfg.start_new_block();
let block_exit = this.in_breakable_scope(None, exit_block,
destination.clone(), |this| {
this.ast_block(destination, block, ast_block)
});
this.cfg.terminate(unpack!(block_exit), source_info,
TerminatorKind::Goto { target: exit_block });
exit_block.unit()
} else {
this.ast_block(destination, block, ast_block)
}
}
ExprKind::Match { discriminant, arms } => {
this.match_expr(destination, expr_span, block, discriminant, arms)
@ -165,8 +177,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
this.cfg.terminate(block, source_info,
TerminatorKind::Goto { target: loop_block });
this.in_loop_scope(
loop_block, exit_block, destination.clone(),
this.in_breakable_scope(
Some(loop_block), exit_block, destination.clone(),
move |this| {
// conduct the test, if necessary
let body_block;

View File

@ -9,7 +9,7 @@
// except according to those terms.
use build::{BlockAnd, BlockAndExtension, Builder};
use build::scope::LoopScope;
use build::scope::BreakableScope;
use hair::*;
use rustc::mir::*;
@ -77,19 +77,21 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
block.unit()
}
ExprKind::Continue { label } => {
let LoopScope { continue_block, extent, .. } =
*this.find_loop_scope(expr_span, label);
let BreakableScope { continue_block, extent, .. } =
*this.find_breakable_scope(expr_span, label);
let continue_block = continue_block.expect(
"Attempted to continue in non-continuable breakable block");
this.exit_scope(expr_span, extent, block, continue_block);
this.cfg.start_new_block().unit()
}
ExprKind::Break { label, value } => {
let (break_block, extent, destination) = {
let LoopScope {
let BreakableScope {
break_block,
extent,
ref break_destination,
..
} = *this.find_loop_scope(expr_span, label);
} = *this.find_breakable_scope(expr_span, label);
(break_block, extent, break_destination.clone())
};
if let Some(value) = value {

View File

@ -36,9 +36,9 @@ pub struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
/// see the `scope` module for more details
scopes: Vec<scope::Scope<'tcx>>,
/// the current set of loops; see the `scope` module for more
/// the current set of breakables; see the `scope` module for more
/// details
loop_scopes: Vec<scope::LoopScope<'tcx>>,
breakable_scopes: Vec<scope::BreakableScope<'tcx>>,
/// the vector of all scopes that we have created thus far;
/// we track this for debuginfo later
@ -248,7 +248,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
scopes: vec![],
visibility_scopes: IndexVec::new(),
visibility_scope: ARGUMENT_VISIBILITY_SCOPE,
loop_scopes: vec![],
breakable_scopes: vec![],
local_decls: IndexVec::from_elem_n(LocalDecl::new_return_pointer(return_ty), 1),
var_indices: NodeMap(),
unit_temp: None,

View File

@ -177,16 +177,16 @@ struct FreeData<'tcx> {
}
#[derive(Clone, Debug)]
pub struct LoopScope<'tcx> {
pub struct BreakableScope<'tcx> {
/// Extent of the loop
pub extent: CodeExtent,
/// Where the body of the loop begins
pub continue_block: BasicBlock,
/// Block to branch into when the loop terminates (either by being `break`-en out from, or by
/// having its condition to become false)
/// Where the body of the loop begins. `None` if block
pub continue_block: Option<BasicBlock>,
/// Block to branch into when the loop or block terminates (either by being `break`-en out
/// from, or by having its condition to become false)
pub break_block: BasicBlock,
/// The destination of the loop expression itself (i.e. where to put the result of a `break`
/// expression)
/// The destination of the loop/block expression itself (i.e. where to put the result of a
/// `break` expression)
pub break_destination: Lvalue<'tcx>,
}
@ -242,28 +242,29 @@ impl<'tcx> Scope<'tcx> {
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
// Adding and removing scopes
// ==========================
/// Start a loop scope, which tracks where `continue` and `break`
/// Start a breakable scope, which tracks where `continue` and `break`
/// should branch to. See module comment for more details.
///
/// Returns the might_break attribute of the LoopScope used.
pub fn in_loop_scope<F>(&mut self,
loop_block: BasicBlock,
/// Returns the might_break attribute of the BreakableScope used.
pub fn in_breakable_scope<F, R>(&mut self,
loop_block: Option<BasicBlock>,
break_block: BasicBlock,
break_destination: Lvalue<'tcx>,
f: F)
where F: FnOnce(&mut Builder<'a, 'gcx, 'tcx>)
f: F) -> R
where F: FnOnce(&mut Builder<'a, 'gcx, 'tcx>) -> R
{
let extent = self.topmost_scope();
let loop_scope = LoopScope {
let scope = BreakableScope {
extent: extent,
continue_block: loop_block,
break_block: break_block,
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);
self.breakable_scopes.push(scope);
let res = f(self);
let breakable_scope = self.breakable_scopes.pop().unwrap();
assert!(breakable_scope.extent == extent);
res
}
/// Convenience wrapper that pushes a scope and then executes `f`
@ -381,18 +382,18 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
// Finding scopes
// ==============
/// Finds the loop scope for a given label. This is used for
/// Finds the breakable scope for a given label. This is used for
/// resolving `break` and `continue`.
pub fn find_loop_scope(&mut self,
pub fn find_breakable_scope(&mut self,
span: Span,
label: CodeExtent)
-> &mut LoopScope<'tcx> {
-> &mut BreakableScope<'tcx> {
// find the loop-scope with the correct id
self.loop_scopes.iter_mut()
self.breakable_scopes.iter_mut()
.rev()
.filter(|loop_scope| loop_scope.extent == label)
.filter(|breakable_scope| breakable_scope.extent == label)
.next()
.unwrap_or_else(|| span_bug!(span, "no enclosing loop scope found?"))
.unwrap_or_else(|| span_bug!(span, "no enclosing breakable scope found"))
}
/// Given a span and the current visibility scope, make a SourceInfo.

View File

@ -606,22 +606,25 @@ 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, ref value) => {
match label.loop_id.into() {
Ok(loop_id) => ExprKind::Break {
label: cx.tcx.region_maps.node_extent(loop_id),
hir::ExprBreak(dest, ref value) => {
match dest.target_id {
hir::ScopeTarget::Block(target_id) |
hir::ScopeTarget::Loop(hir::LoopIdResult::Ok(target_id)) => ExprKind::Break {
label: cx.tcx.region_maps.node_extent(target_id),
value: value.to_ref(),
},
Err(err) => bug!("invalid loop id for break: {}", err)
hir::ScopeTarget::Loop(hir::LoopIdResult::Err(err)) =>
bug!("invalid loop id for break: {}", err)
}
}
hir::ExprAgain(label) => {
match label.loop_id.into() {
Ok(loop_id) => ExprKind::Continue {
hir::ExprAgain(dest) => {
match dest.target_id {
hir::ScopeTarget::Block(_) => bug!("cannot continue to blocks"),
hir::ScopeTarget::Loop(hir::LoopIdResult::Ok(loop_id)) => ExprKind::Continue {
label: cx.tcx.region_maps.node_extent(loop_id),
},
Err(err) => bug!("invalid loop id for continue: {}", err)
hir::ScopeTarget::Loop(hir::LoopIdResult::Err(err)) =>
bug!("invalid loop id for continue: {}", err)
}
}
hir::ExprMatch(ref discr, ref arms, _) => {

View File

@ -87,14 +87,19 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
self.with_context(Closure, |v| v.visit_nested_body(b));
}
hir::ExprBreak(label, ref opt_expr) => {
let loop_id = match label.loop_id.into() {
Ok(loop_id) => loop_id,
Err(hir::LoopIdError::OutsideLoopScope) => ast::DUMMY_NODE_ID,
Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
self.emit_unlabled_cf_in_while_condition(e.span, "break");
ast::DUMMY_NODE_ID
},
Err(hir::LoopIdError::UnresolvedLabel) => ast::DUMMY_NODE_ID,
let loop_id = match label.target_id {
hir::ScopeTarget::Block(_) => return,
hir::ScopeTarget::Loop(loop_res) => {
match loop_res.into() {
Ok(loop_id) => loop_id,
Err(hir::LoopIdError::OutsideLoopScope) => ast::DUMMY_NODE_ID,
Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
self.emit_unlabled_cf_in_while_condition(e.span, "break");
ast::DUMMY_NODE_ID
},
Err(hir::LoopIdError::UnresolvedLabel) => ast::DUMMY_NODE_ID,
}
}
};
if opt_expr.is_some() {
@ -124,7 +129,9 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
self.require_loop("break", e.span);
}
hir::ExprAgain(label) => {
if let Err(hir::LoopIdError::UnlabeledCfInWhileCondition) = label.loop_id.into() {
if let hir::ScopeTarget::Loop(
hir::LoopIdResult::Err(
hir::LoopIdError::UnlabeledCfInWhileCondition)) = label.target_id {
self.emit_unlabled_cf_in_while_condition(e.span, "continue");
}
self.require_loop("continue", e.span)

View File

@ -402,7 +402,7 @@ impl Diverges {
}
#[derive(Clone)]
pub struct LoopCtxt<'gcx, 'tcx> {
pub struct BreakableCtxt<'gcx, 'tcx> {
unified: Ty<'tcx>,
coerce_to: Ty<'tcx>,
break_exprs: Vec<&'gcx hir::Expr>,
@ -410,15 +410,17 @@ pub struct LoopCtxt<'gcx, 'tcx> {
}
#[derive(Clone)]
pub struct EnclosingLoops<'gcx, 'tcx> {
stack: Vec<LoopCtxt<'gcx, 'tcx>>,
pub struct EnclosingBreakables<'gcx, 'tcx> {
stack: Vec<BreakableCtxt<'gcx, 'tcx>>,
by_id: NodeMap<usize>,
}
impl<'gcx, 'tcx> EnclosingLoops<'gcx, 'tcx> {
fn find_loop(&mut self, id: hir::LoopIdResult) -> Option<&mut LoopCtxt<'gcx, 'tcx>> {
let id_res: Result<_,_> = id.into();
if let Some(ix) = id_res.ok().and_then(|id| self.by_id.get(&id).cloned()) {
impl<'gcx, 'tcx> EnclosingBreakables<'gcx, 'tcx> {
fn find_breakable(&mut self, target: hir::ScopeTarget)
-> Option<&mut BreakableCtxt<'gcx, 'tcx>>
{
let opt_index = target.opt_id().and_then(|id| self.by_id.get(&id).cloned());
if let Some(ix) = opt_index {
Some(&mut self.stack[ix])
} else {
None
@ -448,7 +450,7 @@ 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>>,
enclosing_breakables: RefCell<EnclosingBreakables<'gcx, 'tcx>>,
inh: &'a Inherited<'a, 'gcx, 'tcx>,
}
@ -1429,7 +1431,7 @@ 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 {
enclosing_breakables: RefCell::new(EnclosingBreakables {
stack: Vec::new(),
by_id: NodeMap(),
}),
@ -3480,10 +3482,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
}
tcx.mk_nil()
}
hir::ExprBreak(label, ref expr_opt) => {
hir::ExprBreak(destination, ref expr_opt) => {
let coerce_to = {
let mut enclosing_loops = self.enclosing_loops.borrow_mut();
enclosing_loops.find_loop(label.loop_id).map(|ctxt| ctxt.coerce_to)
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
enclosing_breakables
.find_breakable(destination.target_id).map(|ctxt| ctxt.coerce_to)
};
if let Some(coerce_to) = coerce_to {
let e_ty;
@ -3499,8 +3502,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
cause = self.misc(expr.span);
}
let mut enclosing_loops = self.enclosing_loops.borrow_mut();
let ctxt = enclosing_loops.find_loop(label.loop_id).unwrap();
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
let ctxt = enclosing_breakables.find_breakable(destination.target_id).unwrap();
let result = if let Some(ref e) = *expr_opt {
// Special-case the first element, as it has no "previous expressions".
@ -3530,8 +3533,9 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
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.
// Otherwise, we failed to find the enclosing breakable; this can only happen if the
// `break` target was not found, which is caught in HIR lowering and reported by the
// loop-checking pass.
tcx.types.never
}
hir::ExprAgain(_) => { tcx.types.never }
@ -3588,13 +3592,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
hir::ExprWhile(ref cond, ref body, _) => {
let unified = self.tcx.mk_nil();
let coerce_to = unified;
let ctxt = LoopCtxt {
let ctxt = BreakableCtxt {
unified: unified,
coerce_to: coerce_to,
break_exprs: vec![],
may_break: true,
};
self.with_loop_ctxt(expr.id, ctxt, || {
self.with_breakable_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);
@ -3612,14 +3616,14 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
hir::ExprLoop(ref body, _, _) => {
let unified = self.next_ty_var(TypeVariableOrigin::TypeInference(body.span));
let coerce_to = expected.only_has_type(self).unwrap_or(unified);
let ctxt = LoopCtxt {
let ctxt = BreakableCtxt {
unified: unified,
coerce_to: coerce_to,
break_exprs: vec![],
may_break: false,
};
let ctxt = self.with_loop_ctxt(expr.id, ctxt, || {
let (ctxt, ()) = self.with_breakable_ctxt(expr.id, ctxt, || {
self.check_block_no_value(&body);
});
if ctxt.may_break {
@ -3638,8 +3642,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
hir::ExprClosure(capture, ref decl, body_id, _) => {
self.check_expr_closure(expr, capture, &decl, body_id, expected)
}
hir::ExprBlock(ref b) => {
self.check_block_with_expected(&b, expected)
hir::ExprBlock(ref body) => {
self.check_block_with_expected(&body, expected)
}
hir::ExprCall(ref callee, ref args) => {
self.check_call(expr, &callee, args, expected)
@ -4031,65 +4035,85 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
replace(&mut *fcx_ps, unsafety_state)
};
for s in &blk.stmts {
self.check_stmt(s);
}
let mut ty = if let Some(break_to_expr_id) = blk.break_to_expr_id {
let unified = self.next_ty_var(TypeVariableOrigin::TypeInference(blk.span));
let coerce_to = expected.only_has_type(self).unwrap_or(unified);
let ctxt = BreakableCtxt {
unified: unified,
coerce_to: coerce_to,
break_exprs: vec![],
may_break: false,
};
let mut ty = match blk.expr {
Some(ref e) => self.check_expr_with_expectation(e, expected),
None => self.tcx.mk_nil()
};
if self.diverges.get().always() {
ty = self.next_diverging_ty_var(TypeVariableOrigin::DivergingBlockExpr(blk.span));
} else if let ExpectHasType(ety) = expected {
if let Some(ref e) = blk.expr {
// Coerce the tail expression to the right type.
self.demand_coerce(e, ty, ety);
} else {
// We're not diverging and there's an expected type, which,
// in case it's not `()`, could result in an error higher-up.
// We have a chance to error here early and be more helpful.
let cause = self.misc(blk.span);
let trace = TypeTrace::types(&cause, false, ty, ety);
match self.sub_types(false, &cause, ty, ety) {
Ok(InferOk { obligations, .. }) => {
// FIXME(#32730) propagate obligations
assert!(obligations.is_empty());
},
Err(err) => {
let mut err = self.report_and_explain_type_error(trace, &err);
// Be helpful when the user wrote `{... expr;}` and
// taking the `;` off is enough to fix the error.
let mut extra_semi = None;
if let Some(stmt) = blk.stmts.last() {
if let hir::StmtSemi(ref e, _) = stmt.node {
if self.can_sub_types(self.node_ty(e.id), ety).is_ok() {
extra_semi = Some(stmt);
}
}
}
if let Some(last_stmt) = extra_semi {
let original_span = original_sp(self.tcx.sess.codemap(),
last_stmt.span, blk.span);
let span_semi = Span {
lo: original_span.hi - BytePos(1),
hi: original_span.hi,
expn_id: original_span.expn_id
};
err.span_help(span_semi, "consider removing this semicolon:");
}
err.emit();
}
let (mut ctxt, (e_ty, cause)) = self.with_breakable_ctxt(break_to_expr_id, ctxt, || {
for s in &blk.stmts {
self.check_stmt(s);
}
let coerce_to = {
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
enclosing_breakables.find_breakable(
hir::ScopeTarget::Block(break_to_expr_id)
).unwrap().coerce_to
};
let e_ty;
let cause;
match blk.expr {
Some(ref e) => {
e_ty = self.check_expr_with_hint(e, coerce_to);
cause = self.misc(e.span);
},
None => {
e_ty = self.tcx.mk_nil();
cause = self.misc(blk.span);
}
};
(e_ty, cause)
});
if let Some(ref e) = blk.expr {
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)
};
match result {
Ok(ty) => ctxt.unified = ty,
Err(err) =>
self.report_mismatched_types(&cause, ctxt.unified, e_ty, err).emit(),
}
} else {
self.check_block_no_expr(blk, self.tcx.mk_nil(), e_ty);
};
ctxt.unified
} else {
for s in &blk.stmts {
self.check_stmt(s);
}
// We already applied the type (and potentially errored),
// use the expected type to avoid further errors out.
ty = ety;
}
let mut ty = match blk.expr {
Some(ref e) => self.check_expr_with_expectation(e, expected),
None => self.tcx.mk_nil()
};
if self.diverges.get().always() {
ty = self.next_diverging_ty_var(TypeVariableOrigin::DivergingBlockExpr(blk.span));
} else if let ExpectHasType(ety) = expected {
if let Some(ref e) = blk.expr {
// Coerce the tail expression to the right type.
self.demand_coerce(e, ty, ety);
} else {
self.check_block_no_expr(blk, ty, ety);
}
// We already applied the type (and potentially errored),
// use the expected type to avoid further errors out.
ty = ety;
}
ty
};
if self.has_errors.get() || ty.references_error() {
ty = self.tcx.types.err
@ -4101,6 +4125,46 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
ty
}
pub fn check_block_no_expr(&self, blk: &'gcx hir::Block, ty: Ty<'tcx>, ety: Ty<'tcx>) {
// We're not diverging and there's an expected type, which,
// in case it's not `()`, could result in an error higher-up.
// We have a chance to error here early and be more helpful.
let cause = self.misc(blk.span);
let trace = TypeTrace::types(&cause, false, ty, ety);
match self.sub_types(false, &cause, ty, ety) {
Ok(InferOk { obligations, .. }) => {
// FIXME(#32730) propagate obligations
assert!(obligations.is_empty());
},
Err(err) => {
let mut err = self.report_and_explain_type_error(trace, &err);
// Be helpful when the user wrote `{... expr;}` and
// taking the `;` off is enough to fix the error.
let mut extra_semi = None;
if let Some(stmt) = blk.stmts.last() {
if let hir::StmtSemi(ref e, _) = stmt.node {
if self.can_sub_types(self.node_ty(e.id), ety).is_ok() {
extra_semi = Some(stmt);
}
}
}
if let Some(last_stmt) = extra_semi {
let original_span = original_sp(self.tcx.sess.codemap(),
last_stmt.span, blk.span);
let span_semi = Span {
lo: original_span.hi - BytePos(1),
hi: original_span.hi,
expn_id: original_span.expn_id
};
err.span_help(span_semi, "consider removing this semicolon:");
}
err.emit();
}
}
}
// Instantiates the given path, which must refer to an item with the given
// number of type parameters and type.
pub fn instantiate_value_path(&self,
@ -4498,22 +4562,24 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
})
}
fn with_loop_ctxt<F: FnOnce()>(&self, id: ast::NodeId, ctxt: LoopCtxt<'gcx, 'tcx>, f: F)
-> LoopCtxt<'gcx, 'tcx> {
fn with_breakable_ctxt<F: FnOnce() -> R, R>(&self, id: ast::NodeId,
ctxt: BreakableCtxt<'gcx, 'tcx>, f: F)
-> (BreakableCtxt<'gcx, 'tcx>, R) {
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);
}
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"))
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
index = enclosing_breakables.stack.len();
enclosing_breakables.by_id.insert(id, index);
enclosing_breakables.stack.push(ctxt);
}
let result = f();
let ctxt = {
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
debug_assert!(enclosing_breakables.stack.len() == index + 1);
enclosing_breakables.by_id.remove(&id).expect("missing breakable context");
enclosing_breakables.stack.pop().expect("missing breakable context")
};
(ctxt, result)
}
}

View File

@ -0,0 +1,43 @@
// Copyright 2017 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(catch_expr)]
// This test checks that borrows made and returned inside catch blocks are properly constrained
pub fn main() {
{
// Test that borrows returned from a catch block must be valid for the lifetime of the
// result variable
let _result: Result<(), &str> = do catch {
let my_string = String::from("");
let my_str: & str = & my_string;
Err(my_str) ?;
Err("") ?;
Ok(())
}; //~ ERROR `my_string` does not live long enough
}
{
// Test that borrows returned from catch blocks freeze their referent
let mut i = 5;
let k = &mut i;
let mut j: Result<(), &mut i32> = do catch {
Err(k) ?;
i = 10; //~ ERROR cannot assign to `i` because it is borrowed
Ok(())
};
::std::mem::drop(k); //~ ERROR use of moved value: `k`
i = 40; //~ ERROR cannot assign to `i` because it is borrowed
let i_ptr = if let Err(i_ptr) = j { i_ptr } else { panic ! ("") };
*i_ptr = 50;
}
}

View File

@ -0,0 +1,21 @@
// Copyright 2017 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(catch_expr)]
pub fn main() {
let res: Result<i32, i32> = do catch {
Err("")?; //~ ERROR the trait bound `i32: std::convert::From<&str>` is not satisfied
Ok(5)
};
let res: Result<i32, i32> = do catch {
Ok("") //~ mismatched types
};
}

View File

@ -0,0 +1,51 @@
// Copyright 2017 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(catch_expr)]
// This test checks that borrows made and returned inside catch blocks are properly constrained
pub fn main() {
{
// Test that a borrow which *might* be returned still freezes its referent
let mut i = 222;
let x: Result<&i32, ()> = do catch {
Err(())?;
Ok(&i)
};
x.ok().cloned();
i = 0; //~ ERROR cannot assign to `i` because it is borrowed
let _ = i;
}
{
let x = String::new();
let _y: Result<(), ()> = do catch {
Err(())?;
::std::mem::drop(x);
Ok(())
};
println!("{}", x); //~ ERROR use of moved value: `x`
}
{
// Test that a borrow which *might* be assigned to an outer variable still freezes
// its referent
let mut i = 222;
let j;
let x: Result<(), ()> = do catch {
Err(())?;
j = &i;
Ok(())
};
i = 0; //~ ERROR cannot assign to `i` because it is borrowed
let _ = i;
}
}

View File

@ -0,0 +1,26 @@
// Copyright 2017 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(catch_expr)]
fn use_val<T: Sized>(_x: T) {}
pub fn main() {
let cfg_res;
let _: Result<(), ()> = do catch {
Err(())?;
cfg_res = 5;
Ok::<(), ()>(())?;
use_val(cfg_res);
Ok(())
};
assert_eq!(cfg_res, 5); //~ ERROR use of possibly uninitialized variable
}

View File

@ -29,4 +29,46 @@ pub fn main() {
match catch {
_ => {}
};
let catch_err = do catch {
Err(22)?;
Ok(1)
};
assert_eq!(catch_err, Err(22));
let catch_okay: Result<i32, i32> = do catch {
if false { Err(25)?; }
Ok::<(), i32>(())?;
Ok(28)
};
assert_eq!(catch_okay, Ok(28));
let catch_from_loop: Result<i32, i32> = do catch {
for i in 0..10 {
if i < 5 { Ok::<i32, i32>(i)?; } else { Err(i)?; }
}
Ok(22)
};
assert_eq!(catch_from_loop, Err(5));
let cfg_init;
let _res: Result<(), ()> = do catch {
cfg_init = 5;
Ok(())
};
assert_eq!(cfg_init, 5);
let cfg_init_2;
let _res: Result<(), ()> = do catch {
cfg_init_2 = 6;
Err(())?;
Ok(())
};
assert_eq!(cfg_init_2, 6);
let my_string = "test".to_string();
let res: Result<&str, ()> = do catch {
Ok(&my_string)
};
assert_eq!(res, Ok("test"));
}