From 588ea599927d62a04c12ccca87196ca415b86274 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 24 Oct 2012 17:04:00 -0700 Subject: [PATCH] rustc: Typecheck, privacy check, and borrow check struct-like enum variants --- src/rustc/middle/mem_categorization.rs | 39 ++++- src/rustc/middle/privacy.rs | 25 +++ src/rustc/middle/typeck/check/alt.rs | 210 ++++++++++++++++--------- 3 files changed, 197 insertions(+), 77 deletions(-) diff --git a/src/rustc/middle/mem_categorization.rs b/src/rustc/middle/mem_categorization.rs index e82892f3d1c33..8ee9adc4e2f3b 100644 --- a/src/rustc/middle/mem_categorization.rs +++ b/src/rustc/middle/mem_categorization.rs @@ -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, _) => { @@ -632,9 +632,14 @@ impl &mem_categorization_ctxt { } } - fn cat_field(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(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( @@ -851,7 +856,7 @@ 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); } } @@ -859,7 +864,7 @@ impl &mem_categorization_ctxt { 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); } } @@ -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 { + f_name: ast::ident, + node_id: ast::node_id) -> Option { // Need to refactor so that records/class fields can be treated uniformly. match ty::get(base_ty).sty { ty::ty_rec(fields) => { @@ -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); + } + } + } + _ => {} + } + } _ => { } } diff --git a/src/rustc/middle/privacy.rs b/src/rustc/middle/privacy.rs index acf56ad81e965..dfdd354116912 100644 --- a/src/rustc/middle/privacy.rs +++ b/src/rustc/middle/privacy.rs @@ -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 \ diff --git a/src/rustc/middle/typeck/check/alt.rs b/src/rustc/middle/typeck/check/alt.rs index caace6051982e..a8308bb1b3c24 100644 --- a/src/rustc/middle/typeck/check/alt.rs +++ b/src/rustc/middle/typeck/check/alt.rs @@ -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). +/// `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) { @@ -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. @@ -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); }