rustc: Implement translation of pattern matching for tuple structs and unit-like structs. r=nmatsakis

This commit is contained in:
Patrick Walton 2012-10-30 15:53:06 -07:00
parent 106f9976ab
commit 4165edff22
13 changed files with 259 additions and 81 deletions

View File

@ -441,7 +441,8 @@ impl gather_loan_ctxt {
alt_id: ast::node_id) {
do self.bccx.cat_pattern(discr_cmt, root_pat) |cmt, pat| {
match pat.node {
ast::pat_ident(bm, _, _) if !self.pat_is_variant(pat) => {
ast::pat_ident(bm, _, _)
if !self.pat_is_variant_or_struct(pat) => {
match bm {
ast::bind_by_value | ast::bind_by_move => {
// copying does not borrow anything, so no check
@ -492,8 +493,8 @@ impl gather_loan_ctxt {
}
}
fn pat_is_variant(&self, pat: @ast::pat) -> bool {
pat_util::pat_is_variant(self.bccx.tcx.def_map, pat)
fn pat_is_variant_or_struct(&self, pat: @ast::pat) -> bool {
pat_util::pat_is_variant_or_struct(self.bccx.tcx.def_map, pat)
}
}

View File

@ -7,7 +7,7 @@ use syntax::codemap::span;
use std::map::HashMap;
export pat_binding_ids, pat_bindings, pat_id_map, PatIdMap;
export pat_is_variant, pat_is_binding_or_wild;
export pat_is_variant_or_struct, pat_is_binding_or_wild;
type PatIdMap = std::map::HashMap<ident, node_id>;
@ -21,20 +21,21 @@ fn pat_id_map(dm: resolve::DefMap, pat: @pat) -> PatIdMap {
return map;
}
fn pat_is_variant(dm: resolve::DefMap, pat: @pat) -> bool {
fn pat_is_variant_or_struct(dm: resolve::DefMap, pat: @pat) -> bool {
match pat.node {
pat_enum(_, _) => true,
pat_ident(_, _, None) | pat_struct(*) => match dm.find(pat.id) {
Some(def_variant(_, _)) => true,
pat_enum(_, _) | pat_ident(_, _, None) | pat_struct(*) => {
match dm.find(pat.id) {
Some(def_variant(*)) | Some(def_class(*)) => true,
_ => false
}
}
_ => false
},
_ => false
}
}
fn pat_is_binding_or_wild(dm: resolve::DefMap, pat: @pat) -> bool {
match pat.node {
pat_ident(*) => !pat_is_variant(dm, pat),
pat_ident(*) => !pat_is_variant_or_struct(dm, pat),
pat_wild => true,
_ => false
}
@ -44,7 +45,8 @@ fn pat_bindings(dm: resolve::DefMap, pat: @pat,
it: fn(binding_mode, node_id, span, @path)) {
do walk_pat(pat) |p| {
match p.node {
pat_ident(binding_mode, pth, _) if !pat_is_variant(dm, p) => {
pat_ident(binding_mode, pth, _)
if !pat_is_variant_or_struct(dm, p) => {
it(binding_mode, p.id, p.span, pth);
}
_ => {}

View File

@ -288,10 +288,10 @@ impl AllowCapturingSelfFlag : cmp::Eq {
pure fn ne(other: &AllowCapturingSelfFlag) -> bool { !self.eq(other) }
}
enum EnumVariantOrConstResolution {
FoundEnumVariant(def),
enum BareIdentifierPatternResolution {
FoundStructOrEnumVariant(def),
FoundConst,
EnumVariantOrConstNotFound
BareIdentifierPatternUnresolved
}
// Specifies how duplicates should be handled when adding a child item if
@ -4187,28 +4187,31 @@ impl Resolver {
if !path.global && path.idents.len() == 1u => {
// The meaning of pat_ident with no type parameters
// depends on whether an enum variant with that name is in
// scope. The probing lookup has to be careful not to emit
// spurious errors. Only matching patterns (match) can
// match nullary variants. For binding patterns (let),
// matching such a variant is simply disallowed (since
// it's rarely what you want).
// depends on whether an enum variant or unit-like struct
// with that name is in scope. The probing lookup has to
// be careful not to emit spurious errors. Only matching
// patterns (match) can match nullary variants or
// unit-like structs. For binding patterns (let), matching
// such a value is simply disallowed (since it's rarely
// what you want).
let ident = path.idents[0];
match self.resolve_enum_variant_or_const(ident) {
FoundEnumVariant(def) if mode == RefutableMode => {
match self.resolve_bare_identifier_pattern(ident) {
FoundStructOrEnumVariant(def)
if mode == RefutableMode => {
debug!("(resolving pattern) resolving `%s` to \
enum variant",
struct or enum variant",
self.session.str_of(ident));
self.record_def(pattern.id, def);
}
FoundEnumVariant(_) => {
FoundStructOrEnumVariant(_) => {
self.session.span_err(pattern.span,
fmt!("declaration of `%s` \
shadows an enum \
that's in scope",
variant or unit-like \
struct in scope",
self.session
.str_of(ident)));
}
@ -4218,7 +4221,7 @@ impl Resolver {
conflicts with a constant \
in scope");
}
EnumVariantOrConstNotFound => {
BareIdentifierPatternUnresolved => {
debug!("(resolving pattern) binding `%s`",
self.session.str_of(ident));
@ -4349,13 +4352,11 @@ impl Resolver {
}
}
fn resolve_enum_variant_or_const(name: ident)
-> EnumVariantOrConstResolution {
fn resolve_bare_identifier_pattern(name: ident)
-> BareIdentifierPatternResolution {
match self.resolve_item_in_lexical_scope(self.current_module,
name,
ValueNS) {
name,
ValueNS) {
Success(target) => {
match target.bindings.value_def {
None => {
@ -4364,14 +4365,14 @@ impl Resolver {
}
Some(def) => {
match def.def {
def @ def_variant(*) => {
return FoundEnumVariant(def);
def @ def_variant(*) | def @ def_class(*) => {
return FoundStructOrEnumVariant(def);
}
def_const(*) => {
return FoundConst;
}
_ => {
return EnumVariantOrConstNotFound;
return BareIdentifierPatternUnresolved;
}
}
}
@ -4383,7 +4384,7 @@ impl Resolver {
}
Failed => {
return EnumVariantOrConstNotFound;
return BareIdentifierPatternUnresolved;
}
}
}

View File

@ -154,16 +154,25 @@ use util::common::indenter;
fn macros() { include!("macros.rs"); } // FIXME(#3114): Macro import/export.
// An option identifying a literal: either a unit-like struct or an
// expression.
enum Lit {
UnitLikeStructLit(ast::node_id), // the node ID of the pattern
ExprLit(@ast::expr)
}
// An option identifying a branch (either a literal, a enum variant or a
// range)
enum Opt {
lit(@ast::expr),
lit(Lit),
var(/* disr val */int, /* variant dids */{enm: def_id, var: def_id}),
range(@ast::expr, @ast::expr)
}
fn opt_eq(tcx: ty::ctxt, a: &Opt, b: &Opt) -> bool {
match (*a, *b) {
(lit(a), lit(b)) => const_eval::compare_lit_exprs(tcx, a, b) == 0,
(lit(ExprLit(a)), lit(ExprLit(b))) =>
const_eval::compare_lit_exprs(tcx, a, b) == 0,
(lit(UnitLikeStructLit(a)), lit(UnitLikeStructLit(b))) => a == b,
(range(a1, a2), range(b1, b2)) => {
const_eval::compare_lit_exprs(tcx, a1, b1) == 0 &&
const_eval::compare_lit_exprs(tcx, a2, b2) == 0
@ -182,10 +191,15 @@ fn trans_opt(bcx: block, o: &Opt) -> opt_result {
let ccx = bcx.ccx();
let mut bcx = bcx;
match *o {
lit(lit_expr) => {
lit(ExprLit(lit_expr)) => {
let datumblock = expr::trans_to_datum(bcx, lit_expr);
return single_result(datumblock.to_result());
}
lit(UnitLikeStructLit(pat_id)) => {
let struct_ty = ty::node_id_to_type(bcx.tcx(), pat_id);
let datumblock = datum::scratch_datum(bcx, struct_ty, true);
return single_result(datumblock.to_result(bcx));
}
var(disr_val, _) => {
return single_result(rslt(bcx, C_int(ccx, disr_val)));
}
@ -197,12 +211,23 @@ fn trans_opt(bcx: block, o: &Opt) -> opt_result {
}
fn variant_opt(tcx: ty::ctxt, pat_id: ast::node_id) -> Opt {
let vdef = ast_util::variant_def_ids(tcx.def_map.get(pat_id));
let variants = ty::enum_variants(tcx, vdef.enm);
for vec::each(*variants) |v| {
if vdef.var == v.id { return var(v.disr_val, vdef); }
match tcx.def_map.get(pat_id) {
ast::def_variant(enum_id, var_id) => {
let variants = ty::enum_variants(tcx, enum_id);
for vec::each(*variants) |v| {
if var_id == v.id {
return var(v.disr_val, {enm: enum_id, var: var_id});
}
}
core::util::unreachable();
}
ast::def_class(_) => {
return lit(UnitLikeStructLit(pat_id));
}
_ => {
tcx.sess.bug(~"non-variant or struct in variant_opt()");
}
}
core::util::unreachable();
}
enum TransBindingMode {
@ -328,7 +353,7 @@ fn enter_match(bcx: block, dm: DefMap, m: &[@Match/&r],
let self = br.pats[col];
match self.node {
ast::pat_ident(_, path, None) => {
if !pat_is_variant(dm, self) {
if !pat_is_variant_or_struct(dm, self) {
let binding_info =
br.data.bindings_map.get(path_to_ident(path));
Store(bcx, val, binding_info.llmatch);
@ -363,7 +388,8 @@ fn enter_default(bcx: block, dm: DefMap, m: &[@Match/&r],
match p.node {
ast::pat_wild | ast::pat_rec(_, _) | ast::pat_tup(_) |
ast::pat_struct(*) => Some(~[]),
ast::pat_ident(_, _, None) if !pat_is_variant(dm, p) => Some(~[]),
ast::pat_ident(_, _, None)
if !pat_is_variant_or_struct(dm, p) => Some(~[]),
_ => None
}
}
@ -417,7 +443,8 @@ fn enter_opt(bcx: block, m: &[@Match/&r], opt: &Opt, col: uint,
None
}
}
ast::pat_ident(_, _, None) if pat_is_variant(tcx.def_map, p) => {
ast::pat_ident(_, _, None)
if pat_is_variant_or_struct(tcx.def_map, p) => {
if opt_eq(tcx, &variant_opt(tcx, p.id), opt) {
Some(~[])
} else {
@ -425,7 +452,7 @@ fn enter_opt(bcx: block, m: &[@Match/&r], opt: &Opt, col: uint,
}
}
ast::pat_lit(l) => {
if opt_eq(tcx, &lit(l), opt) {Some(~[])} else {None}
if opt_eq(tcx, &lit(ExprLit(l)), opt) {Some(~[])} else {None}
}
ast::pat_range(l1, l2) => {
if opt_eq(tcx, &range(l1, l2), opt) {Some(~[])} else {None}
@ -522,6 +549,29 @@ fn enter_tup(bcx: block, dm: DefMap, m: &[@Match/&r],
}
}
fn enter_tuple_struct(bcx: block, dm: DefMap, m: &[@Match/&r], col: uint,
val: ValueRef, n_elts: uint)
-> ~[@Match/&r]
{
debug!("enter_tuple_struct(bcx=%s, m=%s, col=%u, val=%?)",
bcx.to_str(),
matches_to_str(bcx, m),
col,
bcx.val_str(val));
let _indenter = indenter();
let dummy = @{id: 0, node: ast::pat_wild, span: dummy_sp()};
do enter_match(bcx, dm, m, col, val) |p| {
match p.node {
ast::pat_enum(_, Some(elts)) => Some(elts),
_ => {
assert_is_binding_or_wild(bcx, p);
Some(vec::from_elem(n_elts, dummy))
}
}
}
}
fn enter_box(bcx: block, dm: DefMap, m: &[@Match/&r],
col: uint, val: ValueRef)
-> ~[@Match/&r]
@ -597,6 +647,9 @@ fn enter_region(bcx: block, dm: DefMap, m: &[@Match/&r],
}
}
// Returns the options in one column of matches. An option is something that
// needs to be conditionally matched at runtime; for example, the discriminant
// on a set of enum variants or a literal.
fn get_options(ccx: @crate_ctxt, m: &[@Match], col: uint) -> ~[Opt] {
fn add_to_set(tcx: ty::ctxt, set: &DVec<Opt>, val: Opt) {
if set.any(|l| opt_eq(tcx, l, &val)) {return;}
@ -606,18 +659,40 @@ fn get_options(ccx: @crate_ctxt, m: &[@Match], col: uint) -> ~[Opt] {
let found = DVec();
for vec::each(m) |br| {
let cur = br.pats[col];
if pat_is_variant(ccx.tcx.def_map, cur) {
add_to_set(ccx.tcx, &found, variant_opt(ccx.tcx, cur.id));
} else {
match cur.node {
ast::pat_lit(l) => {
add_to_set(ccx.tcx, &found, lit(l));
}
ast::pat_range(l1, l2) => {
add_to_set(ccx.tcx, &found, range(l1, l2));
}
_ => ()
match cur.node {
ast::pat_lit(l) => {
add_to_set(ccx.tcx, &found, lit(ExprLit(l)));
}
ast::pat_ident(*) => {
// This is one of: an enum variant, a unit-like struct, or a
// variable binding.
match ccx.tcx.def_map.find(cur.id) {
Some(ast::def_variant(*)) => {
add_to_set(ccx.tcx, &found,
variant_opt(ccx.tcx, cur.id));
}
Some(ast::def_class(*)) => {
add_to_set(ccx.tcx, &found,
lit(UnitLikeStructLit(cur.id)));
}
_ => {}
}
}
ast::pat_enum(*) | ast::pat_struct(*) => {
// This could be one of: a tuple-like enum variant, a
// struct-like enum variant, or a struct.
match ccx.tcx.def_map.find(cur.id) {
Some(ast::def_variant(*)) => {
add_to_set(ccx.tcx, &found,
variant_opt(ccx.tcx, cur.id));
}
_ => {}
}
}
ast::pat_range(l1, l2) => {
add_to_set(ccx.tcx, &found, range(l1, l2));
}
_ => {}
}
}
return dvec::unwrap(move found);
@ -733,6 +808,21 @@ fn any_tup_pat(m: &[@Match], col: uint) -> bool {
any_pat!(m, ast::pat_tup(_))
}
fn any_tuple_struct_pat(bcx: block, m: &[@Match], col: uint) -> bool {
vec::any(m, |br| {
let pat = br.pats[col];
match pat.node {
ast::pat_enum(_, Some(_)) => {
match bcx.tcx().def_map.find(pat.id) {
Some(ast::def_class(*)) => true,
_ => false
}
}
_ => false
}
})
}
type mk_fail = fn@() -> BasicBlockRef;
fn pick_col(m: &[@Match]) -> uint {
@ -1028,6 +1118,29 @@ fn compile_submatch(bcx: block,
return;
}
if any_tuple_struct_pat(bcx, m, col) {
let struct_ty = node_id_type(bcx, pat_id);
let struct_element_count;
match ty::get(struct_ty).sty {
ty::ty_class(struct_id, _) => {
struct_element_count =
ty::lookup_class_fields(tcx, struct_id).len();
}
_ => {
ccx.sess.bug(~"non-struct type in tuple struct pattern");
}
}
let llstructvals = vec::from_fn(
struct_element_count, |i| GEPi(bcx, val, struct_field(i)));
compile_submatch(bcx,
enter_tuple_struct(bcx, dm, m, col, val,
struct_element_count),
vec::append(llstructvals, vals_left),
chk);
return;
}
// Unbox in case of a box field
if any_box_pat(m, col) {
let llbox = Load(bcx, val);
@ -1316,7 +1429,7 @@ fn bind_irrefutable_pat(bcx: block, pat: @ast::pat, val: ValueRef,
// Necessary since bind_irrefutable_pat is called outside trans_alt
match pat.node {
ast::pat_ident(_, _,inner) => {
if pat_is_variant(bcx.tcx().def_map, pat) {
if pat_is_variant_or_struct(bcx.tcx().def_map, pat) {
return bcx;
}
@ -1335,15 +1448,39 @@ fn bind_irrefutable_pat(bcx: block, pat: @ast::pat, val: ValueRef,
for inner.each |inner_pat| {
bcx = bind_irrefutable_pat(bcx, *inner_pat, val, true);
}
}
}
ast::pat_enum(_, sub_pats) => {
let pat_def = ccx.tcx.def_map.get(pat.id);
let vdefs = ast_util::variant_def_ids(pat_def);
let args = extract_variant_args(bcx, pat.id, vdefs, val);
for sub_pats.each |sub_pat| {
for vec::eachi(args.vals) |i, argval| {
bcx = bind_irrefutable_pat(bcx, sub_pat[i],
*argval, make_copy);
match bcx.tcx().def_map.find(pat.id) {
Some(ast::def_variant(*)) => {
let pat_def = ccx.tcx.def_map.get(pat.id);
let vdefs = ast_util::variant_def_ids(pat_def);
let args = extract_variant_args(bcx, pat.id, vdefs, val);
for sub_pats.each |sub_pat| {
for vec::eachi(args.vals) |i, argval| {
bcx = bind_irrefutable_pat(bcx,
sub_pat[i],
*argval,
make_copy);
}
}
}
Some(ast::def_class(*)) => {
match sub_pats {
None => {
// This is a unit-like struct. Nothing to do here.
}
Some(elems) => {
// This is the tuple variant case.
for vec::eachi(elems) |i, elem| {
let fldptr = GEPi(bcx, val, struct_field(i));
bcx = bind_irrefutable_pat(bcx, *elem, fldptr,
make_copy);
}
}
}
}
_ => {
// Nothing to do here.
}
}
}

View File

@ -1893,12 +1893,14 @@ fn trans_struct_def(ccx: @crate_ctxt, struct_def: @ast::struct_def,
// If this is a tuple-like struct, translate the constructor.
match struct_def.ctor_id {
None => {}
Some(ctor_id) => {
// We only need to translate a constructor if there are fields;
// otherwise this is a unit-like struct.
Some(ctor_id) if struct_def.fields.len() > 0 => {
let llfndecl = get_item_val(ccx, ctor_id);
trans_tuple_struct(ccx, struct_def.fields, ctor_id, None,
llfndecl);
}
Some(_) | None => {}
}
}

View File

@ -664,6 +664,11 @@ fn trans_def_dps_unadjusted(bcx: block, ref_expr: @ast::expr,
return bcx;
}
}
ast::def_class(*) => {
// Nothing to do here.
// XXX: May not be true in the case of classes with destructors.
return bcx;
}
_ => {
bcx.tcx().sess.span_bug(ref_expr.span, fmt!(
"Non-DPS def %? referened by %s",

View File

@ -2980,7 +2980,7 @@ fn expr_kind(tcx: ctxt,
ast::expr_path(*) => {
match resolve_expr(tcx, expr) {
ast::def_fn(*) | ast::def_static_method(*) |
ast::def_variant(*) => RvalueDpsExpr,
ast::def_variant(*) | ast::def_class(*) => RvalueDpsExpr,
// Note: there is actually a good case to be made that
// def_args, particularly those of immediate type, ought to

View File

@ -48,7 +48,7 @@ use syntax::visit;
use metadata::csearch;
use util::common::{block_query, loop_query};
use syntax::codemap::span;
use pat_util::{pat_is_variant, pat_id_map, PatIdMap};
use pat_util::{pat_id_map, PatIdMap};
use middle::ty;
use middle::ty::{arg, field, node_type_table, mk_nil, ty_param_bounds_and_ty};
use middle::ty::{ty_param_substs_and_ty, vstore_uniq};

View File

@ -376,7 +376,8 @@ fn check_fn(ccx: @crate_ctxt,
let visit_pat = fn@(p: @ast::pat, &&e: (), v: visit::vt<()>) {
match p.node {
ast::pat_ident(_, path, _)
if !pat_util::pat_is_variant(fcx.ccx.tcx.def_map, p) => {
if !pat_util::pat_is_variant_or_struct(fcx.ccx.tcx.def_map,
p) => {
assign(p.span, p.id, None);
debug!("Pattern binding %s is assigned to %s",
tcx.sess.str_of(path.idents[0]),
@ -467,10 +468,6 @@ fn check_struct(ccx: @crate_ctxt, struct_def: @ast::struct_def,
for struct_def.methods.each |m| {
check_method(ccx, *m, self_ty, local_def(id));
}
// Check that there's at least one field
if struct_def.fields.len() < 1u {
ccx.tcx.sess.span_err(span, ~"a struct must have at least one field");
}
// Check that the class is instantiable
check_instantiable(ccx.tcx, span, id);
}

View File

@ -1,6 +1,6 @@
use syntax::print::pprust;
use syntax::ast_util::{walk_pat};
use pat_util::{pat_is_variant};
use pat_util::{pat_is_variant_or_struct};
fn check_alt(fcx: @fn_ctxt,
expr: @ast::expr,
@ -74,7 +74,7 @@ fn check_legality_of_move_bindings(fcx: @fn_ctxt,
if !any_by_move { return; } // pointless micro-optimization
for pats.each |pat| {
do walk_pat(*pat) |p| {
if !pat_is_variant(def_map, p) {
if !pat_is_variant_or_struct(def_map, p) {
match p.node {
ast::pat_ident(ast::bind_by_move, _, sub) => {
// check legality of moving out of the enum
@ -391,7 +391,8 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) {
}
fcx.write_ty(pat.id, b_ty);
}
ast::pat_ident(bm, name, sub) if !pat_is_variant(tcx.def_map, pat) => {
ast::pat_ident(bm, name, sub)
if !pat_is_variant_or_struct(tcx.def_map, pat) => {
let vid = lookup_local(fcx, pat.span, pat.id);
let mut typ = ty::mk_var(tcx, vid);

View File

@ -0,0 +1,10 @@
struct Foo(int, int);
fn main() {
let x = Foo(1, 2);
let Foo(y, z) = x;
io::println(fmt!("%d %d", y, z));
assert y == 1;
assert z == 2;
}

View File

@ -0,0 +1,13 @@
struct Foo(int, int);
fn main() {
let x = Foo(1, 2);
match x {
Foo(a, b) => {
assert a == 1;
assert b == 2;
io::println(fmt!("%d %d", a, b));
}
}
}

View File

@ -0,0 +1,9 @@
struct Foo;
fn main() {
let x: Foo = Foo;
match x {
Foo => { io::println("hi"); }
}
}