1
Fork 0

Rewrite exhaustiveness checker

Issue #352
Closes #1720

The old checker would happily accept things like 'alt x { @some(a) { a } }'.
It now properly descends into patterns, checks exhaustiveness of booleans,
and complains when number/string patterns aren't exhaustive.
This commit is contained in:
Marijn Haverbeke 2012-02-15 09:40:42 +01:00
parent 4b63826050
commit 67cc89f38d
32 changed files with 193 additions and 125 deletions

View file

@ -218,7 +218,7 @@ fn lookup_def(cnum: ast::crate_num, data: @[u8], did_: ast::def_id) ->
let fam_ch = item_family(item);
let did = {crate: cnum, node: did_.node};
// We treat references to enums as references to types.
alt fam_ch {
alt check fam_ch {
'c' { ast::def_const(did) }
'u' { ast::def_fn(did, ast::unsafe_fn) }
'f' { ast::def_fn(did, ast::impure_fn) }
@ -336,7 +336,7 @@ fn get_iface_methods(cdata: cmd, id: ast::node_id, tcx: ty::ctxt)
_ { tcx.sess.bug("get_iface_methods: id has non-function type");
} };
result += [{ident: name, tps: bounds, fty: fty,
purity: alt item_family(mth) {
purity: alt check item_family(mth) {
'u' { ast::unsafe_fn }
'f' { ast::impure_fn }
'p' { ast::pure_fn }
@ -346,7 +346,7 @@ fn get_iface_methods(cdata: cmd, id: ast::node_id, tcx: ty::ctxt)
}
fn family_has_type_params(fam_ch: char) -> bool {
alt fam_ch {
alt check fam_ch {
'c' | 'T' | 'm' | 'n' { false }
'f' | 'u' | 'p' | 'F' | 'U' | 'P' | 'y' | 't' | 'v' | 'i' | 'I' { true }
}
@ -370,7 +370,7 @@ fn describe_def(items: ebml::doc, id: ast::def_id) -> str {
}
fn item_family_to_str(fam: char) -> str {
alt fam {
alt check fam {
'c' { ret "const"; }
'f' { ret "fn"; }
'u' { ret "unsafe fn"; }

View file

@ -177,7 +177,7 @@ fn parse_proto(c: char) -> ast::proto {
}
fn parse_ty(st: @pstate, conv: conv_did) -> ty::t {
alt next(st) {
alt check next(st) {
'n' { ret ty::mk_nil(st.tcx); }
'z' { ret ty::mk_bot(st.tcx); }
'b' { ret ty::mk_bool(st.tcx); }
@ -185,7 +185,7 @@ fn parse_ty(st: @pstate, conv: conv_did) -> ty::t {
'u' { ret ty::mk_uint(st.tcx); }
'l' { ret ty::mk_float(st.tcx); }
'M' {
alt next(st) {
alt check next(st) {
'b' { ret ty::mk_mach_uint(st.tcx, ast::ty_u8); }
'w' { ret ty::mk_mach_uint(st.tcx, ast::ty_u16); }
'l' { ret ty::mk_mach_uint(st.tcx, ast::ty_u32); }
@ -269,7 +269,7 @@ fn parse_ty(st: @pstate, conv: conv_did) -> ty::t {
'Y' { ret ty::mk_type(st.tcx); }
'y' { ret ty::mk_send_type(st.tcx); }
'C' {
let ck = alt next(st) {
let ck = alt check next(st) {
'&' { ty::ck_block }
'@' { ty::ck_box }
'~' { ty::ck_uniq }
@ -355,7 +355,7 @@ fn parse_ty_fn(st: @pstate, conv: conv_did) -> ty::fn_ty {
assert (next(st) == '[');
let inputs: [ty::arg] = [];
while peek(st) != ']' {
let mode = alt peek(st) {
let mode = alt check peek(st) {
'&' { ast::by_mut_ref }
'-' { ast::by_move }
'+' { ast::by_copy }
@ -405,7 +405,7 @@ fn parse_bounds_data(data: @[u8], start: uint,
fn parse_bounds(st: @pstate, conv: conv_did) -> @[ty::param_bound] {
let bounds = [];
while true {
bounds += [alt next(st) {
bounds += [alt check next(st) {
'S' { ty::bound_send }
'C' { ty::bound_copy }
'I' { ty::bound_iface(parse_ty(st, conv)) }

View file

@ -27,7 +27,7 @@ fn check_expr(tcx: ty::ctxt, ex: @expr, &&s: (), v: visit::vt<()>) {
/* Check for exhaustiveness */
if mode == alt_exhaustive {
let arms = vec::concat(vec::filter_map(arms, unguarded_pat));
check_exhaustive(tcx, ex.span, expr_ty(tcx, scrut), arms);
check_exhaustive(tcx, ex.span, arms);
}
}
_ { }
@ -59,89 +59,136 @@ fn check_arms(tcx: ty::ctxt, arms: [arm]) {
}
}
fn raw_pat(p: @pat) -> @pat {
alt p.node {
pat_ident(_, some(s)) { raw_pat(s) }
_ { p }
}
}
// Precondition: patterns have been normalized
// (not checked statically yet)
fn check_exhaustive(tcx: ty::ctxt, sp:span, scrut_ty:ty::t, pats:[@pat]) {
let represented : [def_id] = [];
/* Determine the type of the scrutinee */
/* If it's not an enum, exit (bailing out on checking non-enum alts
for now) */
/* Otherwise, get the list of variants and make sure each one is
represented. Then recurse on the columns. */
fn check_exhaustive(tcx: ty::ctxt, sp: span, pats: [@pat]) {
if pats.len() == 0u {
tcx.sess.span_err(sp, "non-exhaustive patterns");
ret;
}
// If there a non-refutable pattern in the set, we're okay.
for pat in pats { if !is_refutable(tcx, pat) { ret; } }
let ty_def_id = alt ty::get(scrut_ty).struct {
ty_enum(id, _) { id }
_ { ret; } };
let variants = *enum_variants(tcx, ty_def_id);
for pat in pats {
if !is_refutable(tcx, pat) {
/* automatically makes this alt complete */ ret;
alt ty::get(ty::node_id_to_type(tcx, pats[0].id)).struct {
ty::ty_enum(id, _) {
check_exhaustive_enum(tcx, id, sp, pats);
}
ty::ty_box(_) {
check_exhaustive(tcx, sp, vec::filter_map(pats, {|p|
alt raw_pat(p).node { pat_box(sub) { some(sub) } _ { none } }
}));
}
ty::ty_uniq(_) {
check_exhaustive(tcx, sp, vec::filter_map(pats, {|p|
alt raw_pat(p).node { pat_uniq(sub) { some(sub) } _ { none } }
}));
}
ty::ty_tup(ts) {
let cols = vec::init_elt_mut(ts.len(), []);
for p in pats {
alt raw_pat(p).node {
pat_tup(sub) {
vec::iteri(sub) {|i, sp| cols[i] += [sp];}
}
_ {}
}
}
alt pat.node {
// want the def_id for the constructor
pat_enum(id,_) {
alt tcx.def_map.find(pat.id) {
some(def_variant(_, variant_def_id)) {
represented += [variant_def_id];
vec::iter(cols) {|col| check_exhaustive(tcx, sp, col); }
}
ty::ty_rec(fs) {
let cols = vec::init_elt(fs.len(), {mutable wild: false,
mutable pats: []});
for p in pats {
alt raw_pat(p).node {
pat_rec(sub, _) {
vec::iteri(fs) {|i, field|
alt vec::find(sub, {|pf| pf.ident == field.ident }) {
some(pf) { cols[i].pats += [pf.pat]; }
none { cols[i].wild = true; }
}
_ { tcx.sess.span_bug(pat.span, "check_exhaustive:
pat_tag not bound to a variant"); }
}
}
_ {}
}
_ { tcx.sess.span_bug(pat.span, "check_exhaustive: ill-typed \
pattern"); // we know this has enum type,
} // so anything else should be impossible
}
}
fn not_represented(v: [def_id], &&vinfo: variant_info) -> bool {
!vec::contains(v, vinfo.id)
}
// Could be more efficient (bitvectors?)
alt vec::find(variants, bind not_represented(represented,_)) {
some(bad) {
// complain
// TODO: give examples of cases that aren't covered
tcx.sess.note("Patterns not covered include:");
tcx.sess.note(bad.name);
tcx.sess.span_err(sp, "Non-exhaustive pattern");
}
_ {}
vec::iter(cols) {|col|
if !col.wild { check_exhaustive(tcx, sp, copy col.pats); }
}
}
ty::ty_bool {
let saw_true = false, saw_false = false;
for p in pats {
alt raw_pat(p).node {
pat_lit(@{node: expr_lit(@{node: lit_bool(b), _}), _}) {
if b { saw_true = true; }
else { saw_false = true; }
}
_ {}
}
}
if !saw_true { tcx.sess.span_err(
sp, "non-exhaustive bool patterns: true not covered"); }
if !saw_false { tcx.sess.span_err(
sp, "non-exhaustive bool patterns: false not covered"); }
}
ty::ty_nil {
let seen = vec::any(pats, {|p|
alt raw_pat(p).node {
pat_lit(@{node: expr_lit(@{node: lit_nil, _}), _}) { true }
_ { false }
}
});
if !seen { tcx.sess.span_err(sp, "non-exhaustive patterns"); }
}
// Literal patterns are always considered non-exhaustive
_ {
tcx.sess.span_err(sp, "non-exhaustive literal patterns");
}
}
// Otherwise, check subpatterns
// inefficient
for variant in variants {
// rows consists of the argument list for each pat that's an enum
let rows : [[@pat]] = [];
for pat in pats {
}
fn check_exhaustive_enum(tcx: ty::ctxt, enum_id: def_id, sp: span,
pats: [@pat]) {
let variants = enum_variants(tcx, enum_id);
let columns_by_variant = vec::map(*variants, {|v|
{mutable seen: false,
cols: vec::init_elt_mut(v.args.len(), [])}
});
for pat in pats {
let pat = raw_pat(pat);
alt tcx.def_map.get(pat.id) {
def_variant(_, id) {
let variant_idx =
option::get(vec::position(*variants, {|v| v.id == id}));
columns_by_variant[variant_idx].seen = true;
alt pat.node {
pat_enum(id, args) {
alt tcx.def_map.find(pat.id) {
some(def_variant(_,variant_id))
if variant_id == variant.id { rows += [args]; }
_ { }
}
}
_ {}
}
}
if check vec::is_not_empty(rows) {
let i = 0u;
for it in rows[0] {
let column = [it];
// Annoying -- see comment in
// tstate::states::find_pre_post_state_loop
check vec::is_not_empty(rows);
for row in vec::tail(rows) {
column += [row[i]];
pat_enum(_, args) {
vec::iteri(args) {|i, p|
columns_by_variant[variant_idx].cols[i] += [p];
}
check_exhaustive(tcx, sp, pat_ty(tcx, it), column);
i += 1u;
}
}
_ {}
}
}
_ {}
}
}
vec::iteri(columns_by_variant) {|i, cv|
if !cv.seen {
tcx.sess.span_err(sp, "non-exhaustive patterns: variant `" +
variants[i].name + "` not covered");
} else {
vec::iter(cv.cols) {|col| check_exhaustive(tcx, sp, col); }
}
// This shouldn't actually happen, since there were no
// irrefutable patterns if we got here.
else { cont; }
}
}

View file

@ -40,7 +40,7 @@ fn time(do_it: bool, what: str, thunk: fn()) {
fn merge_opts(attrs: [ast::attribute], cmd_opts: [(option, bool)]) ->
[(option, bool)] {
fn str_to_option(name: str) -> (option, bool) {
ret alt name {
ret alt check name {
"ctypes" { (ctypes, true) }
"no_ctypes" { (ctypes, false) }
}

View file

@ -1023,7 +1023,7 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
}
ast::expr_be(result) { word_nbsp(s, "be"); print_expr(s, result); }
ast::expr_log(lvl, lexp, expr) {
alt lvl {
alt check lvl {
1 { word_nbsp(s, "log"); print_expr(s, expr); }
0 { word_nbsp(s, "log_err"); print_expr(s, expr); }
2 {

View file

@ -68,7 +68,7 @@ fn run(lib_path: str, prog: str, args: [str],
let count = 2;
while count > 0 {
let stream = comm::recv(p);
alt stream {
alt check stream {
(1, s) {
outs = s;
}

View file

@ -60,9 +60,10 @@ pure fn is_false(v: t) -> bool { !v }
brief = "Parse logic value from `s`"
)]
pure fn from_str(s: str) -> t {
alt s {
alt check s {
"true" { true }
"false" { false }
_ { fail "'" + s + "' is not a valid boolean string"; }
}
}

View file

@ -2140,7 +2140,7 @@ mod tests {
fn test_chars_iter() {
let i = 0;
chars_iter("x\u03c0y") {|ch|
alt i {
alt check i {
0 { assert ch == 'x'; }
1 { assert ch == '\u03c0'; }
2 { assert ch == 'y'; }
@ -2156,7 +2156,7 @@ mod tests {
let i = 0;
bytes_iter("xyz") {|bb|
alt i {
alt check i {
0 { assert bb == 'x' as u8; }
1 { assert bb == 'y' as u8; }
2 { assert bb == 'z' as u8; }

View file

@ -166,7 +166,7 @@ Function: from_str
Parse logic value from `s`
*/
pure fn from_str(s: str) -> t {
alt s {
alt check s {
"none" { none }
"false" { four::false }
"true" { four::true }
@ -181,7 +181,7 @@ Convert `v` into a string
*/
pure fn to_str(v: t) -> str {
// FIXME replace with consts as soon as that works
alt v {
alt check v {
0u8 { "none" }
1u8 { "true" }
2u8 { "false" }
@ -265,7 +265,7 @@ mod tests {
}
fn to_tup(v: four::t) -> (bool, bool) {
alt v {
alt check v {
0u8 { (false, false) }
1u8 { (false, true) }
2u8 { (true, false) }

View file

@ -241,7 +241,7 @@ fn test_option_int() {
fn deserialize_0<S: deserializer>(s: S) -> option<int> {
s.read_enum("option") {||
s.read_enum_variant {|i|
alt i {
alt check i {
0u { none }
1u {
let v0 = s.read_enum_variant_arg(0u) {||

View file

@ -52,8 +52,12 @@ fn insert<K: copy, V: copy>(m: treemap<K, V>, k: K, v: V) {
// We have to name left and right individually, because
// otherwise the alias checker complains.
if k < kk {
alt m { @node(_, _, left, _) { insert(left, k, v); } }
} else { alt m { @node(_, _, _, right) { insert(right, k, v); } } }
alt check m { @node(_, _, left, _) { insert(left, k, v); } }
} else {
alt check m {
@node(_, _, _, right) { insert(right, k, v); }
}
}
}
}
}

View file

@ -137,7 +137,7 @@ Function: from_str
Parse logic value from `s`
*/
pure fn from_str(s: str) -> t {
alt s {
alt check s {
"unknown" { unknown }
"true" { tri::true }
"false" { tri::false }
@ -151,7 +151,7 @@ Convert `v` into a string
*/
pure fn to_str(v: t) -> str {
// FIXME replace with consts as soon as that works
alt v {
alt check v {
0u8 { "unknown" }
1u8 { "true" }
2u8 { "false" }

View file

@ -3,7 +3,7 @@
fn f() -> int {
// Make sure typestate doesn't interpret this alt expression
// as the function result
alt true { true { } };
alt check true { true { } };
}
fn main() { }

View file

@ -1,5 +1,5 @@
// -*- rust -*-
// error-pattern: Non-exhaustive pattern
// error-pattern: non-exhaustive patterns
enum t { a(u), b }
enum u { c, d }

View file

@ -1,5 +1,15 @@
// -*- rust -*-
// error-pattern: Non-exhaustive pattern
enum t { a, b, }
fn main() { let x = a; alt x { b { } } }
fn main() {
let x = a;
alt x { b { } } //! ERROR non-exhaustive patterns
alt true { //! ERROR non-exhaustive bool patterns
true {}
}
alt @some(10) { //! ERROR non-exhaustive patterns
@none {}
}
alt (2, 3, 4) { //! ERROR non-exhaustive literal patterns
(_, _, 4) {}
}
}

View file

@ -8,7 +8,7 @@ fn test2() -> int { let val = @0; { } *val }
fn test3() {
let regs = @{mutable eax: 0};
alt true { true { } }
alt check true { true { } }
(*regs).eax = 1;
}
@ -20,13 +20,13 @@ fn test6() -> bool { { } (true || false) && true }
fn test7() -> uint {
let regs = @0;
alt true { true { } }
alt check true { true { } }
(*regs < 2) as uint
}
fn test8() -> int {
let val = @0;
alt true {
alt check true {
true { }
}
if *val < 1 {
@ -36,11 +36,11 @@ fn test8() -> int {
}
}
fn test9() { let regs = @mutable 0; alt true { true { } } *regs += 1; }
fn test9() { let regs = @mutable 0; alt check true { true { } } *regs += 1; }
fn test10() -> int {
let regs = @mutable [0];
alt true { true { } }
alt check true { true { } }
(*regs)[0]
}

View file

@ -10,8 +10,8 @@ fn if_semi() -> int { if true { f() } else { f() }; -1 }
fn if_nosemi() -> int { (if true { 0 } else { 0 }) - 1 }
fn alt_semi() -> int { alt true { true { f() } }; -1 }
fn alt_semi() -> int { alt check true { true { f() } }; -1 }
fn alt_no_semi() -> int { (alt true { true { 0 } }) - 1 }
fn alt_no_semi() -> int { (alt check true { true { 0 } }) - 1 }
fn stmt() { { f() }; -1; }

View file

@ -4,7 +4,7 @@ fn test_box() {
@0;
}
fn test_str() {
let res = alt false { true { "happy" } };
let res = alt check false { true { "happy" } };
assert res == "happy";
}
fn main() {

View file

@ -1,3 +1,3 @@
// n.b. This was only ever failing with optimization disabled.
fn a() -> int { alt ret 1 { 2 { 3 } } }
fn a() -> int { alt check ret 1 { 2 { 3 } } }
fn main() { a(); }

View file

@ -1,7 +1,7 @@
fn altlit(f: int) -> int {
alt f {
alt check f {
10 { #debug("case 10"); ret 20; }
11 { #debug("case 11"); ret 22; }
}

View file

@ -7,7 +7,7 @@ fn main() {
6u to 7u { fail "shouldn't match range"; }
_ {}
}
alt 5u {
alt check 5u {
1u { fail "should match non-first range"; }
2u to 6u {}
}

View file

@ -1,7 +1,7 @@
// Issue #53
fn main() {
alt "test" { "not-test" { fail; } "test" { } _ { fail; } }
alt check "test" { "not-test" { fail; } "test" { } _ { fail; } }
enum t { tag1(str), tag2, }
@ -13,9 +13,9 @@ fn main() {
_ { fail; }
}
let x = alt "a" { "a" { 1 } "b" { 2 } };
let x = alt check "a" { "a" { 1 } "b" { 2 } };
assert (x == 1);
alt "a" { "a" { } "b" { } }
alt check "a" { "a" { } "b" { } }
}

View file

@ -1,6 +1,6 @@
// Check that issue #954 stays fixed
fn main() {
alt -1 { -1 {} }
alt check -1 { -1 {} }
assert 1-1 == 0;
}

View file

@ -4,10 +4,13 @@
// -*- rust -*-
// Tests for alt as expressions resulting in boxed types
fn test_box() { let res = alt true { true { @100 } }; assert (*res == 100); }
fn test_box() {
let res = alt check true { true { @100 } };
assert (*res == 100);
}
fn test_str() {
let res = alt true { true { "happy" } };
let res = alt check true { true { "happy" } };
assert (res == "happy");
}

View file

@ -5,7 +5,7 @@
type compare<T> = fn@(@T, @T) -> bool;
fn test_generic<T>(expected: @T, eq: compare<T>) {
let actual: @T = alt true { true { expected } };
let actual: @T = alt check true { true { expected } };
assert (eq(expected, actual));
}

View file

@ -5,7 +5,7 @@
type compare<T> = fn@(T, T) -> bool;
fn test_generic<T: copy>(expected: T, eq: compare<T>) {
let actual: T = alt true { true { expected } };
let actual: T = alt check true { true { expected } };
assert (eq(expected, actual));
}

View file

@ -4,7 +4,7 @@
type compare<T> = fn@(~T, ~T) -> bool;
fn test_generic<T: copy>(expected: ~T, eq: compare<T>) {
let actual: ~T = alt true { true { expected } };
let actual: ~T = alt check true { true { expected } };
assert (eq(expected, actual));
}

View file

@ -5,7 +5,7 @@
type compare<T> = fn@(T, T) -> bool;
fn test_generic<T: copy>(expected: T, eq: compare<T>) {
let actual: T = alt true { true { expected } };
let actual: T = alt check true { true { expected } };
assert (eq(expected, actual));
}

View file

@ -5,7 +5,7 @@
type compare<T> = fn@(T, T) -> bool;
fn test_generic<T: copy>(expected: T, eq: compare<T>) {
let actual: T = alt true { true { expected } };
let actual: T = alt check true { true { expected } };
assert (eq(expected, actual));
}

View file

@ -5,7 +5,7 @@
// Tests for alt as expressions resulting in structural types
fn test_rec() {
let rs = alt true { true { {i: 100} } };
let rs = alt check true { true { {i: 100} } };
assert (rs == {i: 100});
}

View file

@ -4,6 +4,9 @@
// -*- rust -*-
// Tests for alt as expressions resulting in boxed types
fn test_box() { let res = alt true { true { ~100 } }; assert (*res == 100); }
fn test_box() {
let res = alt check true { true { ~100 } };
assert (*res == 100);
}
fn main() { test_box(); }

View file

@ -30,7 +30,7 @@ fn log_cont() { do { log(error, cont); } while false }
fn ret_ret() -> int { ret (ret 2) + 3; }
fn ret_guard() {
alt 2 {
alt check 2 {
x if (ret) { x; }
}
}