mirror of
https://github.com/rust-lang/rust.git
synced 2024-12-18 03:25:55 +00:00
revise handling of match expressions so that arms branch to next arm.
Update the graphviz tests accordingly. Fixes #22073. (Includes regression test for the issue.) (Factoring of aatch CFG code, Part 4.)
This commit is contained in:
parent
eb4961b961
commit
4bae133070
@ -11,6 +11,7 @@
|
|||||||
use middle::cfg::*;
|
use middle::cfg::*;
|
||||||
use middle::def;
|
use middle::def;
|
||||||
use middle::graph;
|
use middle::graph;
|
||||||
|
use middle::pat_util;
|
||||||
use middle::region::CodeExtent;
|
use middle::region::CodeExtent;
|
||||||
use middle::ty;
|
use middle::ty;
|
||||||
use syntax::ast;
|
use syntax::ast;
|
||||||
@ -149,23 +150,6 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
|
|||||||
pats.fold(pred, |pred, pat| self.pat(&**pat, pred))
|
pats.fold(pred, |pred, pat| self.pat(&**pat, pred))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pats_any(&mut self,
|
|
||||||
pats: &[P<ast::Pat>],
|
|
||||||
pred: CFGIndex) -> CFGIndex {
|
|
||||||
//! Handles case where just one of the patterns must match.
|
|
||||||
|
|
||||||
if pats.len() == 1 {
|
|
||||||
self.pat(&*pats[0], pred)
|
|
||||||
} else {
|
|
||||||
let collect = self.add_dummy_node(&[]);
|
|
||||||
for pat in pats {
|
|
||||||
let pat_exit = self.pat(&**pat, pred);
|
|
||||||
self.add_contained_edge(pat_exit, collect);
|
|
||||||
}
|
|
||||||
collect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expr(&mut self, expr: &ast::Expr, pred: CFGIndex) -> CFGIndex {
|
fn expr(&mut self, expr: &ast::Expr, pred: CFGIndex) -> CFGIndex {
|
||||||
match expr.node {
|
match expr.node {
|
||||||
ast::ExprBlock(ref blk) => {
|
ast::ExprBlock(ref blk) => {
|
||||||
@ -288,45 +272,7 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ast::ExprMatch(ref discr, ref arms, _) => {
|
ast::ExprMatch(ref discr, ref arms, _) => {
|
||||||
//
|
self.match_(expr.id, &discr, &arms, pred)
|
||||||
// [pred]
|
|
||||||
// |
|
|
||||||
// v 1
|
|
||||||
// [discr]
|
|
||||||
// |
|
|
||||||
// v 2
|
|
||||||
// [cond1]
|
|
||||||
// / \
|
|
||||||
// | \
|
|
||||||
// v 3 \
|
|
||||||
// [pat1] \
|
|
||||||
// | |
|
|
||||||
// v 4 |
|
|
||||||
// [guard1] |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// v 5 v
|
|
||||||
// [body1] [cond2]
|
|
||||||
// | / \
|
|
||||||
// | ... ...
|
|
||||||
// | | |
|
|
||||||
// v 6 v v
|
|
||||||
// [.....expr.....]
|
|
||||||
//
|
|
||||||
let discr_exit = self.expr(&**discr, pred); // 1
|
|
||||||
|
|
||||||
let expr_exit = self.add_ast_node(expr.id, &[]);
|
|
||||||
let mut cond_exit = discr_exit;
|
|
||||||
for arm in arms {
|
|
||||||
cond_exit = self.add_dummy_node(&[cond_exit]); // 2
|
|
||||||
let pats_exit = self.pats_any(&arm.pats,
|
|
||||||
cond_exit); // 3
|
|
||||||
let guard_exit = self.opt_expr(&arm.guard,
|
|
||||||
pats_exit); // 4
|
|
||||||
let body_exit = self.expr(&*arm.body, guard_exit); // 5
|
|
||||||
self.add_contained_edge(body_exit, expr_exit); // 6
|
|
||||||
}
|
|
||||||
expr_exit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::ExprBinary(op, ref l, ref r) if ast_util::lazy_binop(op.node) => {
|
ast::ExprBinary(op, ref l, ref r) if ast_util::lazy_binop(op.node) => {
|
||||||
@ -503,6 +449,108 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
|
|||||||
self.add_ast_node(expr.id, &[subexprs_exit])
|
self.add_ast_node(expr.id, &[subexprs_exit])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn match_(&mut self, id: ast::NodeId, discr: &ast::Expr,
|
||||||
|
arms: &[ast::Arm], pred: CFGIndex) -> CFGIndex {
|
||||||
|
// The CFG for match expression is quite complex, so no ASCII
|
||||||
|
// art for it (yet).
|
||||||
|
//
|
||||||
|
// The CFG generated below matches roughly what trans puts
|
||||||
|
// out. Each pattern and guard is visited in parallel, with
|
||||||
|
// arms containing multiple patterns generating multiple nodes
|
||||||
|
// for the same guard expression. The guard expressions chain
|
||||||
|
// into each other from top to bottom, with a specific
|
||||||
|
// exception to allow some additional valid programs
|
||||||
|
// (explained below). Trans differs slightly in that the
|
||||||
|
// pattern matching may continue after a guard but the visible
|
||||||
|
// behaviour should be the same.
|
||||||
|
//
|
||||||
|
// What is going on is explained in further comments.
|
||||||
|
|
||||||
|
// Visit the discriminant expression
|
||||||
|
let discr_exit = self.expr(discr, pred);
|
||||||
|
|
||||||
|
// Add a node for the exit of the match expression as a whole.
|
||||||
|
let expr_exit = self.add_ast_node(id, &[]);
|
||||||
|
|
||||||
|
// Keep track of the previous guard expressions
|
||||||
|
let mut prev_guards = Vec::new();
|
||||||
|
// Track if the previous pattern contained bindings or wildcards
|
||||||
|
let mut prev_has_bindings = false;
|
||||||
|
|
||||||
|
for arm in arms {
|
||||||
|
// Add an exit node for when we've visited all the
|
||||||
|
// patterns and the guard (if there is one) in the arm.
|
||||||
|
let arm_exit = self.add_dummy_node(&[]);
|
||||||
|
|
||||||
|
for pat in &arm.pats {
|
||||||
|
// Visit the pattern, coming from the discriminant exit
|
||||||
|
let mut pat_exit = self.pat(&**pat, discr_exit);
|
||||||
|
|
||||||
|
// If there is a guard expression, handle it here
|
||||||
|
if let Some(ref guard) = arm.guard {
|
||||||
|
// Add a dummy node for the previous guard
|
||||||
|
// expression to target
|
||||||
|
let guard_start = self.add_dummy_node(&[pat_exit]);
|
||||||
|
// Visit the guard expression
|
||||||
|
let guard_exit = self.expr(&**guard, guard_start);
|
||||||
|
|
||||||
|
let this_has_bindings = pat_util::pat_contains_bindings_or_wild(
|
||||||
|
&self.tcx.def_map, &**pat);
|
||||||
|
|
||||||
|
// If both this pattern and the previous pattern
|
||||||
|
// were free of bindings, they must consist only
|
||||||
|
// of "constant" patterns. Note we cannot match an
|
||||||
|
// all-constant pattern, fail the guard, and then
|
||||||
|
// match *another* all-constant pattern. This is
|
||||||
|
// because if the previous pattern matches, then
|
||||||
|
// we *cannot* match this one, unless all the
|
||||||
|
// constants are the same (which is rejected by
|
||||||
|
// `check_match`).
|
||||||
|
//
|
||||||
|
// We can use this to be smarter about the flow
|
||||||
|
// along guards. If the previous pattern matched,
|
||||||
|
// then we know we will not visit the guard in
|
||||||
|
// this one (whether or not the guard succeeded),
|
||||||
|
// if the previous pattern failed, then we know
|
||||||
|
// the guard for that pattern will not have been
|
||||||
|
// visited. Thus, it is not possible to visit both
|
||||||
|
// the previous guard and the current one when
|
||||||
|
// both patterns consist only of constant
|
||||||
|
// sub-patterns.
|
||||||
|
//
|
||||||
|
// However, if the above does not hold, then all
|
||||||
|
// previous guards need to be wired to visit the
|
||||||
|
// current guard pattern.
|
||||||
|
if prev_has_bindings || this_has_bindings {
|
||||||
|
while let Some(prev) = prev_guards.pop() {
|
||||||
|
self.add_contained_edge(prev, guard_start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_has_bindings = this_has_bindings;
|
||||||
|
|
||||||
|
// Push the guard onto the list of previous guards
|
||||||
|
prev_guards.push(guard_exit);
|
||||||
|
|
||||||
|
// Update the exit node for the pattern
|
||||||
|
pat_exit = guard_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an edge from the exit of this pattern to the
|
||||||
|
// exit of the arm
|
||||||
|
self.add_contained_edge(pat_exit, arm_exit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit the body of this arm
|
||||||
|
let body_exit = self.expr(&arm.body, arm_exit);
|
||||||
|
|
||||||
|
// Link the body to the exit of the expression
|
||||||
|
self.add_contained_edge(body_exit, expr_exit);
|
||||||
|
}
|
||||||
|
|
||||||
|
expr_exit
|
||||||
|
}
|
||||||
|
|
||||||
fn add_dummy_node(&mut self, preds: &[CFGIndex]) -> CFGIndex {
|
fn add_dummy_node(&mut self, preds: &[CFGIndex]) -> CFGIndex {
|
||||||
self.add_node(CFGNodeData::Dummy, preds)
|
self.add_node(CFGNodeData::Dummy, preds)
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,21 @@ pub fn pat_contains_bindings(dm: &DefMap, pat: &ast::Pat) -> bool {
|
|||||||
contains_bindings
|
contains_bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the pattern contains any patterns that bind something to
|
||||||
|
/// an ident or wildcard, e.g. `foo`, or `Foo(_)`, `foo @ Bar(..)`,
|
||||||
|
pub fn pat_contains_bindings_or_wild(dm: &DefMap, pat: &ast::Pat) -> bool {
|
||||||
|
let mut contains_bindings = false;
|
||||||
|
walk_pat(pat, |p| {
|
||||||
|
if pat_is_binding_or_wild(dm, p) {
|
||||||
|
contains_bindings = true;
|
||||||
|
false // there's at least one binding/wildcard, can short circuit now.
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
contains_bindings
|
||||||
|
}
|
||||||
|
|
||||||
pub fn simple_identifier<'a>(pat: &'a ast::Pat) -> Option<&'a ast::Ident> {
|
pub fn simple_identifier<'a>(pat: &'a ast::Pat) -> Option<&'a ast::Ident> {
|
||||||
match pat.node {
|
match pat.node {
|
||||||
ast::PatIdent(ast::BindByValue(_), ref path1, None) => {
|
ast::PatIdent(ast::BindByValue(_), ref path1, None) => {
|
||||||
|
25
src/test/compile-fail/move-in-guard-1.rs
Normal file
25
src/test/compile-fail/move-in-guard-1.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2015 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(box_syntax)]
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let x = box 1;
|
||||||
|
|
||||||
|
let v = (1, 2);
|
||||||
|
|
||||||
|
match v {
|
||||||
|
(1, _) if take(x) => (),
|
||||||
|
(_, 2) if take(x) => (), //~ ERROR use of moved value: `x`
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take<T>(_: T) -> bool { false }
|
25
src/test/compile-fail/move-in-guard-2.rs
Normal file
25
src/test/compile-fail/move-in-guard-2.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2015 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(box_syntax)]
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let x = box 1;
|
||||||
|
|
||||||
|
let v = (1, 2);
|
||||||
|
|
||||||
|
match v {
|
||||||
|
(1, _) |
|
||||||
|
(_, 2) if take(x) => (), //~ ERROR use of moved value: `x`
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take<T>(_: T) -> bool { false }
|
@ -22,12 +22,12 @@ digraph block {
|
|||||||
N3 -> N4;
|
N3 -> N4;
|
||||||
N4 -> N5;
|
N4 -> N5;
|
||||||
N5 -> N6;
|
N5 -> N6;
|
||||||
N6 -> N8;
|
N6 -> N9;
|
||||||
N8 -> N9;
|
|
||||||
N9 -> N10;
|
N9 -> N10;
|
||||||
N10 -> N11;
|
N10 -> N11;
|
||||||
N11 -> N12;
|
N11 -> N12;
|
||||||
N12 -> N13;
|
N12 -> N8;
|
||||||
|
N8 -> N13;
|
||||||
N13 -> N14;
|
N13 -> N14;
|
||||||
N14 -> N15;
|
N14 -> N15;
|
||||||
N15 -> N7;
|
N15 -> N7;
|
||||||
|
@ -32,16 +32,16 @@ digraph block {
|
|||||||
N6 -> N7;
|
N6 -> N7;
|
||||||
N7 -> N8;
|
N7 -> N8;
|
||||||
N8 -> N9;
|
N8 -> N9;
|
||||||
N9 -> N11;
|
N9 -> N12;
|
||||||
N11 -> N12;
|
N12 -> N11;
|
||||||
N12 -> N13;
|
N11 -> N13;
|
||||||
N13 -> N14;
|
N13 -> N14;
|
||||||
N14 -> N15;
|
N14 -> N15;
|
||||||
N15 -> N10;
|
N15 -> N10;
|
||||||
N11 -> N16;
|
N9 -> N17;
|
||||||
N16 -> N17;
|
|
||||||
N17 -> N18;
|
N17 -> N18;
|
||||||
N18 -> N19;
|
N18 -> N16;
|
||||||
|
N16 -> N19;
|
||||||
N19 -> N20;
|
N19 -> N20;
|
||||||
N20 -> N21;
|
N20 -> N21;
|
||||||
N21 -> N22;
|
N21 -> N22;
|
||||||
|
25
src/test/run-pass/move-guard-const.rs
Normal file
25
src/test/run-pass/move-guard-const.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2015 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(box_syntax)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let x = box 1;
|
||||||
|
|
||||||
|
let v = (1, 2);
|
||||||
|
|
||||||
|
match v {
|
||||||
|
(2, 1) if take(x) => (),
|
||||||
|
(1, 2) if take(x) => (),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take<T>(_: T) -> bool { false }
|
Loading…
Reference in New Issue
Block a user