diff --git a/src/librustc/cfg/construct.rs b/src/librustc/cfg/construct.rs index 7a70eda955b69dec324b78be14b67ddf8181d592..777408457d1f6819ab6f05dccf86187fb0624f73 100644 --- a/src/librustc/cfg/construct.rs +++ b/src/librustc/cfg/construct.rs @@ -22,13 +22,20 @@ struct CFGBuilder<'a, 'tcx: 'a> { graph: CFGGraph, fn_exit: CFGIndex, loop_scopes: Vec, + breakable_block_scopes: Vec, +} + +#[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, blk_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); + let expr_exit = self.opt_expr(&blk.expr, stmts_exit); - self.add_ast_node(blk.id, &[expr_exit]) + self.add_ast_node(blk.id, &[expr_exit]) + } } fn stmt(&mut self, stmt: &hir::Stmt, pred: CFGIndex) -> CFGIndex { @@ -295,18 +323,18 @@ fn expr(&mut self, expr: &hir::Expr, pred: CFGIndex) -> CFGIndex { 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 @@ fn add_contained_edge(&mut self, 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 @@ fn add_returning_edge(&mut self, 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 { - - match destination.loop_id.into() { - Ok(loop_id) => { + destination: hir::Destination, + scope_cf_kind: ScopeCfKind) -> (ast::NodeId, CFGIndex) { + + 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, +} diff --git a/src/librustc/hir/intravisit.rs b/src/librustc/hir/intravisit.rs index fd6796ccc0bf2b0cd6b7957281a960c99322ff4c..f59b8b757f5cc4de61cb1aae6731a8ee023655e9 100644 --- a/src/librustc/hir/intravisit.rs +++ b/src/librustc/hir/intravisit.rs @@ -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); }); } diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs index d92eaee6f0c8ec9e72902fa48a3b5c54aac5b969..4a927261bdafbd82660167e378a4048ffae1eb3f 100644 --- a/src/librustc/hir/lowering.rs +++ b/src/librustc/hir/lowering.rs @@ -356,22 +356,26 @@ fn lower_opt_sp_ident(&mut self, o_id: Option>) -> Option)>) + fn lower_loop_destination(&mut self, destination: Option<(NodeId, Spanned)>) -> 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 @@ fn lower_bounds(&mut self, bounds: &[TyParamBound]) -> hir::TyParamBounds { bounds.iter().map(|bound| self.lower_ty_param_bound(bound)).collect() } - fn lower_block(&mut self, b: &Block) -> P { + fn lower_block(&mut self, b: &Block, break_to: Option) -> P { let mut expr = None; let mut stmts = b.stmts.iter().flat_map(|s| self.lower_stmt(s)).collect::>(); @@ -1008,6 +1012,7 @@ fn lower_block(&mut self, b: &Block) -> P { expr: expr, rules: self.lower_block_check_mode(&b.rules), span: b.span, + break_to_expr_id: break_to, }) } @@ -1085,7 +1090,7 @@ fn lower_item_kind(&mut self, } 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 @@ fn lower_trait_item(&mut self, i: &TraitItem) -> hir::TraitItem { 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 @@ fn lower_impl_item(&mut self, i: &ImplItem) -> hir::ImplItem { 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 @@ fn lower_expr(&mut self, e: &Expr) -> hir::Expr { id: id, rules: hir::DefaultBlock, span: span, + break_to_expr_id: None, }); P(self.expr_block(blk, ThinVec::new())) } @@ -1669,24 +1675,24 @@ fn lower_expr(&mut self, e: &Expr) -> hir::Expr { } }); - 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 @@ fn lower_expr(&mut self, e: &Expr) -> hir::Expr { }) }) } - 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 @@ fn make_struct(this: &mut LoweringContext, 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 @@ fn make_struct(this: &mut LoweringContext, 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 @@ fn make_struct(this: &mut LoweringContext, // ` => ` 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 @@ fn make_struct(this: &mut LoweringContext, // 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 @@ fn make_struct(this: &mut LoweringContext, // `::std::option::Option::Some() => ` 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 @@ fn make_struct(this: &mut LoweringContext, // match Carrier::translate() { // 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() @@ -2157,9 +2163,24 @@ fn make_struct(this: &mut LoweringContext, 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 @@ fn field(&mut self, name: Name, expr: P, span: Span) -> hir::Field { } fn expr_break(&mut self, span: Span, attrs: ThinVec) -> P { - 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 @@ fn block_all(&mut self, span: Span, stmts: hir::HirVec, expr: Option< id: self.next_id(), rules: hir::DefaultBlock, span: span, + break_to_expr_id: None, } } @@ -2519,6 +2541,7 @@ fn signal_block_expr(&mut self, id: id, stmts: stmts, expr: Some(expr), + break_to_expr_id: None, }); self.expr_block(block, attrs) } diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs index 22bc28eb3fec0a00e90a08bdb4fc7af268c5fa1a..d5f496b90035dc526c6fb22de6f3737cf3bad5d8 100644 --- a/src/librustc/hir/mod.rs +++ b/src/librustc/hir/mod.rs @@ -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, } #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash)] @@ -1080,6 +1083,22 @@ fn from(res: Result) -> Self { } } +#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)] +pub enum ScopeTarget { + Block(NodeId), + Loop(LoopIdResult), +} + +impl ScopeTarget { + pub fn opt_id(self) -> Option { + 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)] diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs index a8c1559ae2373b309ca0072d0c0ec90790d1f917..769dc8aeb54ddce49744a685505a047dd6da0e41 100644 --- a/src/librustc/middle/liveness.rs +++ b/src/librustc/middle/liveness.rs @@ -516,14 +516,15 @@ struct Liveness<'a, 'tcx: 'a> { s: Specials, successors: Vec, users: Vec, - // The list of node IDs for the nested loop scopes - // we're in. - loop_scope: Vec, + // mappings from loop node ID to LiveNode // ("break" label should map to loop node ID, // it probably doesn't now) break_ln: NodeMap, - cont_ln: NodeMap + cont_ln: NodeMap, + + // mappings from node ID to LiveNode for "breakable" blocks-- currently only `catch {...}` + breakable_block_ln: NodeMap, } impl<'a, 'tcx> Liveness<'a, 'tcx> { @@ -550,9 +551,9 @@ fn new(ir: &'a mut IrMaps<'a, 'tcx>, body: hir::BodyId) -> 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 @@ fn compute(&mut self, body: &hir::Expr) -> LiveNode { 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 @@ fn compute(&mut self, body: &hir::Expr) -> LiveNode { 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 @@ fn propagate_through_expr(&mut self, expr: &Expr, succ: LiveNode) } 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 @@ fn propagate_through_expr(&mut self, expr: &Expr, succ: LiveNode) 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 @@ fn propagate_through_loop(&mut self, 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 @@ fn propagate_through_loop(&mut self, } }; 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(&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 - } } // _______________________________________________________________________ diff --git a/src/librustc_mir/build/expr/into.rs b/src/librustc_mir/build/expr/into.rs index dab85106873299e2747b79e954f36c2c41941620..e1b0c6a6f042e0ee980b39d05688b45e4277958b 100644 --- a/src/librustc_mir/build/expr/into.rs +++ b/src/librustc_mir/build/expr/into.rs @@ -40,7 +40,19 @@ pub fn into_expr(&mut self, 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 @@ pub fn into_expr(&mut self, 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; diff --git a/src/librustc_mir/build/expr/stmt.rs b/src/librustc_mir/build/expr/stmt.rs index be39dcbf6d08d84a3940a32188dffb6838710cfb..7336da654c1840f550e82d5b66ffc125bd7454ac 100644 --- a/src/librustc_mir/build/expr/stmt.rs +++ b/src/librustc_mir/build/expr/stmt.rs @@ -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 @@ pub fn stmt_expr(&mut self, mut block: BasicBlock, expr: Expr<'tcx>) -> BlockAnd 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 { diff --git a/src/librustc_mir/build/mod.rs b/src/librustc_mir/build/mod.rs index 41374a0012327a4e78cb7d49e1b9682b682c2890..db03a1c68f7156023cde3ad4c0b631b71036fb41 100644 --- a/src/librustc_mir/build/mod.rs +++ b/src/librustc_mir/build/mod.rs @@ -36,9 +36,9 @@ pub struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { /// see the `scope` module for more details scopes: Vec>, - /// 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>, + breakable_scopes: Vec>, /// the vector of all scopes that we have created thus far; /// we track this for debuginfo later @@ -248,7 +248,7 @@ fn new(hir: Cx<'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, diff --git a/src/librustc_mir/build/scope.rs b/src/librustc_mir/build/scope.rs index 3dab1717f6b2e4a466534457c6eb971435eef903..1de5b9218564fdb73a96682b9fb3dc291c540392 100644 --- a/src/librustc_mir/build/scope.rs +++ b/src/librustc_mir/build/scope.rs @@ -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, + /// 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 @@ fn source_info(&self, span: Span) -> SourceInfo { 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(&mut self, - loop_block: BasicBlock, + /// Returns the might_break attribute of the BreakableScope used. + pub fn in_breakable_scope(&mut self, + loop_block: Option, 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 @@ pub fn new_visibility_scope(&mut self, span: Span) -> VisibilityScope { // 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. diff --git a/src/librustc_mir/hair/cx/expr.rs b/src/librustc_mir/hair/cx/expr.rs index c67bb8ec6c585346fc7d646addcecfaeda46df6f..da58a1ed1f49b2a90c5f72b1fd4f7da63cd0b9b5 100644 --- a/src/librustc_mir/hair/cx/expr.rs +++ b/src/librustc_mir/hair/cx/expr.rs @@ -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, _) => { diff --git a/src/librustc_passes/loops.rs b/src/librustc_passes/loops.rs index b2d51be5bf72095bc3b4199dec8af37c285ae3e9..421181c68c4cda90eef45bf85fe12ab59a27e424 100644 --- a/src/librustc_passes/loops.rs +++ b/src/librustc_passes/loops.rs @@ -87,14 +87,19 @@ fn visit_expr(&mut self, e: &'hir hir::Expr) { 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 @@ fn visit_expr(&mut self, e: &'hir hir::Expr) { 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) diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index 5a582a523ea1c910a6c52102b4104b2ec352ccfc..894c5f12297a0fc62a8a238623224ec10c413af0 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -402,7 +402,7 @@ fn always(self) -> bool { } #[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>, +pub struct EnclosingBreakables<'gcx, 'tcx> { + stack: Vec>, by_id: NodeMap, } -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, - enclosing_loops: RefCell>, + enclosing_breakables: RefCell>, inh: &'a Inherited<'a, 'gcx, 'tcx>, } @@ -1429,7 +1431,7 @@ pub fn new(inh: &'a Inherited<'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(), }), @@ -3467,10 +3469,11 @@ fn check_expr_kind(&self, } 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; @@ -3486,8 +3489,8 @@ fn check_expr_kind(&self, 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". @@ -3517,8 +3520,9 @@ fn check_expr_kind(&self, 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 } @@ -3575,13 +3579,13 @@ fn check_expr_kind(&self, 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); @@ -3599,14 +3603,14 @@ fn check_expr_kind(&self, 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 { @@ -3625,8 +3629,8 @@ fn check_expr_kind(&self, 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) @@ -4018,65 +4022,85 @@ fn check_block_with_expected(&self, 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() - }; + 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 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); + 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 { - // 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:"); - } + self.check_block_no_expr(blk, self.tcx.mk_nil(), e_ty); + }; - err.emit(); - } - } + 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 @@ -4088,6 +4112,46 @@ fn check_block_with_expected(&self, 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, @@ -4485,22 +4549,24 @@ pub fn structurally_resolved_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> { }) } - fn with_loop_ctxt(&self, id: ast::NodeId, ctxt: LoopCtxt<'gcx, 'tcx>, f: F) - -> LoopCtxt<'gcx, 'tcx> { + fn with_breakable_ctxt 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) } } diff --git a/src/test/compile-fail/catch-bad-lifetime.rs b/src/test/compile-fail/catch-bad-lifetime.rs new file mode 100644 index 0000000000000000000000000000000000000000..d8de419df6d05c873ea0708c31942938c364387b --- /dev/null +++ b/src/test/compile-fail/catch-bad-lifetime.rs @@ -0,0 +1,35 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(catch_expr)] + +pub fn main() { + let _: 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 + + 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; +} + diff --git a/src/test/compile-fail/catch-bad-type.rs b/src/test/compile-fail/catch-bad-type.rs new file mode 100644 index 0000000000000000000000000000000000000000..cff9f508275b60daa9f0061698eaed282bf72fdb --- /dev/null +++ b/src/test/compile-fail/catch-bad-type.rs @@ -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 or the MIT license +// , 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 = do catch { + Err("")?; //~ ERROR the trait bound `i32: std::convert::From<&str>` is not satisfied + Ok(5) + }; + let res: Result = do catch { + Ok("") //~ mismatched types + }; +} diff --git a/src/test/compile-fail/catch-opt-init.rs b/src/test/compile-fail/catch-opt-init.rs new file mode 100644 index 0000000000000000000000000000000000000000..c5d116c82ddf806e7ab2c7109fe8a1b47146fcb0 --- /dev/null +++ b/src/test/compile-fail/catch-opt-init.rs @@ -0,0 +1,22 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(catch_expr)] + +pub fn main() { + let cfg_res; + let _: Result<(), ()> = do catch { + Err(())?; + cfg_res = 5; + Ok(()) + }; + assert_eq!(cfg_res, 5); //~ ERROR use of possibly uninitialized variable +} + diff --git a/src/test/run-pass/catch-expr.rs b/src/test/run-pass/catch-expr.rs index a9b28a534a3483edd4b69a98f00bcfd0f309f6dc..f2852bac27ae8de01648e82886a4cbcb5d0ee4fd 100644 --- a/src/test/run-pass/catch-expr.rs +++ b/src/test/run-pass/catch-expr.rs @@ -29,4 +29,38 @@ pub fn main() { match catch { _ => {} }; + + let catch_err = do catch { + Err(22)?; + Ok(1) + }; + assert_eq!(catch_err, Err(22)); + + let catch_okay: Result = do catch { + if false { Err(25)?; } + Ok::<(), i32>(())?; + Ok(28) + }; + assert_eq!(catch_okay, Ok(28)); + + let catch_from_loop: Result = do catch { + for i in 0..10 { + if i < 5 { Ok::(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 my_string = "test".to_string(); + let res: Result<&str, ()> = do catch { + Ok(&my_string) + }; + assert_eq!(res, Ok("test")); }