rustc: Implement functional record update for structs

This commit is contained in:
Patrick Walton 2012-08-06 13:15:40 -07:00
parent 1e3143b34e
commit bff512a90f
10 changed files with 127 additions and 46 deletions

View file

@ -347,9 +347,7 @@ enum expr_ {
expr_mac(mac),
// A struct literal expression.
//
// XXX: Add functional record update.
expr_struct(@path, ~[field]),
expr_struct(@path, ~[field], option<@expr>),
// A vector literal constructed from one repeated element.
expr_repeat(@expr /* element */, @expr /* count */, mutability)

View file

@ -478,8 +478,10 @@ fn noop_fold_expr(e: expr_, fld: ast_fold) -> expr_ {
fld.fold_expr(e)),
expr_assert(e) => expr_assert(fld.fold_expr(e)),
expr_mac(mac) => expr_mac(fold_mac(mac)),
expr_struct(path, fields) => {
expr_struct(fld.fold_path(path), vec::map(fields, fold_field))
expr_struct(path, fields, maybe_expr) => {
expr_struct(fld.fold_path(path),
vec::map(fields, fold_field),
option::map(maybe_expr, |x| fld.fold_expr(x)))
}
}
}

View file

@ -912,18 +912,27 @@ class parser {
self.bump();
let mut fields = ~[];
vec::push(fields, self.parse_field(token::COLON));
while self.token != token::RBRACE {
while self.token != token::RBRACE &&
!self.is_keyword(~"with") {
self.expect(token::COMMA);
if self.token == token::RBRACE {
if self.token == token::RBRACE ||
self.is_keyword(~"with") {
// Accept an optional trailing comma.
break;
}
vec::push(fields, self.parse_field(token::COLON));
}
let base;
if self.eat_keyword(~"with") {
base = some(self.parse_expr());
} else {
base = none;
}
hi = pth.span.hi;
self.expect(token::RBRACE);
ex = expr_struct(pth, fields);
ex = expr_struct(pth, fields, base);
return self.mk_pexpr(lo, hi, ex);
}
}

View file

@ -973,11 +973,20 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
}
word(s.s, ~"}");
}
ast::expr_struct(path, fields) => {
ast::expr_struct(path, fields, wth) => {
print_path(s, path, true);
word(s.s, ~"{");
commasep_cmnt(s, consistent, fields, print_field, get_span);
word(s.s, ~",");
alt wth {
some(expr) => {
if vec::len(fields) > 0u { space(s.s); }
ibox(s, indent_unit);
word_space(s, ~"with");
print_expr(s, expr);
end(s);
}
_ => word(s.s, ~",")
}
word(s.s, ~"}");
}
ast::expr_tup(exprs) => {

View file

@ -364,9 +364,10 @@ fn visit_expr<E>(ex: @expr, e: E, v: vt<E>) {
for flds.each |f| { v.visit_expr(f.node.expr, e, v); }
visit_expr_opt(base, e, v);
}
expr_struct(p, flds) => {
expr_struct(p, flds, base) => {
visit_path(p, e, v);
for flds.each |f| { v.visit_expr(f.node.expr, e, v); }
visit_expr_opt(base, e, v);
}
expr_tup(elts) => for elts.each |el| { v.visit_expr(el, e, v); }
expr_call(callee, args, _) => {

View file

@ -1071,7 +1071,8 @@ class liveness {
}
}
expr_struct(_, fields) => {
expr_struct(_, fields, with_expr) => {
let succ = self.propagate_through_opt_expr(with_expr, succ);
do fields.foldr(succ) |field, succ| {
self.propagate_through_expr(field.node.expr, succ)
}

View file

@ -4272,7 +4272,7 @@ class Resolver {
visitor);
}
expr_struct(path, _) => {
expr_struct(path, _, _) => {
// Resolve the path to the structure it goes to.
//
// XXX: We might want to support explicit type parameters in

View file

@ -3392,7 +3392,8 @@ fn trans_rec(bcx: block, fields: ~[ast::field],
}
fn trans_struct(block_context: block, span: span, fields: ~[ast::field],
id: ast::node_id, dest: dest) -> block {
base: option<@ast::expr>, id: ast::node_id, dest: dest)
-> block {
let _instruction_context = block_context.insn_ctxt(~"trans_struct");
let mut block_context = block_context;
@ -3433,6 +3434,18 @@ fn trans_struct(block_context: block, span: span, fields: ~[ast::field],
}
}
// If the class has a destructor, our GEP is a little more
// complicated.
fn get_field(block_context: block, dest_address: ValueRef,
class_id: ast::def_id, index: uint) -> ValueRef {
if ty::ty_dtor(block_context.tcx(), class_id).is_some() {
return GEPi(block_context,
GEPi(block_context, dest_address, ~[0, 1]),
~[0, index]);
}
return GEPi(block_context, dest_address, ~[0, index]);
}
// Now translate each field.
let mut temp_cleanups = ~[];
for fields.each |field| {
@ -3455,16 +3468,7 @@ fn trans_struct(block_context: block, span: span, fields: ~[ast::field],
}
}
// If the class has a destructor, our GEP is a little more
// complicated.
let dest;
if ty::ty_dtor(block_context.tcx(), class_id).is_some() {
dest = GEPi(block_context,
GEPi(block_context, dest_address, ~[0, 1]),
~[0, index]);
} else {
dest = GEPi(block_context, dest_address, ~[0, index]);
}
let dest = get_field(block_context, dest_address, class_id, index);
block_context = trans_expr_save_in(block_context,
field.node.expr,
@ -3476,6 +3480,42 @@ fn trans_struct(block_context: block, span: span, fields: ~[ast::field],
vec::push(temp_cleanups, dest);
}
match base {
some(base_expr) => {
let { bcx: bcx, val: llbasevalue } =
trans_temp_expr(block_context, base_expr);
block_context = bcx;
// Copy over inherited fields.
for class_fields.eachi |i, class_field| {
let exists = do vec::any(fields) |provided_field| {
str::eq(provided_field.node.ident, class_field.ident)
};
if exists {
again;
}
let lldestfieldvalue = get_field(block_context,
dest_address,
class_id,
i);
let llbasefieldvalue = GEPi(block_context,
llbasevalue,
~[0, i]);
let field_type = ty::lookup_field_type(block_context.tcx(),
class_id,
class_field.id,
substitutions);
let llbasefieldvalue = load_if_immediate(block_context,
llbasefieldvalue,
field_type);
block_context = copy_val(block_context, INIT,
lldestfieldvalue, llbasefieldvalue,
field_type);
}
}
none => ()
}
// Now revoke the cleanups, as we pass responsibility for the data
// structure onto the caller.
for temp_cleanups.each |temp_cleanup| {
@ -3633,8 +3673,8 @@ fn trans_expr(bcx: block, e: @ast::expr, dest: dest) -> block {
ast::expr_rec(args, base) => {
return trans_rec(bcx, args, base, e.id, dest);
}
ast::expr_struct(_, fields) => {
return trans_struct(bcx, e.span, fields, e.id, dest);
ast::expr_struct(_, fields, base) => {
return trans_struct(bcx, e.span, fields, base, e.id, dest);
}
ast::expr_tup(args) => { return trans_tup(bcx, args, dest); }
ast::expr_vstore(e, v) => {

View file

@ -1699,7 +1699,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
}
}
}
ast::expr_struct(path, fields) => {
ast::expr_struct(path, fields, base_expr) => {
// Resolve the path.
let class_id;
alt tcx.def_map.find(id) {
@ -1804,27 +1804,36 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
}
}
// Make sure the programmer specified all the fields.
assert fields_found <= class_fields.len();
if fields_found < class_fields.len() {
let mut missing_fields = ~[];
for class_fields.each |class_field| {
let name = *class_field.ident;
let (_, seen) = class_field_map.get(name);
if !seen {
vec::push(missing_fields,
~"`" + name + ~"`");
match base_expr {
none => {
// Make sure the programmer specified all the fields.
assert fields_found <= class_fields.len();
if fields_found < class_fields.len() {
let mut missing_fields = ~[];
for class_fields.each |class_field| {
let name = *class_field.ident;
let (_, seen) = class_field_map.get(name);
if !seen {
vec::push(missing_fields,
~"`" + name + ~"`");
}
}
tcx.sess.span_err(expr.span,
fmt!{"missing field%s: %s",
if missing_fields.len() == 1 {
~""
} else {
~"s"
},
str::connect(missing_fields,
~", ")});
}
}
tcx.sess.span_err(expr.span,
fmt!{"missing field%s: %s",
if missing_fields.len() == 1 {
~""
} else {
~"s"
},
str::connect(missing_fields, ~", ")});
some(base_expr) => {
// Just check the base expression.
check_expr(fcx, base_expr, some(struct_type));
}
}
// Write in the resulting type.

View file

@ -0,0 +1,12 @@
struct Foo {
x: int;
y: int;
}
fn main() {
let a = Foo { x: 1, y: 2 };
let b = Foo { x: 3 with a };
let c = Foo { x: 4, with a };
io::println(fmt!("%? %?", b, c));
}