From 57cd6b3e3f0fc3d1805a167dfa2b4d5afc2f43a5 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 24 Oct 2012 18:47:59 -0700 Subject: [PATCH] rustc: Translate and check exhaustiveness of struct-like enum variant patterns. r=nmatsakis --- src/rustc/middle/check_alt.rs | 57 +++++++++++----- src/rustc/middle/pat_util.rs | 2 +- src/rustc/middle/trans/alt.rs | 66 ++++++++++++++++++- .../struct-like-enum-nonexhaustive.rs | 14 ++++ .../run-pass/struct-like-variant-match.rs | 31 +++++++++ 5 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 src/test/compile-fail/struct-like-enum-nonexhaustive.rs create mode 100644 src/test/run-pass/struct-like-variant-match.rs diff --git a/src/rustc/middle/check_alt.rs b/src/rustc/middle/check_alt.rs index fc040ecc4cd..7ed7829cf34 100644 --- a/src/rustc/middle/check_alt.rs +++ b/src/rustc/middle/check_alt.rs @@ -234,8 +234,14 @@ fn pat_ctor_id(tcx: ty::ctxt, p: @pat) -> Option { pat_range(lo, hi) => { Some(range(eval_const_expr(tcx, lo), eval_const_expr(tcx, hi))) } - pat_box(_) | pat_uniq(_) | pat_rec(_, _) | pat_tup(_) | pat_region(*) | pat_struct(*) => { + match tcx.def_map.find(pat.id) { + Some(def_variant(_, id)) => Some(variant(id)), + _ => Some(single) + } + } + pat_box(_) | pat_uniq(_) | pat_rec(_, _) | pat_tup(_) | + pat_region(*) => { Some(single) } } @@ -366,25 +372,44 @@ fn specialize(tcx: ty::ctxt, r: ~[@pat], ctor_id: ctor, arity: uint, Some(vec::append(args, vec::tail(r))) } pat_struct(_, flds, _) => { - // Grab the class data that we care about. - let class_fields, class_id; - match ty::get(left_ty).sty { - ty::ty_class(cid, _) => { - class_id = cid; - class_fields = ty::lookup_class_fields(tcx, class_id); + // Is this a struct or an enum variant? + match tcx.def_map.get(r0.id) { + def_variant(_, variant_id) => { + if variant(variant_id) == ctor_id { + // XXX: Is this right? --pcw + let args = flds.map(|ty_f| { + match vec::find(flds, |f| f.ident == ty_f.ident) { + Some(f) => f.pat, + _ => wild() + } + }); + Some(vec::append(args, vec::tail(r))) + } else { + None + } } _ => { - tcx.sess.span_bug(r0.span, ~"struct pattern didn't resolve \ - to a struct"); + // Grab the class data that we care about. + let class_fields, class_id; + match ty::get(left_ty).sty { + ty::ty_class(cid, _) => { + class_id = cid; + class_fields = ty::lookup_class_fields(tcx, class_id); + } + _ => { + tcx.sess.span_bug(r0.span, ~"struct pattern didn't \ + resolve to a struct"); + } + } + let args = vec::map(class_fields, |class_field| { + match vec::find(flds, |f| f.ident == class_field.ident ) { + Some(f) => f.pat, + _ => wild() + } + }); + Some(vec::append(args, vec::tail(r))) } } - let args = vec::map(class_fields, |class_field| { - match vec::find(flds, |f| f.ident == class_field.ident ) { - Some(f) => f.pat, - _ => wild() - } - }); - Some(vec::append(args, vec::tail(r))) } pat_tup(args) => Some(vec::append(args, vec::tail(r))), pat_box(a) | pat_uniq(a) | pat_region(a) => diff --git a/src/rustc/middle/pat_util.rs b/src/rustc/middle/pat_util.rs index 006065988b9..48ebda9a67e 100644 --- a/src/rustc/middle/pat_util.rs +++ b/src/rustc/middle/pat_util.rs @@ -24,7 +24,7 @@ fn pat_id_map(dm: resolve::DefMap, pat: @pat) -> PatIdMap { fn pat_is_variant(dm: resolve::DefMap, pat: @pat) -> bool { match pat.node { pat_enum(_, _) => true, - pat_ident(_, _, None) => match dm.find(pat.id) { + pat_ident(_, _, None) | pat_struct(*) => match dm.find(pat.id) { Some(def_variant(_, _)) => true, _ => false }, diff --git a/src/rustc/middle/trans/alt.rs b/src/rustc/middle/trans/alt.rs index c1b499567ed..b3776e49994 100644 --- a/src/rustc/middle/trans/alt.rs +++ b/src/rustc/middle/trans/alt.rs @@ -369,6 +369,30 @@ fn enter_default(bcx: block, dm: DefMap, m: &[@Match/&r], } } +// nmatsakis: what does enter_opt do? +// in trans/alt +// trans/alt.rs is like stumbling around in a dark cave +// pcwalton: the enter family of functions adjust the set of +// patterns as needed +// yeah, at some point I kind of achieved some level of +// understanding +// anyhow, they adjust the patterns given that something of that +// kind has been found +// pcwalton: ok, right, so enter_XXX() adjusts the patterns, as I +// said +// enter_match() kind of embodies the generic code +// it is provided with a function that tests each pattern to see +// if it might possibly apply and so forth +// so, if you have a pattern like {a: _, b: _, _} and one like _ +// then _ would be expanded to (_, _) +// one spot for each of the sub-patterns +// enter_opt() is one of the more complex; it covers the fallible +// cases +// enter_rec_or_struct() or enter_tuple() are simpler, since they +// are infallible patterns +// so all patterns must either be records (resp. tuples) or +// wildcards + fn enter_opt(bcx: block, m: &[@Match/&r], opt: &Opt, col: uint, variant_size: uint, val: ValueRef) -> ~[@Match/&r] @@ -406,6 +430,35 @@ fn enter_opt(bcx: block, m: &[@Match/&r], opt: &Opt, col: uint, ast::pat_range(l1, l2) => { if opt_eq(tcx, &range(l1, l2), opt) {Some(~[])} else {None} } + ast::pat_struct(_, field_pats, _) => { + if opt_eq(tcx, &variant_opt(tcx, p.id), opt) { + // Look up the struct variant ID. + let struct_id; + match tcx.def_map.get(p.id) { + ast::def_variant(_, found_struct_id) => { + struct_id = found_struct_id; + } + _ => { + tcx.sess.span_bug(p.span, ~"expected enum \ + variant def"); + } + } + + // Reorder the patterns into the same order they were + // specified in the struct definition. Also fill in + // unspecified fields with dummy. + let reordered_patterns = dvec::DVec(); + for ty::lookup_class_fields(tcx, struct_id).each |field| { + match field_pats.find(|p| p.ident == field.ident) { + None => reordered_patterns.push(dummy), + Some(fp) => reordered_patterns.push(fp.pat) + } + } + Some(dvec::unwrap(move reordered_patterns)) + } else { + None + } + } _ => { assert_is_binding_or_wild(bcx, p); Some(vec::from_elem(variant_size, dummy)) @@ -599,12 +652,19 @@ fn extract_variant_args(bcx: block, pat_id: ast::node_id, return {vals: args, bcx: bcx}; } -fn collect_record_or_struct_fields(m: &[@Match], col: uint) -> ~[ast::ident] { +// NB: This function does not collect fields from struct-like enum variants. +fn collect_record_or_struct_fields(bcx: block, m: &[@Match], col: uint) -> + ~[ast::ident] { let mut fields: ~[ast::ident] = ~[]; for vec::each(m) |br| { match br.pats[col].node { ast::pat_rec(fs, _) => extend(&mut fields, fs), - ast::pat_struct(_, fs, _) => extend(&mut fields, fs), + ast::pat_struct(_, fs, _) => { + match ty::get(node_id_type(bcx, br.pats[col].id)).sty { + ty::ty_class(*) => extend(&mut fields, fs), + _ => () + } + } _ => () } } @@ -939,7 +999,7 @@ fn compile_submatch(bcx: block, root_pats_as_necessary(bcx, m, col, val); - let rec_fields = collect_record_or_struct_fields(m, col); + let rec_fields = collect_record_or_struct_fields(bcx, m, col); if rec_fields.len() > 0 { let pat_ty = node_id_type(bcx, pat_id); do expr::with_field_tys(tcx, pat_ty, None) |_has_dtor, field_tys| { diff --git a/src/test/compile-fail/struct-like-enum-nonexhaustive.rs b/src/test/compile-fail/struct-like-enum-nonexhaustive.rs new file mode 100644 index 00000000000..f910161f87a --- /dev/null +++ b/src/test/compile-fail/struct-like-enum-nonexhaustive.rs @@ -0,0 +1,14 @@ +enum A { + B { x: Option }, + C +} + +fn main() { + let x = B { x: Some(3) }; + match x { //~ ERROR non-exhaustive patterns + C => {} + B { x: None } => {} + } +} + + diff --git a/src/test/run-pass/struct-like-variant-match.rs b/src/test/run-pass/struct-like-variant-match.rs new file mode 100644 index 00000000000..16c63255fb7 --- /dev/null +++ b/src/test/run-pass/struct-like-variant-match.rs @@ -0,0 +1,31 @@ +enum Foo { + Bar { + x: int, + y: int + }, + Baz { + x: float, + y: float + } +} + +fn f(x: &Foo) { + match *x { + Baz { x: x, y: y } => { + assert x == 1.0; + assert y == 2.0; + } + Bar { y: y, x: x } => { + assert x == 1; + assert y == 2; + } + } +} + +fn main() { + let x = Bar { x: 1, y: 2 }; + f(&x); + let y = Baz { x: 1.0, y: 2.0 }; + f(&y); +} +