Skip to content

Commit

Permalink
Merge pull request #3858 from pcwalton/struct-like-typeck
Browse files Browse the repository at this point in the history
rustc: Typecheck, privacy check, and borrow check struct-like enum variants. r=tjc
  • Loading branch information
pcwalton committed Oct 25, 2012
2 parents 75947b3 + 588ea59 commit 65ee0e1
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 77 deletions.
39 changes: 32 additions & 7 deletions src/rustc/middle/mem_categorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ impl &mem_categorization_ctxt {
}

let base_cmt = self.cat_expr(base);
self.cat_field(expr, base_cmt, f_name)
self.cat_field(expr, base_cmt, f_name, expr.id)
}

ast::expr_index(base, _) => {
Expand Down Expand Up @@ -632,9 +632,14 @@ impl &mem_categorization_ctxt {
}
}

fn cat_field<N:ast_node>(node: N, base_cmt: cmt,
f_name: ast::ident) -> cmt {
let f_mutbl = match field_mutbl(self.tcx, base_cmt.ty, f_name) {
/// The `field_id` parameter is the ID of the enclosing expression or
/// pattern. It is used to determine which variant of an enum is in use.
fn cat_field<N:ast_node>(node: N,
base_cmt: cmt,
f_name: ast::ident,
field_id: ast::node_id) -> cmt {
let f_mutbl = match field_mutbl(self.tcx, base_cmt.ty, f_name,
field_id) {
Some(f_mutbl) => f_mutbl,
None => {
self.tcx.sess.span_bug(
Expand Down Expand Up @@ -851,15 +856,15 @@ impl &mem_categorization_ctxt {
ast::pat_rec(field_pats, _) => {
// {f1: p1, ..., fN: pN}
for field_pats.each |fp| {
let cmt_field = self.cat_field(fp.pat, cmt, fp.ident);
let cmt_field = self.cat_field(fp.pat, cmt, fp.ident, pat.id);
self.cat_pattern(cmt_field, fp.pat, op);
}
}

ast::pat_struct(_, field_pats, _) => {
// {f1: p1, ..., fN: pN}
for field_pats.each |fp| {
let cmt_field = self.cat_field(fp.pat, cmt, fp.ident);
let cmt_field = self.cat_field(fp.pat, cmt, fp.ident, pat.id);
self.cat_pattern(cmt_field, fp.pat, op);
}
}
Expand Down Expand Up @@ -998,9 +1003,13 @@ impl &mem_categorization_ctxt {
}
}

/// The node_id here is the node of the expression that references the field.
/// This function looks it up in the def map in case the type happens to be
/// an enum to determine which variant is in use.
fn field_mutbl(tcx: ty::ctxt,
base_ty: ty::t,
f_name: ast::ident) -> Option<ast::mutability> {
f_name: ast::ident,
node_id: ast::node_id) -> Option<ast::mutability> {
// Need to refactor so that records/class fields can be treated uniformly.
match ty::get(base_ty).sty {
ty::ty_rec(fields) => {
Expand All @@ -1021,6 +1030,22 @@ fn field_mutbl(tcx: ty::ctxt,
}
}
}
ty::ty_enum(*) => {
match tcx.def_map.get(node_id) {
ast::def_variant(_, variant_id) => {
for ty::lookup_class_fields(tcx, variant_id).each |fld| {
if fld.ident == f_name {
let m = match fld.mutability {
ast::class_mutable => ast::m_mutbl,
ast::class_immutable => ast::m_imm
};
return Some(m);
}
}
}
_ => {}
}
}
_ => { }
}

Expand Down
25 changes: 25 additions & 0 deletions src/rustc/middle/privacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,31 @@ fn check_crate(tcx: ty::ctxt, method_map: &method_map, crate: @ast::crate) {
}
}
}
ty_enum(enum_id, _) => {
if enum_id.crate != local_crate ||
!privileged_items.contains(
&enum_id.node) {
match tcx.def_map.find(pattern.id) {
Some(def_variant(_, variant_id)) => {
for fields.each |field| {
debug!("(privacy checking) \
checking field in \
struct variant pattern");
check_field(pattern.span,
variant_id,
field.ident);
}
}
_ => {
tcx.sess.span_bug(pattern.span,
~"resolve didn't \
map enum struct \
pattern to a \
variant def");
}
}
}
}
_ => {
tcx.sess.span_bug(pattern.span,
~"struct pattern didn't have \
Expand Down
210 changes: 140 additions & 70 deletions src/rustc/middle/typeck/check/alt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,139 @@ fn check_pat_variant(pcx: pat_ctxt, pat: @ast::pat, path: @ast::path,
}
}

/// `path` is the AST path item naming the type of this struct.
/// `fields` is the field patterns of the struct pattern.
/// `class_fields` describes the type of each field of the struct.
/// `class_id` is the ID of the struct.
/// `substitutions` are the type substitutions applied to this struct type
/// (e.g. K,V in HashMap<K,V>).
/// `etc` is true if the pattern said '...' and false otherwise.
fn check_struct_pat_fields(pcx: pat_ctxt,
span: span,
path: @ast::path,
fields: ~[ast::field_pat],
class_fields: ~[ty::field_ty],
class_id: ast::def_id,
substitutions: &ty::substs,
etc: bool) {
let tcx = pcx.fcx.ccx.tcx;

// Index the class fields.
let field_map = std::map::HashMap();
for class_fields.eachi |i, class_field| {
field_map.insert(class_field.ident, i);
}

// Typecheck each field.
let found_fields = std::map::HashMap();
for fields.each |field| {
match field_map.find(field.ident) {
Some(index) => {
let class_field = class_fields[index];
let field_type = ty::lookup_field_type(tcx,
class_id,
class_field.id,
substitutions);
check_pat(pcx, field.pat, field_type);
found_fields.insert(index, ());
}
None => {
let name = pprust::path_to_str(path, tcx.sess.intr());
tcx.sess.span_err(span,
fmt!("struct `%s` does not have a field
named `%s`", name,
tcx.sess.str_of(field.ident)));
}
}
}

// Report an error if not all the fields were specified.
if !etc {
for class_fields.eachi |i, field| {
if found_fields.contains_key(i) {
loop;
}
tcx.sess.span_err(span,
fmt!("pattern does not mention field `%s`",
tcx.sess.str_of(field.ident)));
}
}
}

fn check_struct_pat(pcx: pat_ctxt, pat_id: ast::node_id, span: span,
expected: ty::t, path: @ast::path,
fields: ~[ast::field_pat], etc: bool,
class_id: ast::def_id, substitutions: &ty::substs) {
let fcx = pcx.fcx;
let tcx = pcx.fcx.ccx.tcx;

let class_fields = ty::lookup_class_fields(tcx, class_id);

// Check to ensure that the struct is the one specified.
match tcx.def_map.find(pat_id) {
Some(ast::def_class(supplied_def_id))
if supplied_def_id == class_id => {
// OK.
}
Some(ast::def_class(*)) | Some(ast::def_variant(*)) => {
let name = pprust::path_to_str(path, tcx.sess.intr());
tcx.sess.span_err(span,
fmt!("mismatched types: expected `%s` but \
found `%s`",
fcx.infcx().ty_to_str(expected),
name));
}
_ => {
tcx.sess.span_bug(span, ~"resolve didn't write in class");
}
}
// Forbid pattern-matching structs with destructors.
if ty::has_dtor(tcx, class_id) {
tcx.sess.span_err(span, ~"deconstructing struct not allowed in \
pattern (it has a destructor)");
}
check_struct_pat_fields(pcx, span, path, fields, class_fields, class_id,
substitutions, etc);
}
fn check_struct_like_enum_variant_pat(pcx: pat_ctxt,
pat_id: ast::node_id,
span: span,
expected: ty::t,
path: @ast::path,
fields: ~[ast::field_pat],
etc: bool,
enum_id: ast::def_id,
substitutions: &ty::substs) {
let fcx = pcx.fcx;
let tcx = pcx.fcx.ccx.tcx;
// Find the variant that was specified.
match tcx.def_map.find(pat_id) {
Some(ast::def_variant(found_enum_id, variant_id))
if found_enum_id == enum_id => {
// Get the struct fields from this struct-like enum variant.
let class_fields = ty::lookup_class_fields(tcx, variant_id);
check_struct_pat_fields(pcx, span, path, fields, class_fields,
variant_id, substitutions, etc);
}
Some(ast::def_class(*)) | Some(ast::def_variant(*)) => {
let name = pprust::path_to_str(path, tcx.sess.intr());
tcx.sess.span_err(span,
fmt!("mismatched types: expected `%s` but \
found `%s`",
fcx.infcx().ty_to_str(expected),
name));
}
_ => {
tcx.sess.span_bug(span, ~"resolve didn't write in variant");
}
}
}
// Pattern checking is top-down rather than bottom-up so that bindings get
// their types immediately.
fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) {
Expand Down Expand Up @@ -306,13 +439,16 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) {
}
ast::pat_struct(path, fields, etc) => {
// Grab the class data that we care about.
let class_fields, class_id, substitutions;
let structure = structure_of(fcx, pat.span, expected);
match structure {
ty::ty_class(cid, ref substs) => {
class_id = cid;
substitutions = substs;
class_fields = ty::lookup_class_fields(tcx, class_id);
check_struct_pat(pcx, pat.id, pat.span, expected, path,
fields, etc, cid, substs);
}
ty::ty_enum(eid, ref substs) => {
check_struct_like_enum_variant_pat(
pcx, pat.id, pat.span, expected, path, fields, etc, eid,
substs);
}
_ => {
// XXX: This should not be fatal.
Expand All @@ -323,72 +459,6 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) {
}
}

// Check to ensure that the struct is the one specified.
match tcx.def_map.get(pat.id) {
ast::def_class(supplied_def_id)
if supplied_def_id == class_id => {
// OK.
}
ast::def_class(*) => {
let name = pprust::path_to_str(path, tcx.sess.intr());
tcx.sess.span_err(pat.span,
fmt!("mismatched types: expected `%s` but \
found `%s`",
fcx.infcx().ty_to_str(expected),
name));
}
_ => {
tcx.sess.span_bug(pat.span, ~"resolve didn't write in class");
}
}
// Forbid pattern-matching structs with destructors.
if ty::has_dtor(tcx, class_id) {
tcx.sess.span_err(pat.span, ~"deconstructing struct not allowed \
in pattern (it has a destructor)");
}
// Index the class fields.
let field_map = std::map::HashMap();
for class_fields.eachi |i, class_field| {
field_map.insert(class_field.ident, i);
}
// Typecheck each field.
let found_fields = std::map::HashMap();
for fields.each |field| {
match field_map.find(field.ident) {
Some(index) => {
let class_field = class_fields[index];
let field_type = ty::lookup_field_type(tcx,
class_id,
class_field.id,
substitutions);
check_pat(pcx, field.pat, field_type);
found_fields.insert(index, ());
}
None => {
let name = pprust::path_to_str(path, tcx.sess.intr());
tcx.sess.span_err(pat.span,
fmt!("struct `%s` does not have a field
named `%s`", name,
tcx.sess.str_of(field.ident)));
}
}
}

// Report an error if not all the fields were specified.
if !etc {
for class_fields.eachi |i, field| {
if found_fields.contains_key(i) {
loop;
}
tcx.sess.span_err(pat.span,
fmt!("pattern does not mention field `%s`",
tcx.sess.str_of(field.ident)));
}
}

// Finally, write in the type.
fcx.write_ty(pat.id, expected);
}
Expand Down

0 comments on commit 65ee0e1

Please sign in to comment.