Skip to content

Commit 4bae133

Browse files
Aatchpnkfelix
authored andcommitted
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.)
1 parent eb4961b commit 4bae133

File tree

7 files changed

+203
-65
lines changed

7 files changed

+203
-65
lines changed

src/librustc/middle/cfg/construct.rs

+104-56
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use middle::cfg::*;
1212
use middle::def;
1313
use middle::graph;
14+
use middle::pat_util;
1415
use middle::region::CodeExtent;
1516
use middle::ty;
1617
use syntax::ast;
@@ -149,23 +150,6 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
149150
pats.fold(pred, |pred, pat| self.pat(&**pat, pred))
150151
}
151152

152-
fn pats_any(&mut self,
153-
pats: &[P<ast::Pat>],
154-
pred: CFGIndex) -> CFGIndex {
155-
//! Handles case where just one of the patterns must match.
156-
157-
if pats.len() == 1 {
158-
self.pat(&*pats[0], pred)
159-
} else {
160-
let collect = self.add_dummy_node(&[]);
161-
for pat in pats {
162-
let pat_exit = self.pat(&**pat, pred);
163-
self.add_contained_edge(pat_exit, collect);
164-
}
165-
collect
166-
}
167-
}
168-
169153
fn expr(&mut self, expr: &ast::Expr, pred: CFGIndex) -> CFGIndex {
170154
match expr.node {
171155
ast::ExprBlock(ref blk) => {
@@ -288,45 +272,7 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
288272
}
289273

290274
ast::ExprMatch(ref discr, ref arms, _) => {
291-
//
292-
// [pred]
293-
// |
294-
// v 1
295-
// [discr]
296-
// |
297-
// v 2
298-
// [cond1]
299-
// / \
300-
// | \
301-
// v 3 \
302-
// [pat1] \
303-
// | |
304-
// v 4 |
305-
// [guard1] |
306-
// | |
307-
// | |
308-
// v 5 v
309-
// [body1] [cond2]
310-
// | / \
311-
// | ... ...
312-
// | | |
313-
// v 6 v v
314-
// [.....expr.....]
315-
//
316-
let discr_exit = self.expr(&**discr, pred); // 1
317-
318-
let expr_exit = self.add_ast_node(expr.id, &[]);
319-
let mut cond_exit = discr_exit;
320-
for arm in arms {
321-
cond_exit = self.add_dummy_node(&[cond_exit]); // 2
322-
let pats_exit = self.pats_any(&arm.pats,
323-
cond_exit); // 3
324-
let guard_exit = self.opt_expr(&arm.guard,
325-
pats_exit); // 4
326-
let body_exit = self.expr(&*arm.body, guard_exit); // 5
327-
self.add_contained_edge(body_exit, expr_exit); // 6
328-
}
329-
expr_exit
275+
self.match_(expr.id, &discr, &arms, pred)
330276
}
331277

332278
ast::ExprBinary(op, ref l, ref r) if ast_util::lazy_binop(op.node) => {
@@ -503,6 +449,108 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
503449
self.add_ast_node(expr.id, &[subexprs_exit])
504450
}
505451

452+
fn match_(&mut self, id: ast::NodeId, discr: &ast::Expr,
453+
arms: &[ast::Arm], pred: CFGIndex) -> CFGIndex {
454+
// The CFG for match expression is quite complex, so no ASCII
455+
// art for it (yet).
456+
//
457+
// The CFG generated below matches roughly what trans puts
458+
// out. Each pattern and guard is visited in parallel, with
459+
// arms containing multiple patterns generating multiple nodes
460+
// for the same guard expression. The guard expressions chain
461+
// into each other from top to bottom, with a specific
462+
// exception to allow some additional valid programs
463+
// (explained below). Trans differs slightly in that the
464+
// pattern matching may continue after a guard but the visible
465+
// behaviour should be the same.
466+
//
467+
// What is going on is explained in further comments.
468+
469+
// Visit the discriminant expression
470+
let discr_exit = self.expr(discr, pred);
471+
472+
// Add a node for the exit of the match expression as a whole.
473+
let expr_exit = self.add_ast_node(id, &[]);
474+
475+
// Keep track of the previous guard expressions
476+
let mut prev_guards = Vec::new();
477+
// Track if the previous pattern contained bindings or wildcards
478+
let mut prev_has_bindings = false;
479+
480+
for arm in arms {
481+
// Add an exit node for when we've visited all the
482+
// patterns and the guard (if there is one) in the arm.
483+
let arm_exit = self.add_dummy_node(&[]);
484+
485+
for pat in &arm.pats {
486+
// Visit the pattern, coming from the discriminant exit
487+
let mut pat_exit = self.pat(&**pat, discr_exit);
488+
489+
// If there is a guard expression, handle it here
490+
if let Some(ref guard) = arm.guard {
491+
// Add a dummy node for the previous guard
492+
// expression to target
493+
let guard_start = self.add_dummy_node(&[pat_exit]);
494+
// Visit the guard expression
495+
let guard_exit = self.expr(&**guard, guard_start);
496+
497+
let this_has_bindings = pat_util::pat_contains_bindings_or_wild(
498+
&self.tcx.def_map, &**pat);
499+
500+
// If both this pattern and the previous pattern
501+
// were free of bindings, they must consist only
502+
// of "constant" patterns. Note we cannot match an
503+
// all-constant pattern, fail the guard, and then
504+
// match *another* all-constant pattern. This is
505+
// because if the previous pattern matches, then
506+
// we *cannot* match this one, unless all the
507+
// constants are the same (which is rejected by
508+
// `check_match`).
509+
//
510+
// We can use this to be smarter about the flow
511+
// along guards. If the previous pattern matched,
512+
// then we know we will not visit the guard in
513+
// this one (whether or not the guard succeeded),
514+
// if the previous pattern failed, then we know
515+
// the guard for that pattern will not have been
516+
// visited. Thus, it is not possible to visit both
517+
// the previous guard and the current one when
518+
// both patterns consist only of constant
519+
// sub-patterns.
520+
//
521+
// However, if the above does not hold, then all
522+
// previous guards need to be wired to visit the
523+
// current guard pattern.
524+
if prev_has_bindings || this_has_bindings {
525+
while let Some(prev) = prev_guards.pop() {
526+
self.add_contained_edge(prev, guard_start);
527+
}
528+
}
529+
530+
prev_has_bindings = this_has_bindings;
531+
532+
// Push the guard onto the list of previous guards
533+
prev_guards.push(guard_exit);
534+
535+
// Update the exit node for the pattern
536+
pat_exit = guard_exit;
537+
}
538+
539+
// Add an edge from the exit of this pattern to the
540+
// exit of the arm
541+
self.add_contained_edge(pat_exit, arm_exit);
542+
}
543+
544+
// Visit the body of this arm
545+
let body_exit = self.expr(&arm.body, arm_exit);
546+
547+
// Link the body to the exit of the expression
548+
self.add_contained_edge(body_exit, expr_exit);
549+
}
550+
551+
expr_exit
552+
}
553+
506554
fn add_dummy_node(&mut self, preds: &[CFGIndex]) -> CFGIndex {
507555
self.add_node(CFGNodeData::Dummy, preds)
508556
}

src/librustc/middle/pat_util.rs

+15
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,21 @@ pub fn pat_contains_bindings(dm: &DefMap, pat: &ast::Pat) -> bool {
119119
contains_bindings
120120
}
121121

122+
/// Checks if the pattern contains any patterns that bind something to
123+
/// an ident or wildcard, e.g. `foo`, or `Foo(_)`, `foo @ Bar(..)`,
124+
pub fn pat_contains_bindings_or_wild(dm: &DefMap, pat: &ast::Pat) -> bool {
125+
let mut contains_bindings = false;
126+
walk_pat(pat, |p| {
127+
if pat_is_binding_or_wild(dm, p) {
128+
contains_bindings = true;
129+
false // there's at least one binding/wildcard, can short circuit now.
130+
} else {
131+
true
132+
}
133+
});
134+
contains_bindings
135+
}
136+
122137
pub fn simple_identifier<'a>(pat: &'a ast::Pat) -> Option<&'a ast::Ident> {
123138
match pat.node {
124139
ast::PatIdent(ast::BindByValue(_), ref path1, None) => {
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(box_syntax)]
12+
13+
pub fn main() {
14+
let x = box 1;
15+
16+
let v = (1, 2);
17+
18+
match v {
19+
(1, _) if take(x) => (),
20+
(_, 2) if take(x) => (), //~ ERROR use of moved value: `x`
21+
_ => (),
22+
}
23+
}
24+
25+
fn take<T>(_: T) -> bool { false }
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(box_syntax)]
12+
13+
pub fn main() {
14+
let x = box 1;
15+
16+
let v = (1, 2);
17+
18+
match v {
19+
(1, _) |
20+
(_, 2) if take(x) => (), //~ ERROR use of moved value: `x`
21+
_ => (),
22+
}
23+
}
24+
25+
fn take<T>(_: T) -> bool { false }

src/test/run-make/graphviz-flowgraph/f07.dot-expected.dot

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ digraph block {
2222
N3 -> N4;
2323
N4 -> N5;
2424
N5 -> N6;
25-
N6 -> N8;
26-
N8 -> N9;
25+
N6 -> N9;
2726
N9 -> N10;
2827
N10 -> N11;
2928
N11 -> N12;
30-
N12 -> N13;
29+
N12 -> N8;
30+
N8 -> N13;
3131
N13 -> N14;
3232
N14 -> N15;
3333
N15 -> N7;

src/test/run-make/graphviz-flowgraph/f13.dot-expected.dot

+6-6
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ digraph block {
3232
N6 -> N7;
3333
N7 -> N8;
3434
N8 -> N9;
35-
N9 -> N11;
36-
N11 -> N12;
37-
N12 -> N13;
35+
N9 -> N12;
36+
N12 -> N11;
37+
N11 -> N13;
3838
N13 -> N14;
3939
N14 -> N15;
4040
N15 -> N10;
41-
N11 -> N16;
42-
N16 -> N17;
41+
N9 -> N17;
4342
N17 -> N18;
44-
N18 -> N19;
43+
N18 -> N16;
44+
N16 -> N19;
4545
N19 -> N20;
4646
N20 -> N21;
4747
N21 -> N22;

src/test/run-pass/move-guard-const.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(box_syntax)]
12+
13+
fn main() {
14+
let x = box 1;
15+
16+
let v = (1, 2);
17+
18+
match v {
19+
(2, 1) if take(x) => (),
20+
(1, 2) if take(x) => (),
21+
_ => (),
22+
}
23+
}
24+
25+
fn take<T>(_: T) -> bool { false }

0 commit comments

Comments
 (0)