mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-23 15:23:46 +00:00
Rollup merge of #39864 - cramertj:normalize-breaks, r=nikomatsakis
Normalize labeled and unlabeled breaks Part of #39849.
This commit is contained in:
commit
ad9079bae4
@ -220,15 +220,24 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
|
||||
// Note that `break` and `continue` statements
|
||||
// may cause additional edges.
|
||||
|
||||
// Is the condition considered part of the loop?
|
||||
let loopback = self.add_dummy_node(&[pred]); // 1
|
||||
let cond_exit = self.expr(&cond, loopback); // 2
|
||||
let expr_exit = self.add_ast_node(expr.id, &[cond_exit]); // 3
|
||||
|
||||
// Create expr_exit without pred (cond_exit)
|
||||
let expr_exit = self.add_ast_node(expr.id, &[]); // 3
|
||||
|
||||
// The LoopScope needs to be on the loop_scopes stack while evaluating the
|
||||
// condition and the body of the loop (both can break out of the loop)
|
||||
self.loop_scopes.push(LoopScope {
|
||||
loop_id: expr.id,
|
||||
continue_index: loopback,
|
||||
break_index: expr_exit
|
||||
});
|
||||
|
||||
let cond_exit = self.expr(&cond, loopback); // 2
|
||||
|
||||
// Add pred (cond_exit) to expr_exit
|
||||
self.add_contained_edge(cond_exit, expr_exit);
|
||||
|
||||
let body_exit = self.block(&body, cond_exit); // 4
|
||||
self.add_contained_edge(body_exit, loopback); // 5
|
||||
self.loop_scopes.pop();
|
||||
@ -294,17 +303,17 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
|
||||
self.add_unreachable_node()
|
||||
}
|
||||
|
||||
hir::ExprBreak(label, ref opt_expr) => {
|
||||
hir::ExprBreak(destination, ref opt_expr) => {
|
||||
let v = self.opt_expr(opt_expr, pred);
|
||||
let loop_scope = self.find_scope(expr, label);
|
||||
let loop_scope = self.find_scope(expr, destination);
|
||||
let b = self.add_ast_node(expr.id, &[v]);
|
||||
self.add_exiting_edge(expr, b,
|
||||
loop_scope, loop_scope.break_index);
|
||||
self.add_unreachable_node()
|
||||
}
|
||||
|
||||
hir::ExprAgain(label) => {
|
||||
let loop_scope = self.find_scope(expr, label);
|
||||
hir::ExprAgain(destination) => {
|
||||
let loop_scope = self.find_scope(expr, destination);
|
||||
let a = self.add_ast_node(expr.id, &[pred]);
|
||||
self.add_exiting_edge(expr, a,
|
||||
loop_scope, loop_scope.continue_index);
|
||||
@ -579,17 +588,18 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
|
||||
|
||||
fn find_scope(&self,
|
||||
expr: &hir::Expr,
|
||||
label: Option<hir::Label>) -> LoopScope {
|
||||
match label {
|
||||
None => *self.loop_scopes.last().unwrap(),
|
||||
Some(label) => {
|
||||
destination: hir::Destination) -> LoopScope {
|
||||
|
||||
match destination.loop_id.into() {
|
||||
Ok(loop_id) => {
|
||||
for l in &self.loop_scopes {
|
||||
if l.loop_id == label.loop_id {
|
||||
if l.loop_id == loop_id {
|
||||
return *l;
|
||||
}
|
||||
}
|
||||
span_bug!(expr.span, "no loop scope for id {}", label.loop_id);
|
||||
span_bug!(expr.span, "no loop scope for id {}", loop_id);
|
||||
}
|
||||
Err(err) => span_bug!(expr.span, "loop scope error: {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1006,18 +1006,22 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
|
||||
ExprPath(ref qpath) => {
|
||||
visitor.visit_qpath(qpath, expression.id, expression.span);
|
||||
}
|
||||
ExprBreak(None, ref opt_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));
|
||||
}
|
||||
visitor.visit_name(ident.span, ident.node.name);
|
||||
});
|
||||
walk_list!(visitor, visit_expr, opt_expr);
|
||||
}
|
||||
ExprBreak(Some(label), ref opt_expr) => {
|
||||
visitor.visit_def_mention(Def::Label(label.loop_id));
|
||||
visitor.visit_name(label.span, label.name);
|
||||
walk_list!(visitor, visit_expr, opt_expr);
|
||||
}
|
||||
ExprAgain(None) => {}
|
||||
ExprAgain(Some(label)) => {
|
||||
visitor.visit_def_mention(Def::Label(label.loop_id));
|
||||
visitor.visit_name(label.span, label.name);
|
||||
ExprAgain(label) => {
|
||||
label.ident.map(|ident| {
|
||||
if let Ok(loop_id) = label.loop_id.into() {
|
||||
visitor.visit_def_mention(Def::Label(loop_id));
|
||||
}
|
||||
visitor.visit_name(ident.span, ident.node.name);
|
||||
});
|
||||
}
|
||||
ExprRet(ref optional_expression) => {
|
||||
walk_list!(visitor, visit_expr, optional_expression);
|
||||
|
@ -50,6 +50,7 @@ use util::nodemap::{DefIdMap, NodeMap, FxHashMap};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter;
|
||||
use std::mem;
|
||||
|
||||
use syntax::attr;
|
||||
use syntax::ast::*;
|
||||
@ -79,6 +80,9 @@ pub struct LoweringContext<'a> {
|
||||
impl_items: BTreeMap<hir::ImplItemId, hir::ImplItem>,
|
||||
bodies: FxHashMap<hir::BodyId, hir::Body>,
|
||||
|
||||
loop_scopes: Vec<NodeId>,
|
||||
is_in_loop_condition: bool,
|
||||
|
||||
type_def_lifetime_params: DefIdMap<usize>,
|
||||
}
|
||||
|
||||
@ -112,6 +116,8 @@ pub fn lower_crate(sess: &Session,
|
||||
trait_items: BTreeMap::new(),
|
||||
impl_items: BTreeMap::new(),
|
||||
bodies: FxHashMap(),
|
||||
loop_scopes: Vec::new(),
|
||||
is_in_loop_condition: false,
|
||||
type_def_lifetime_params: DefIdMap(),
|
||||
}.lower_crate(krate)
|
||||
}
|
||||
@ -244,6 +250,55 @@ impl<'a> LoweringContext<'a> {
|
||||
span
|
||||
}
|
||||
|
||||
fn with_loop_scope<T, F>(&mut self, loop_id: NodeId, f: F) -> T
|
||||
where F: FnOnce(&mut LoweringContext) -> T
|
||||
{
|
||||
// We're no longer in the base loop's condition; we're in another loop.
|
||||
let was_in_loop_condition = self.is_in_loop_condition;
|
||||
self.is_in_loop_condition = false;
|
||||
|
||||
let len = self.loop_scopes.len();
|
||||
self.loop_scopes.push(loop_id);
|
||||
|
||||
let result = f(self);
|
||||
assert_eq!(len + 1, self.loop_scopes.len(),
|
||||
"Loop scopes should be added and removed in stack order");
|
||||
|
||||
self.loop_scopes.pop().unwrap();
|
||||
|
||||
self.is_in_loop_condition = was_in_loop_condition;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn with_loop_condition_scope<T, F>(&mut self, f: F) -> T
|
||||
where F: FnOnce(&mut LoweringContext) -> T
|
||||
{
|
||||
let was_in_loop_condition = self.is_in_loop_condition;
|
||||
self.is_in_loop_condition = true;
|
||||
|
||||
let result = f(self);
|
||||
|
||||
self.is_in_loop_condition = was_in_loop_condition;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn with_new_loop_scopes<T, F>(&mut self, f: F) -> T
|
||||
where F: FnOnce(&mut LoweringContext) -> T
|
||||
{
|
||||
let was_in_loop_condition = self.is_in_loop_condition;
|
||||
self.is_in_loop_condition = false;
|
||||
|
||||
let loop_scopes = mem::replace(&mut self.loop_scopes, Vec::new());
|
||||
let result = f(self);
|
||||
mem::replace(&mut self.loop_scopes, loop_scopes);
|
||||
|
||||
self.is_in_loop_condition = was_in_loop_condition;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn with_parent_def<T, F>(&mut self, parent_id: NodeId, f: F) -> T
|
||||
where F: FnOnce(&mut LoweringContext) -> T
|
||||
{
|
||||
@ -271,17 +326,24 @@ impl<'a> LoweringContext<'a> {
|
||||
o_id.map(|sp_ident| respan(sp_ident.span, sp_ident.node.name))
|
||||
}
|
||||
|
||||
fn lower_label(&mut self, id: NodeId, label: Option<Spanned<Ident>>) -> Option<hir::Label> {
|
||||
label.map(|sp_ident| {
|
||||
hir::Label {
|
||||
span: sp_ident.span,
|
||||
name: sp_ident.node.name,
|
||||
loop_id: match self.expect_full_def(id) {
|
||||
Def::Label(loop_id) => loop_id,
|
||||
_ => DUMMY_NODE_ID
|
||||
fn lower_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) {
|
||||
hir::LoopIdResult::Ok(loop_id)
|
||||
} else {
|
||||
hir::LoopIdResult::Err(hir::LoopIdError::UnresolvedLabel)
|
||||
}
|
||||
},
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_attrs(&mut self, attrs: &Vec<Attribute>) -> hir::HirVec<Attribute> {
|
||||
@ -992,15 +1054,17 @@ impl<'a> LoweringContext<'a> {
|
||||
self.record_body(value, None))
|
||||
}
|
||||
ItemKind::Fn(ref decl, unsafety, constness, abi, ref generics, ref body) => {
|
||||
let body = self.lower_block(body);
|
||||
let body = self.expr_block(body, ThinVec::new());
|
||||
let body_id = self.record_body(body, Some(decl));
|
||||
hir::ItemFn(self.lower_fn_decl(decl),
|
||||
self.lower_unsafety(unsafety),
|
||||
self.lower_constness(constness),
|
||||
abi,
|
||||
self.lower_generics(generics),
|
||||
body_id)
|
||||
self.with_new_loop_scopes(|this| {
|
||||
let body = this.lower_block(body);
|
||||
let body = this.expr_block(body, ThinVec::new());
|
||||
let body_id = this.record_body(body, Some(decl));
|
||||
hir::ItemFn(this.lower_fn_decl(decl),
|
||||
this.lower_unsafety(unsafety),
|
||||
this.lower_constness(constness),
|
||||
abi,
|
||||
this.lower_generics(generics),
|
||||
body_id)
|
||||
})
|
||||
}
|
||||
ItemKind::Mod(ref m) => hir::ItemMod(self.lower_mod(m)),
|
||||
ItemKind::ForeignMod(ref nm) => hir::ItemForeignMod(self.lower_foreign_mod(nm)),
|
||||
@ -1562,13 +1626,17 @@ impl<'a> LoweringContext<'a> {
|
||||
hir::ExprIf(P(self.lower_expr(cond)), self.lower_block(blk), else_opt)
|
||||
}
|
||||
ExprKind::While(ref cond, ref body, opt_ident) => {
|
||||
hir::ExprWhile(P(self.lower_expr(cond)), self.lower_block(body),
|
||||
self.lower_opt_sp_ident(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_opt_sp_ident(opt_ident)))
|
||||
}
|
||||
ExprKind::Loop(ref body, opt_ident) => {
|
||||
hir::ExprLoop(self.lower_block(body),
|
||||
self.lower_opt_sp_ident(opt_ident),
|
||||
hir::LoopSource::Loop)
|
||||
self.with_loop_scope(e.id, |this|
|
||||
hir::ExprLoop(this.lower_block(body),
|
||||
this.lower_opt_sp_ident(opt_ident),
|
||||
hir::LoopSource::Loop))
|
||||
}
|
||||
ExprKind::Match(ref expr, ref arms) => {
|
||||
hir::ExprMatch(P(self.lower_expr(expr)),
|
||||
@ -1576,12 +1644,14 @@ impl<'a> LoweringContext<'a> {
|
||||
hir::MatchSource::Normal)
|
||||
}
|
||||
ExprKind::Closure(capture_clause, ref decl, ref body, fn_decl_span) => {
|
||||
self.with_parent_def(e.id, |this| {
|
||||
let expr = this.lower_expr(body);
|
||||
hir::ExprClosure(this.lower_capture_clause(capture_clause),
|
||||
this.lower_fn_decl(decl),
|
||||
this.record_body(expr, Some(decl)),
|
||||
fn_decl_span)
|
||||
self.with_new_loop_scopes(|this| {
|
||||
this.with_parent_def(e.id, |this| {
|
||||
let expr = this.lower_expr(body);
|
||||
hir::ExprClosure(this.lower_capture_clause(capture_clause),
|
||||
this.lower_fn_decl(decl),
|
||||
this.record_body(expr, Some(decl)),
|
||||
fn_decl_span)
|
||||
})
|
||||
})
|
||||
}
|
||||
ExprKind::Block(ref blk) => hir::ExprBlock(self.lower_block(blk)),
|
||||
@ -1660,10 +1730,29 @@ impl<'a> LoweringContext<'a> {
|
||||
hir::ExprPath(self.lower_qpath(e.id, qself, path, ParamMode::Optional))
|
||||
}
|
||||
ExprKind::Break(opt_ident, ref opt_expr) => {
|
||||
hir::ExprBreak(self.lower_label(e.id, opt_ident),
|
||||
opt_expr.as_ref().map(|x| P(self.lower_expr(x))))
|
||||
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(),
|
||||
}
|
||||
} else {
|
||||
self.lower_destination(opt_ident.map(|ident| (e.id, ident)))
|
||||
};
|
||||
hir::ExprBreak(
|
||||
label_result,
|
||||
opt_expr.as_ref().map(|x| P(self.lower_expr(x))))
|
||||
}
|
||||
ExprKind::Continue(opt_ident) => hir::ExprAgain(self.lower_label(e.id, opt_ident)),
|
||||
ExprKind::Continue(opt_ident) =>
|
||||
hir::ExprAgain(
|
||||
if self.is_in_loop_condition && opt_ident.is_none() {
|
||||
hir::Destination {
|
||||
ident: opt_ident,
|
||||
loop_id: Err(
|
||||
hir::LoopIdError::UnlabeledCfInWhileCondition).into(),
|
||||
}
|
||||
} else {
|
||||
self.lower_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) => {
|
||||
let hir_asm = hir::InlineAsm {
|
||||
@ -1804,9 +1893,16 @@ 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.expr_break(e.span, ThinVec::new()),
|
||||
this.with_loop_condition_scope(|this| P(this.lower_expr(sub_expr))),
|
||||
));
|
||||
|
||||
// `<pat> => <body>`
|
||||
let pat_arm = {
|
||||
let body = self.lower_block(body);
|
||||
let body_expr = P(self.expr_block(body, ThinVec::new()));
|
||||
let pat = self.lower_pat(pat);
|
||||
self.arm(hir_vec![pat], body_expr)
|
||||
@ -1815,13 +1911,11 @@ impl<'a> LoweringContext<'a> {
|
||||
// `_ => break`
|
||||
let break_arm = {
|
||||
let pat_under = self.pat_wild(e.span);
|
||||
let break_expr = self.expr_break(e.span, ThinVec::new());
|
||||
self.arm(hir_vec![pat_under], break_expr)
|
||||
};
|
||||
|
||||
// `match <sub_expr> { ... }`
|
||||
let arms = hir_vec![pat_arm, break_arm];
|
||||
let sub_expr = P(self.lower_expr(sub_expr));
|
||||
let match_expr = self.expr(e.span,
|
||||
hir::ExprMatch(sub_expr,
|
||||
arms,
|
||||
@ -1863,7 +1957,7 @@ impl<'a> LoweringContext<'a> {
|
||||
|
||||
// `::std::option::Option::Some(<pat>) => <body>`
|
||||
let pat_arm = {
|
||||
let body_block = self.lower_block(body);
|
||||
let body_block = self.with_loop_scope(e.id, |this| this.lower_block(body));
|
||||
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);
|
||||
@ -1873,7 +1967,8 @@ impl<'a> LoweringContext<'a> {
|
||||
|
||||
// `::std::option::Option::None => break`
|
||||
let break_arm = {
|
||||
let break_expr = self.expr_break(e.span, ThinVec::new());
|
||||
let break_expr = self.with_loop_scope(e.id, |this|
|
||||
this.expr_break(e.span, ThinVec::new()));
|
||||
let pat = self.pat_none(e.span);
|
||||
self.arm(hir_vec![pat], break_expr)
|
||||
};
|
||||
@ -2151,7 +2246,8 @@ impl<'a> LoweringContext<'a> {
|
||||
}
|
||||
|
||||
fn expr_break(&mut self, span: Span, attrs: ThinVec<Attribute>) -> P<hir::Expr> {
|
||||
P(self.expr(span, hir::ExprBreak(None, None), attrs))
|
||||
let expr_break = hir::ExprBreak(self.lower_destination(None), None);
|
||||
P(self.expr(span, expr_break, attrs))
|
||||
}
|
||||
|
||||
fn expr_call(&mut self, span: Span, e: P<hir::Expr>, args: hir::HirVec<hir::Expr>)
|
||||
|
@ -36,7 +36,7 @@ use util::nodemap::{NodeMap, FxHashMap, FxHashSet};
|
||||
use syntax_pos::{Span, ExpnId, DUMMY_SP};
|
||||
use syntax::codemap::{self, Spanned};
|
||||
use syntax::abi::Abi;
|
||||
use syntax::ast::{Name, NodeId, DUMMY_NODE_ID, AsmDialect};
|
||||
use syntax::ast::{Ident, Name, NodeId, DUMMY_NODE_ID, AsmDialect};
|
||||
use syntax::ast::{Attribute, Lit, StrStyle, FloatTy, IntTy, UintTy, MetaItem};
|
||||
use syntax::ptr::P;
|
||||
use syntax::symbol::{Symbol, keywords};
|
||||
@ -959,9 +959,9 @@ pub enum Expr_ {
|
||||
/// A referencing operation (`&a` or `&mut a`)
|
||||
ExprAddrOf(Mutability, P<Expr>),
|
||||
/// A `break`, with an optional label to break
|
||||
ExprBreak(Option<Label>, Option<P<Expr>>),
|
||||
ExprBreak(Destination, Option<P<Expr>>),
|
||||
/// A `continue`, with an optional label
|
||||
ExprAgain(Option<Label>),
|
||||
ExprAgain(Destination),
|
||||
/// A `return`, with an optional value to be returned
|
||||
ExprRet(Option<P<Expr>>),
|
||||
|
||||
@ -1030,12 +1030,56 @@ pub enum LoopSource {
|
||||
ForLoop,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
|
||||
pub enum LoopIdError {
|
||||
OutsideLoopScope,
|
||||
UnlabeledCfInWhileCondition,
|
||||
UnresolvedLabel,
|
||||
}
|
||||
|
||||
impl fmt::Display for LoopIdError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(match *self {
|
||||
LoopIdError::OutsideLoopScope => "not inside loop scope",
|
||||
LoopIdError::UnlabeledCfInWhileCondition =>
|
||||
"unlabeled control flow (break or continue) in while condition",
|
||||
LoopIdError::UnresolvedLabel => "label not found",
|
||||
}, f)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(cramertj) this should use `Result` once master compiles w/ a vesion of Rust where
|
||||
// `Result` implements `Encodable`/`Decodable`
|
||||
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
|
||||
pub enum LoopIdResult {
|
||||
Ok(NodeId),
|
||||
Err(LoopIdError),
|
||||
}
|
||||
impl Into<Result<NodeId, LoopIdError>> for LoopIdResult {
|
||||
fn into(self) -> Result<NodeId, LoopIdError> {
|
||||
match self {
|
||||
LoopIdResult::Ok(ok) => Ok(ok),
|
||||
LoopIdResult::Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Result<NodeId, LoopIdError>> for LoopIdResult {
|
||||
fn from(res: Result<NodeId, LoopIdError>) -> Self {
|
||||
match res {
|
||||
Ok(ok) => LoopIdResult::Ok(ok),
|
||||
Err(err) => LoopIdResult::Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
|
||||
pub struct Label {
|
||||
pub span: Span,
|
||||
pub name: Name,
|
||||
pub loop_id: NodeId
|
||||
pub struct Destination {
|
||||
// This is `Some(_)` iff there is an explicit user-specified `label
|
||||
pub ident: Option<Spanned<Ident>>,
|
||||
|
||||
// These errors are caught and then reported during the diagnostics pass in
|
||||
// librustc_passes/loops.rs
|
||||
pub loop_id: LoopIdResult,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
|
||||
|
@ -1354,11 +1354,11 @@ impl<'a> State<'a> {
|
||||
hir::ExprPath(ref qpath) => {
|
||||
self.print_qpath(qpath, true)?
|
||||
}
|
||||
hir::ExprBreak(opt_label, ref opt_expr) => {
|
||||
hir::ExprBreak(label, ref opt_expr) => {
|
||||
word(&mut self.s, "break")?;
|
||||
space(&mut self.s)?;
|
||||
if let Some(label) = opt_label {
|
||||
self.print_name(label.name)?;
|
||||
if let Some(label_ident) = label.ident {
|
||||
self.print_name(label_ident.node.name)?;
|
||||
space(&mut self.s)?;
|
||||
}
|
||||
if let Some(ref expr) = *opt_expr {
|
||||
@ -1366,11 +1366,11 @@ impl<'a> State<'a> {
|
||||
space(&mut self.s)?;
|
||||
}
|
||||
}
|
||||
hir::ExprAgain(opt_label) => {
|
||||
hir::ExprAgain(label) => {
|
||||
word(&mut self.s, "continue")?;
|
||||
space(&mut self.s)?;
|
||||
if let Some(label) = opt_label {
|
||||
self.print_name(label.name)?;
|
||||
if let Some(label_ident) = label.ident {
|
||||
self.print_name(label_ident.node.name)?;
|
||||
space(&mut self.s)?
|
||||
}
|
||||
}
|
||||
|
@ -675,23 +675,6 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_loop_scope(&self,
|
||||
opt_label: Option<hir::Label>,
|
||||
sp: Span)
|
||||
-> NodeId {
|
||||
match opt_label {
|
||||
Some(label) => label.loop_id,
|
||||
None => {
|
||||
// Vanilla 'break' or 'continue', so use the enclosing
|
||||
// loop scope
|
||||
if self.loop_scope.is_empty() {
|
||||
span_bug!(sp, "break outside loop");
|
||||
} else {
|
||||
*self.loop_scope.last().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
fn ln_str(&self, ln: LiveNode) -> String {
|
||||
@ -1018,9 +1001,12 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
|
||||
self.propagate_through_opt_expr(o_e.as_ref().map(|e| &**e), exit_ln)
|
||||
}
|
||||
|
||||
hir::ExprBreak(opt_label, ref opt_expr) => {
|
||||
hir::ExprBreak(label, ref opt_expr) => {
|
||||
// Find which label this break jumps to
|
||||
let sc = self.find_loop_scope(opt_label, expr.span);
|
||||
let sc = match label.loop_id.into() {
|
||||
Ok(loop_id) => loop_id,
|
||||
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 break loop nodes table
|
||||
@ -1031,9 +1017,13 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
hir::ExprAgain(opt_label) => {
|
||||
hir::ExprAgain(label) => {
|
||||
// Find which label this expr continues to
|
||||
let sc = self.find_loop_scope(opt_label, expr.span);
|
||||
let sc = match label.loop_id.into() {
|
||||
Ok(loop_id) => loop_id,
|
||||
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
|
||||
@ -1297,12 +1287,13 @@ 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 = match kind {
|
||||
LoopLoop => ln,
|
||||
WhileLoop(ref cond) => self.propagate_through_expr(&cond, ln),
|
||||
};
|
||||
let body_ln = self.with_loop_nodes(expr.id, succ, ln, |this| {
|
||||
this.propagate_through_block(body, cond_ln)
|
||||
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)
|
||||
});
|
||||
|
||||
// repeat until fixed point is reached:
|
||||
|
@ -338,8 +338,10 @@ fn saw_expr<'a>(node: &'a Expr_,
|
||||
ExprIndex(..) => (SawExprIndex, true),
|
||||
ExprPath(_) => (SawExprPath, false),
|
||||
ExprAddrOf(m, _) => (SawExprAddrOf(m), false),
|
||||
ExprBreak(label, _) => (SawExprBreak(label.map(|l| l.name.as_str())), false),
|
||||
ExprAgain(label) => (SawExprAgain(label.map(|l| l.name.as_str())), false),
|
||||
ExprBreak(label, _) => (SawExprBreak(label.ident.map(|i|
|
||||
i.node.name.as_str())), false),
|
||||
ExprAgain(label) => (SawExprAgain(label.ident.map(|i|
|
||||
i.node.name.as_str())), false),
|
||||
ExprRet(..) => (SawExprRet, false),
|
||||
ExprInlineAsm(ref a,..) => (SawExprInlineAsm(StableInlineAsm(a)), false),
|
||||
ExprStruct(..) => (SawExprStruct, false),
|
||||
|
@ -385,22 +385,14 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||
/// resolving `break` and `continue`.
|
||||
pub fn find_loop_scope(&mut self,
|
||||
span: Span,
|
||||
label: Option<CodeExtent>)
|
||||
label: CodeExtent)
|
||||
-> &mut LoopScope<'tcx> {
|
||||
let loop_scopes = &mut self.loop_scopes;
|
||||
match label {
|
||||
None => {
|
||||
// no label? return the innermost loop scope
|
||||
loop_scopes.iter_mut().rev().next()
|
||||
}
|
||||
Some(label) => {
|
||||
// otherwise, find the loop-scope with the correct id
|
||||
loop_scopes.iter_mut()
|
||||
.rev()
|
||||
.filter(|loop_scope| loop_scope.extent == label)
|
||||
.next()
|
||||
}
|
||||
}.unwrap_or_else(|| span_bug!(span, "no enclosing loop scope found?"))
|
||||
// find the loop-scope with the correct id
|
||||
self.loop_scopes.iter_mut()
|
||||
.rev()
|
||||
.filter(|loop_scope| loop_scope.extent == label)
|
||||
.next()
|
||||
.unwrap_or_else(|| span_bug!(span, "no enclosing loop scope found?"))
|
||||
}
|
||||
|
||||
/// Given a span and the current visibility scope, make a SourceInfo.
|
||||
|
@ -605,14 +605,21 @@ 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) => {
|
||||
ExprKind::Break {
|
||||
label: label.map(|label| cx.tcx.region_maps.node_extent(label.loop_id)),
|
||||
value: value.to_ref(),
|
||||
match label.loop_id.into() {
|
||||
Ok(loop_id) => ExprKind::Break {
|
||||
label: cx.tcx.region_maps.node_extent(loop_id),
|
||||
value: value.to_ref(),
|
||||
},
|
||||
Err(err) => bug!("invalid loop id for break: {}", err)
|
||||
}
|
||||
|
||||
}
|
||||
hir::ExprAgain(label) => {
|
||||
ExprKind::Continue {
|
||||
label: label.map(|label| cx.tcx.region_maps.node_extent(label.loop_id)),
|
||||
match label.loop_id.into() {
|
||||
Ok(loop_id) => ExprKind::Continue {
|
||||
label: cx.tcx.region_maps.node_extent(loop_id),
|
||||
},
|
||||
Err(err) => bug!("invalid loop id for continue: {}", err)
|
||||
}
|
||||
}
|
||||
hir::ExprMatch(ref discr, ref arms, _) => {
|
||||
|
@ -205,11 +205,11 @@ pub enum ExprKind<'tcx> {
|
||||
arg: ExprRef<'tcx>,
|
||||
},
|
||||
Break {
|
||||
label: Option<CodeExtent>,
|
||||
label: CodeExtent,
|
||||
value: Option<ExprRef<'tcx>>,
|
||||
},
|
||||
Continue {
|
||||
label: Option<CodeExtent>,
|
||||
label: CodeExtent,
|
||||
},
|
||||
Return {
|
||||
value: Option<ExprRef<'tcx>>,
|
||||
|
@ -241,6 +241,22 @@ match 5u32 {
|
||||
}
|
||||
```
|
||||
"##,
|
||||
|
||||
E0590: r##"
|
||||
`break` or `continue` must include a label when used in the condition of a
|
||||
`while` loop.
|
||||
|
||||
Example of erroneous code:
|
||||
|
||||
```compile_fail
|
||||
while break {}
|
||||
```
|
||||
|
||||
To fix this, add a label specifying which loop is being broken out of:
|
||||
```
|
||||
`foo: while break `foo {}
|
||||
```
|
||||
"##
|
||||
}
|
||||
|
||||
register_diagnostics! {
|
||||
|
@ -87,23 +87,26 @@ 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,
|
||||
};
|
||||
|
||||
if opt_expr.is_some() {
|
||||
let loop_kind = if let Some(label) = label {
|
||||
if label.loop_id == ast::DUMMY_NODE_ID {
|
||||
None
|
||||
} else {
|
||||
Some(match self.hir_map.expect_expr(label.loop_id).node {
|
||||
hir::ExprWhile(..) => LoopKind::WhileLoop,
|
||||
hir::ExprLoop(_, _, source) => LoopKind::Loop(source),
|
||||
ref r => span_bug!(e.span,
|
||||
"break label resolved to a non-loop: {:?}", r),
|
||||
})
|
||||
}
|
||||
} else if let Loop(kind) = self.cx {
|
||||
Some(kind)
|
||||
} else {
|
||||
// `break` outside a loop - caught below
|
||||
let loop_kind = if loop_id == ast::DUMMY_NODE_ID {
|
||||
None
|
||||
} else {
|
||||
Some(match self.hir_map.expect_expr(loop_id).node {
|
||||
hir::ExprWhile(..) => LoopKind::WhileLoop,
|
||||
hir::ExprLoop(_, _, source) => LoopKind::Loop(source),
|
||||
ref r => span_bug!(e.span,
|
||||
"break label resolved to a non-loop: {:?}", r),
|
||||
})
|
||||
};
|
||||
match loop_kind {
|
||||
None | Some(LoopKind::Loop(hir::LoopSource::Loop)) => (),
|
||||
@ -117,9 +120,15 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.require_loop("break", e.span);
|
||||
}
|
||||
hir::ExprAgain(_) => self.require_loop("continue", e.span),
|
||||
hir::ExprAgain(label) => {
|
||||
if let Err(hir::LoopIdError::UnlabeledCfInWhileCondition) = label.loop_id.into() {
|
||||
self.emit_unlabled_cf_in_while_condition(e.span, "continue");
|
||||
}
|
||||
self.require_loop("continue", e.span)
|
||||
},
|
||||
_ => intravisit::walk_expr(self, e),
|
||||
}
|
||||
}
|
||||
@ -150,4 +159,12 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_unlabled_cf_in_while_condition(&mut self, span: Span, cf_type: &str) {
|
||||
struct_span_err!(self.sess, span, E0590,
|
||||
"`break` or `continue` with no label in the condition of a `while` loop")
|
||||
.span_label(span,
|
||||
&format!("unlabeled `{}` in the condition of a `while` loop", cf_type))
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
@ -2768,18 +2768,24 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_labeled_block(&mut self, label: Option<SpannedIdent>, id: NodeId, block: &Block) {
|
||||
fn with_resolved_label<F>(&mut self, label: Option<SpannedIdent>, id: NodeId, f: F)
|
||||
where F: FnOnce(&mut Resolver)
|
||||
{
|
||||
if let Some(label) = label {
|
||||
let def = Def::Label(id);
|
||||
self.with_label_rib(|this| {
|
||||
this.label_ribs.last_mut().unwrap().bindings.insert(label.node, def);
|
||||
this.visit_block(block);
|
||||
f(this);
|
||||
});
|
||||
} else {
|
||||
self.visit_block(block);
|
||||
f(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_labeled_block(&mut self, label: Option<SpannedIdent>, id: NodeId, block: &Block) {
|
||||
self.with_resolved_label(label, id, |this| this.visit_block(block));
|
||||
}
|
||||
|
||||
fn resolve_expr(&mut self, expr: &Expr, parent: Option<&ExprKind>) {
|
||||
// First, record candidate traits for this expression if it could
|
||||
// result in the invocation of a method call.
|
||||
@ -2833,18 +2839,18 @@ impl<'a> Resolver<'a> {
|
||||
ExprKind::Loop(ref block, label) => self.resolve_labeled_block(label, expr.id, &block),
|
||||
|
||||
ExprKind::While(ref subexpression, ref block, label) => {
|
||||
self.visit_expr(subexpression);
|
||||
self.resolve_labeled_block(label, expr.id, &block);
|
||||
self.with_resolved_label(label, expr.id, |this| {
|
||||
this.visit_expr(subexpression);
|
||||
this.visit_block(block);
|
||||
});
|
||||
}
|
||||
|
||||
ExprKind::WhileLet(ref pattern, ref subexpression, ref block, label) => {
|
||||
self.visit_expr(subexpression);
|
||||
self.ribs[ValueNS].push(Rib::new(NormalRibKind));
|
||||
self.resolve_pattern(pattern, PatternSource::WhileLet, &mut FxHashMap());
|
||||
|
||||
self.resolve_labeled_block(label, expr.id, block);
|
||||
|
||||
self.ribs[ValueNS].pop();
|
||||
self.with_resolved_label(label, expr.id, |this| {
|
||||
this.visit_expr(subexpression);
|
||||
this.resolve_pattern(pattern, PatternSource::WhileLet, &mut FxHashMap());
|
||||
this.visit_block(block);
|
||||
});
|
||||
}
|
||||
|
||||
ExprKind::ForLoop(ref pattern, ref subexpression, ref block, label) => {
|
||||
|
@ -425,15 +425,12 @@ pub struct EnclosingLoops<'gcx, 'tcx> {
|
||||
}
|
||||
|
||||
impl<'gcx, 'tcx> EnclosingLoops<'gcx, 'tcx> {
|
||||
fn find_loop(&mut self, id: Option<ast::NodeId>) -> Option<&mut LoopCtxt<'gcx, 'tcx>> {
|
||||
if let Some(id) = id {
|
||||
if let Some(ix) = self.by_id.get(&id).cloned() {
|
||||
Some(&mut self.stack[ix])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
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()) {
|
||||
Some(&mut self.stack[ix])
|
||||
} else {
|
||||
self.stack.last_mut()
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3596,10 +3593,9 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
||||
tcx.mk_nil()
|
||||
}
|
||||
hir::ExprBreak(label, ref expr_opt) => {
|
||||
let loop_id = label.map(|l| l.loop_id);
|
||||
let coerce_to = {
|
||||
let mut enclosing_loops = self.enclosing_loops.borrow_mut();
|
||||
enclosing_loops.find_loop(loop_id).map(|ctxt| ctxt.coerce_to)
|
||||
enclosing_loops.find_loop(label.loop_id).map(|ctxt| ctxt.coerce_to)
|
||||
};
|
||||
if let Some(coerce_to) = coerce_to {
|
||||
let e_ty;
|
||||
@ -3614,8 +3610,9 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
||||
e_ty = tcx.mk_nil();
|
||||
cause = self.misc(expr.span);
|
||||
}
|
||||
|
||||
let mut enclosing_loops = self.enclosing_loops.borrow_mut();
|
||||
let ctxt = enclosing_loops.find_loop(loop_id).unwrap();
|
||||
let ctxt = enclosing_loops.find_loop(label.loop_id).unwrap();
|
||||
|
||||
let result = if let Some(ref e) = *expr_opt {
|
||||
// Special-case the first element, as it has no "previous expressions".
|
||||
|
55
src/test/compile-fail/issue-37576.rs
Normal file
55
src/test/compile-fail/issue-37576.rs
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
fn main() {
|
||||
'test_1: while break 'test_1 {}
|
||||
while break {}
|
||||
//~^ ERROR `break` or `continue` with no label
|
||||
|
||||
'test_2: while let true = break 'test_2 {}
|
||||
while let true = break {}
|
||||
//~^ ERROR `break` or `continue` with no label
|
||||
|
||||
loop { 'test_3: while break 'test_3 {} }
|
||||
loop { while break {} }
|
||||
//~^ ERROR `break` or `continue` with no label
|
||||
|
||||
loop {
|
||||
'test_4: while break 'test_4 {}
|
||||
break;
|
||||
}
|
||||
loop {
|
||||
while break {}
|
||||
//~^ ERROR `break` or `continue` with no label
|
||||
break;
|
||||
}
|
||||
|
||||
'test_5: while continue 'test_5 {}
|
||||
while continue {}
|
||||
//~^ ERROR `break` or `continue` with no label
|
||||
|
||||
'test_6: while let true = continue 'test_6 {}
|
||||
while let true = continue {}
|
||||
//~^ ERROR `break` or `continue` with no label
|
||||
|
||||
loop { 'test_7: while continue 'test_7 {} }
|
||||
loop { while continue {} }
|
||||
//~^ ERROR `break` or `continue` with no label
|
||||
|
||||
loop {
|
||||
'test_8: while continue 'test_8 {}
|
||||
continue;
|
||||
}
|
||||
loop {
|
||||
while continue {}
|
||||
//~^ ERROR `break` or `continue` with no label
|
||||
continue;
|
||||
}
|
||||
}
|
@ -17,8 +17,7 @@ fn f() {
|
||||
}
|
||||
}
|
||||
|
||||
// issue #37353
|
||||
loop { 'w: while break 'w { } } //~ ERROR use of undeclared label
|
||||
loop { 'w: while break 'w { } }
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
@ -5,10 +5,10 @@ digraph block {
|
||||
N3[label="local mut x"];
|
||||
N4[label="stmt let mut x = 10;"];
|
||||
N5[label="(dummy_node)"];
|
||||
N6[label="expr x"];
|
||||
N7[label="expr 0"];
|
||||
N8[label="expr x > 0"];
|
||||
N9[label="expr while x > 0 { x -= 1; }"];
|
||||
N6[label="expr while x > 0 { x -= 1; }"];
|
||||
N7[label="expr x"];
|
||||
N8[label="expr 0"];
|
||||
N9[label="expr x > 0"];
|
||||
N10[label="expr 1"];
|
||||
N11[label="expr x"];
|
||||
N12[label="expr x -= 1"];
|
||||
@ -20,17 +20,17 @@ digraph block {
|
||||
N2 -> N3;
|
||||
N3 -> N4;
|
||||
N4 -> N5;
|
||||
N5 -> N6;
|
||||
N6 -> N7;
|
||||
N5 -> N7;
|
||||
N7 -> N8;
|
||||
N8 -> N9;
|
||||
N8 -> N10;
|
||||
N9 -> N6;
|
||||
N9 -> N10;
|
||||
N10 -> N11;
|
||||
N11 -> N12;
|
||||
N12 -> N13;
|
||||
N13 -> N14;
|
||||
N14 -> N5;
|
||||
N9 -> N15;
|
||||
N6 -> N15;
|
||||
N15 -> N16;
|
||||
N16 -> N1;
|
||||
}
|
||||
|
@ -11,28 +11,28 @@ digraph block {
|
||||
N9[label="local mut z"];
|
||||
N10[label="stmt let mut z = 23;"];
|
||||
N11[label="(dummy_node)"];
|
||||
N12[label="expr x"];
|
||||
N13[label="expr 0"];
|
||||
N14[label="expr x > 0"];
|
||||
N15[label="expr while x > 0 {\l x -= 1;\l while y > 0 {\l y -= 1;\l while z > 0 { z -= 1; }\l if x > 10 { return; \"unreachable\"; }\l }\l}\l"];
|
||||
N12[label="expr while x > 0 {\l x -= 1;\l while y > 0 {\l y -= 1;\l while z > 0 { z -= 1; }\l if x > 10 { return; \"unreachable\"; }\l }\l}\l"];
|
||||
N13[label="expr x"];
|
||||
N14[label="expr 0"];
|
||||
N15[label="expr x > 0"];
|
||||
N16[label="expr 1"];
|
||||
N17[label="expr x"];
|
||||
N18[label="expr x -= 1"];
|
||||
N19[label="stmt x -= 1;"];
|
||||
N20[label="(dummy_node)"];
|
||||
N21[label="expr y"];
|
||||
N22[label="expr 0"];
|
||||
N23[label="expr y > 0"];
|
||||
N24[label="expr while y > 0 {\l y -= 1;\l while z > 0 { z -= 1; }\l if x > 10 { return; \"unreachable\"; }\l}\l"];
|
||||
N21[label="expr while y > 0 {\l y -= 1;\l while z > 0 { z -= 1; }\l if x > 10 { return; \"unreachable\"; }\l}\l"];
|
||||
N22[label="expr y"];
|
||||
N23[label="expr 0"];
|
||||
N24[label="expr y > 0"];
|
||||
N25[label="expr 1"];
|
||||
N26[label="expr y"];
|
||||
N27[label="expr y -= 1"];
|
||||
N28[label="stmt y -= 1;"];
|
||||
N29[label="(dummy_node)"];
|
||||
N30[label="expr z"];
|
||||
N31[label="expr 0"];
|
||||
N32[label="expr z > 0"];
|
||||
N33[label="expr while z > 0 { z -= 1; }"];
|
||||
N30[label="expr while z > 0 { z -= 1; }"];
|
||||
N31[label="expr z"];
|
||||
N32[label="expr 0"];
|
||||
N33[label="expr z > 0"];
|
||||
N34[label="expr 1"];
|
||||
N35[label="expr z"];
|
||||
N36[label="expr z -= 1"];
|
||||
@ -63,35 +63,35 @@ digraph block {
|
||||
N8 -> N9;
|
||||
N9 -> N10;
|
||||
N10 -> N11;
|
||||
N11 -> N12;
|
||||
N12 -> N13;
|
||||
N11 -> N13;
|
||||
N13 -> N14;
|
||||
N14 -> N15;
|
||||
N14 -> N16;
|
||||
N15 -> N12;
|
||||
N15 -> N16;
|
||||
N16 -> N17;
|
||||
N17 -> N18;
|
||||
N18 -> N19;
|
||||
N19 -> N20;
|
||||
N20 -> N21;
|
||||
N21 -> N22;
|
||||
N20 -> N22;
|
||||
N22 -> N23;
|
||||
N23 -> N24;
|
||||
N23 -> N25;
|
||||
N24 -> N21;
|
||||
N24 -> N25;
|
||||
N25 -> N26;
|
||||
N26 -> N27;
|
||||
N27 -> N28;
|
||||
N28 -> N29;
|
||||
N29 -> N30;
|
||||
N30 -> N31;
|
||||
N29 -> N31;
|
||||
N31 -> N32;
|
||||
N32 -> N33;
|
||||
N32 -> N34;
|
||||
N33 -> N30;
|
||||
N33 -> N34;
|
||||
N34 -> N35;
|
||||
N35 -> N36;
|
||||
N36 -> N37;
|
||||
N37 -> N38;
|
||||
N38 -> N29;
|
||||
N33 -> N39;
|
||||
N30 -> N39;
|
||||
N39 -> N40;
|
||||
N40 -> N41;
|
||||
N41 -> N42;
|
||||
@ -105,9 +105,9 @@ digraph block {
|
||||
N48 -> N49;
|
||||
N49 -> N50;
|
||||
N50 -> N20;
|
||||
N24 -> N51;
|
||||
N21 -> N51;
|
||||
N51 -> N11;
|
||||
N15 -> N52;
|
||||
N12 -> N52;
|
||||
N52 -> N53;
|
||||
N53 -> N1;
|
||||
}
|
||||
|
@ -124,10 +124,18 @@ pub fn main() {
|
||||
assert_eq!(nested_break_value, "hello");
|
||||
|
||||
let break_from_while_cond = loop {
|
||||
while break {
|
||||
'inner_loop: while break 'inner_loop {
|
||||
panic!();
|
||||
}
|
||||
break 123;
|
||||
};
|
||||
assert_eq!(break_from_while_cond, 123);
|
||||
|
||||
let break_from_while_to_outer = 'outer_loop: loop {
|
||||
while break 'outer_loop 567 {
|
||||
panic!("from_inner");
|
||||
}
|
||||
panic!("from outer");
|
||||
};
|
||||
assert_eq!(break_from_while_to_outer, 567);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user