Skip to content

Commit

Permalink
Rewrite exhaustiveness checker
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
marijnh committed Feb 15, 2012
1 parent 4b63826 commit 67cc89f
Show file tree
Hide file tree
Showing 32 changed files with 192 additions and 124 deletions.
8 changes: 4 additions & 4 deletions src/comp/metadata/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down Expand Up @@ -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 }
Expand All @@ -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 }
}
Expand All @@ -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"; }
Expand Down
10 changes: 5 additions & 5 deletions src/comp/metadata/tydecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,15 @@ 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); }
'i' { ret ty::mk_int(st.tcx); }
'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); }
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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)) }
Expand Down
189 changes: 118 additions & 71 deletions src/comp/middle/check_alt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
_ { }
Expand Down Expand Up @@ -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. */

let ty_def_id = alt ty::get(scrut_ty).struct {
ty_enum(id, _) { id }
_ { ret; } };
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 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"); }
}
}
_ {}
}
}
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; }
}
_ {}
}
_ { 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");
}
_ {}
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]; }
_ { }
}
}
_ {}
pat_enum(_, args) {
vec::iteri(args) {|i, p|
columns_by_variant[variant_idx].cols[i] += [p];
}
}
_ {}
}
}
_ {}
}
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]];
}
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; }
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/comp/middle/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
Expand Down
2 changes: 1 addition & 1 deletion src/comp/syntax/print/pprust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/compiletest/procsrv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion src/libcore/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"; }
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/libcore/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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'; }
Expand All @@ -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; }
Expand Down
6 changes: 3 additions & 3 deletions src/libstd/four.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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" }
Expand Down Expand Up @@ -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) }
Expand Down
2 changes: 1 addition & 1 deletion src/libstd/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {||
Expand Down
Loading

0 comments on commit 67cc89f

Please sign in to comment.