Skip to content

Commit e560db7

Browse files
committed
auto merge of #13034 : edwardw/rust/match, r=nikomatsakis
The `_match.rs` takes advantage of passes prior to `trans` and aggressively prunes the sub-match tree based on exact equality. When it comes to literal or range, the strategy may lead to wrong result if there's guard function or multiple patterns inside tuple. Closes #12582. Closes #13027.
2 parents c329a17 + 4112941 commit e560db7

File tree

3 files changed

+280
-41
lines changed

3 files changed

+280
-41
lines changed

src/librustc/middle/trans/_match.rs

+81-41
Original file line numberDiff line numberDiff line change
@@ -256,43 +256,23 @@ enum Opt {
256256
vec_len(/* length */ uint, VecLenOpt, /*range of matches*/(uint, uint))
257257
}
258258

259+
fn lit_to_expr(tcx: &ty::ctxt, a: &Lit) -> @ast::Expr {
260+
match *a {
261+
ExprLit(existing_a_expr) => existing_a_expr,
262+
ConstLit(a_const) => const_eval::lookup_const_by_id(tcx, a_const).unwrap(),
263+
UnitLikeStructLit(_) => fail!("lit_to_expr: unexpected struct lit"),
264+
}
265+
}
266+
259267
fn opt_eq(tcx: &ty::ctxt, a: &Opt, b: &Opt) -> bool {
260268
match (a, b) {
269+
(&lit(UnitLikeStructLit(a)), &lit(UnitLikeStructLit(b))) => a == b,
261270
(&lit(a), &lit(b)) => {
262-
match (a, b) {
263-
(UnitLikeStructLit(a), UnitLikeStructLit(b)) => a == b,
264-
_ => {
265-
let a_expr;
266-
match a {
267-
ExprLit(existing_a_expr) => a_expr = existing_a_expr,
268-
ConstLit(a_const) => {
269-
let e = const_eval::lookup_const_by_id(tcx, a_const);
270-
a_expr = e.unwrap();
271-
}
272-
UnitLikeStructLit(_) => {
273-
fail!("UnitLikeStructLit should have been handled \
274-
above")
275-
}
276-
}
277-
278-
let b_expr;
279-
match b {
280-
ExprLit(existing_b_expr) => b_expr = existing_b_expr,
281-
ConstLit(b_const) => {
282-
let e = const_eval::lookup_const_by_id(tcx, b_const);
283-
b_expr = e.unwrap();
284-
}
285-
UnitLikeStructLit(_) => {
286-
fail!("UnitLikeStructLit should have been handled \
287-
above")
288-
}
289-
}
290-
291-
match const_eval::compare_lit_exprs(tcx, a_expr, b_expr) {
292-
Some(val1) => val1 == 0,
293-
None => fail!("compare_list_exprs: type mismatch"),
294-
}
295-
}
271+
let a_expr = lit_to_expr(tcx, &a);
272+
let b_expr = lit_to_expr(tcx, &b);
273+
match const_eval::compare_lit_exprs(tcx, a_expr, b_expr) {
274+
Some(val1) => val1 == 0,
275+
None => fail!("compare_list_exprs: type mismatch"),
296276
}
297277
}
298278
(&range(a1, a2), &range(b1, b2)) => {
@@ -310,6 +290,42 @@ fn opt_eq(tcx: &ty::ctxt, a: &Opt, b: &Opt) -> bool {
310290
}
311291
}
312292

293+
fn opt_overlap(tcx: &ty::ctxt, a: &Opt, b: &Opt) -> bool {
294+
match (a, b) {
295+
(&lit(a), &lit(b)) => {
296+
let a_expr = lit_to_expr(tcx, &a);
297+
let b_expr = lit_to_expr(tcx, &b);
298+
match const_eval::compare_lit_exprs(tcx, a_expr, b_expr) {
299+
Some(val1) => val1 == 0,
300+
None => fail!("opt_overlap: type mismatch"),
301+
}
302+
}
303+
304+
(&range(a1, a2), &range(b1, b2)) => {
305+
let m1 = const_eval::compare_lit_exprs(tcx, a1, b2);
306+
let m2 = const_eval::compare_lit_exprs(tcx, b1, a2);
307+
match (m1, m2) {
308+
// two ranges [a1, a2] and [b1, b2] overlap iff:
309+
// a1 <= b2 && b1 <= a2
310+
(Some(val1), Some(val2)) => (val1 <= 0 && val2 <= 0),
311+
_ => fail!("opt_overlap: type mismatch"),
312+
}
313+
}
314+
315+
(&range(a1, a2), &lit(b)) | (&lit(b), &range(a1, a2)) => {
316+
let b_expr = lit_to_expr(tcx, &b);
317+
let m1 = const_eval::compare_lit_exprs(tcx, a1, b_expr);
318+
let m2 = const_eval::compare_lit_exprs(tcx, a2, b_expr);
319+
match (m1, m2) {
320+
// b is in range [a1, a2] iff a1 <= b and b <= a2
321+
(Some(val1), Some(val2)) => (val1 <= 0 && 0 <= val2),
322+
_ => fail!("opt_overlap: type mismatch"),
323+
}
324+
}
325+
_ => fail!("opt_overlap: expect lit or range")
326+
}
327+
}
328+
313329
pub enum opt_result<'a> {
314330
single_result(Result<'a>),
315331
lower_bound(Result<'a>),
@@ -490,7 +506,7 @@ fn assert_is_binding_or_wild(bcx: &Block, p: @ast::Pat) {
490506
}
491507
}
492508

493-
type enter_pat<'a> = 'a |@ast::Pat| -> Option<Vec<@ast::Pat> >;
509+
type enter_pat<'a> = 'a |@ast::Pat| -> Option<Vec<@ast::Pat>>;
494510

495511
fn enter_match<'r,'b>(
496512
bcx: &'b Block<'b>,
@@ -632,16 +648,30 @@ fn enter_opt<'r,'b>(
632648
let tcx = bcx.tcx();
633649
let dummy = @ast::Pat {id: 0, node: ast::PatWild, span: DUMMY_SP};
634650
let mut i = 0;
651+
// By the virtue of fact that we are in `trans` already, `enter_opt` is able
652+
// to prune sub-match tree aggressively based on exact equality. But when it
653+
// comes to literal or range, that strategy may lead to wrong result if there
654+
// are guard function or multiple patterns inside tuple; in that case, pruning
655+
// based on the overlap of patterns is required.
656+
//
657+
// Ideally, when constructing the sub-match tree for certain arm, only those
658+
// arms beneath it matter. But that isn't how algorithm works right now and
659+
// all other arms are taken into consideration when computing `guarded` below.
660+
// That is ok since each round of `compile_submatch` guarantees to trim one
661+
// "column" of arm patterns and the algorithm will converge.
662+
let guarded = m.iter().any(|x| x.data.arm.guard.is_some());
663+
let multi_pats = m.len() > 0 && m[0].pats.len() > 1;
635664
enter_match(bcx, tcx.def_map, m, col, val, |p| {
636665
let answer = match p.node {
637666
ast::PatEnum(..) |
638667
ast::PatIdent(_, _, None) if pat_is_const(tcx.def_map, p) => {
639668
let const_def = tcx.def_map.borrow().get_copy(&p.id);
640669
let const_def_id = ast_util::def_id_of_def(const_def);
641-
if opt_eq(tcx, &lit(ConstLit(const_def_id)), opt) {
642-
Some(Vec::new())
643-
} else {
644-
None
670+
let konst = lit(ConstLit(const_def_id));
671+
match guarded || multi_pats {
672+
false if opt_eq(tcx, &konst, opt) => Some(Vec::new()),
673+
true if opt_overlap(tcx, &konst, opt) => Some(Vec::new()),
674+
_ => None,
645675
}
646676
}
647677
ast::PatEnum(_, ref subpats) => {
@@ -666,10 +696,20 @@ fn enter_opt<'r,'b>(
666696
}
667697
}
668698
ast::PatLit(l) => {
669-
if opt_eq(tcx, &lit(ExprLit(l)), opt) {Some(Vec::new())} else {None}
699+
let lit_expr = lit(ExprLit(l));
700+
match guarded || multi_pats {
701+
false if opt_eq(tcx, &lit_expr, opt) => Some(Vec::new()),
702+
true if opt_overlap(tcx, &lit_expr, opt) => Some(Vec::new()),
703+
_ => None,
704+
}
670705
}
671706
ast::PatRange(l1, l2) => {
672-
if opt_eq(tcx, &range(l1, l2), opt) {Some(Vec::new())} else {None}
707+
let rng = range(l1, l2);
708+
match guarded || multi_pats {
709+
false if opt_eq(tcx, &rng, opt) => Some(Vec::new()),
710+
true if opt_overlap(tcx, &rng, opt) => Some(Vec::new()),
711+
_ => None,
712+
}
673713
}
674714
ast::PatStruct(_, ref field_pats, _) => {
675715
if opt_eq(tcx, &variant_opt(bcx, p.id), opt) {

src/test/run-pass/issue-12582.rs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2012-2014 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+
pub fn main() {
12+
let x = 1;
13+
let y = 2;
14+
15+
assert_eq!(3, match (x, y) {
16+
(1, 1) => 1,
17+
(2, 2) => 2,
18+
(1..2, 2) => 3,
19+
_ => 4,
20+
});
21+
22+
// nested tuple
23+
assert_eq!(3, match ((x, y),) {
24+
((1, 1),) => 1,
25+
((2, 2),) => 2,
26+
((1..2, 2),) => 3,
27+
_ => 4,
28+
});
29+
}

src/test/run-pass/issue-13027.rs

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright 2012-2014 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+
// Tests that match expression handles overlapped literal and range
12+
// properly in the presence of guard function.
13+
14+
fn val() -> uint { 1 }
15+
16+
static CONST: uint = 1;
17+
18+
pub fn main() {
19+
lit_shadow_range();
20+
range_shadow_lit();
21+
range_shadow_range();
22+
multi_pats_shadow_lit();
23+
multi_pats_shadow_range();
24+
lit_shadow_multi_pats();
25+
range_shadow_multi_pats();
26+
}
27+
28+
fn lit_shadow_range() {
29+
assert_eq!(2, match 1 {
30+
1 if false => 1,
31+
1..2 => 2,
32+
_ => 3
33+
});
34+
35+
let x = 0;
36+
assert_eq!(2, match x+1 {
37+
0 => 0,
38+
1 if false => 1,
39+
1..2 => 2,
40+
_ => 3
41+
});
42+
43+
assert_eq!(2, match val() {
44+
1 if false => 1,
45+
1..2 => 2,
46+
_ => 3
47+
});
48+
49+
assert_eq!(2, match CONST {
50+
0 => 0,
51+
1 if false => 1,
52+
1..2 => 2,
53+
_ => 3
54+
});
55+
56+
// value is out of the range of second arm, should match wildcard pattern
57+
assert_eq!(3, match 3 {
58+
1 if false => 1,
59+
1..2 => 2,
60+
_ => 3
61+
});
62+
}
63+
64+
fn range_shadow_lit() {
65+
assert_eq!(2, match 1 {
66+
1..2 if false => 1,
67+
1 => 2,
68+
_ => 3
69+
});
70+
71+
let x = 0;
72+
assert_eq!(2, match x+1 {
73+
0 => 0,
74+
1..2 if false => 1,
75+
1 => 2,
76+
_ => 3
77+
});
78+
79+
assert_eq!(2, match val() {
80+
1..2 if false => 1,
81+
1 => 2,
82+
_ => 3
83+
});
84+
85+
assert_eq!(2, match CONST {
86+
0 => 0,
87+
1..2 if false => 1,
88+
1 => 2,
89+
_ => 3
90+
});
91+
92+
// ditto
93+
assert_eq!(3, match 3 {
94+
1..2 if false => 1,
95+
1 => 2,
96+
_ => 3
97+
});
98+
}
99+
100+
fn range_shadow_range() {
101+
assert_eq!(2, match 1 {
102+
0..2 if false => 1,
103+
1..3 => 2,
104+
_ => 3,
105+
});
106+
107+
let x = 0;
108+
assert_eq!(2, match x+1 {
109+
100 => 0,
110+
0..2 if false => 1,
111+
1..3 => 2,
112+
_ => 3,
113+
});
114+
115+
assert_eq!(2, match val() {
116+
0..2 if false => 1,
117+
1..3 => 2,
118+
_ => 3,
119+
});
120+
121+
assert_eq!(2, match CONST {
122+
100 => 0,
123+
0..2 if false => 1,
124+
1..3 => 2,
125+
_ => 3,
126+
});
127+
128+
// ditto
129+
assert_eq!(3, match 5 {
130+
0..2 if false => 1,
131+
1..3 => 2,
132+
_ => 3,
133+
});
134+
}
135+
136+
fn multi_pats_shadow_lit() {
137+
assert_eq!(2, match 1 {
138+
100 => 0,
139+
0 | 1..10 if false => 1,
140+
1 => 2,
141+
_ => 3,
142+
});
143+
}
144+
145+
fn multi_pats_shadow_range() {
146+
assert_eq!(2, match 1 {
147+
100 => 0,
148+
0 | 1..10 if false => 1,
149+
1..3 => 2,
150+
_ => 3,
151+
});
152+
}
153+
154+
fn lit_shadow_multi_pats() {
155+
assert_eq!(2, match 1 {
156+
100 => 0,
157+
1 if false => 1,
158+
0 | 1..10 => 2,
159+
_ => 3,
160+
});
161+
}
162+
163+
fn range_shadow_multi_pats() {
164+
assert_eq!(2, match 1 {
165+
100 => 0,
166+
1..3 if false => 1,
167+
0 | 1..10 => 2,
168+
_ => 3,
169+
});
170+
}

0 commit comments

Comments
 (0)