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:
commit
fcc2d25794
@ -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,
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
// _______________________________________________________________________
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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, _) => {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
43
src/test/compile-fail/catch-bad-lifetime.rs
Normal file
43
src/test/compile-fail/catch-bad-lifetime.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
21
src/test/compile-fail/catch-bad-type.rs
Normal file
21
src/test/compile-fail/catch-bad-type.rs
Normal 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
|
||||
};
|
||||
}
|
51
src/test/compile-fail/catch-maybe-bad-lifetime.rs
Normal file
51
src/test/compile-fail/catch-maybe-bad-lifetime.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
26
src/test/compile-fail/catch-opt-init.rs
Normal file
26
src/test/compile-fail/catch-opt-init.rs
Normal 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
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user