diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index 1e43ae219bab..daf3e20f469c 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -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) diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs index ec6d7010aa15..71c23ff4fa6d 100644 --- a/src/libsyntax/fold.rs +++ b/src/libsyntax/fold.rs @@ -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))) } } } diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index a6c4e6c41319..71bd87981bc8 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -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); } } diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs index c81b8b5335f1..eca571b9ccd3 100644 --- a/src/libsyntax/print/pprust.rs +++ b/src/libsyntax/print/pprust.rs @@ -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) => { diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs index 8d80f9663a31..1c92f26cabe3 100644 --- a/src/libsyntax/visit.rs +++ b/src/libsyntax/visit.rs @@ -364,9 +364,10 @@ fn visit_expr(ex: @expr, e: E, v: vt) { 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, _) => { diff --git a/src/rustc/middle/liveness.rs b/src/rustc/middle/liveness.rs index fe343d76cc92..38942b407507 100644 --- a/src/rustc/middle/liveness.rs +++ b/src/rustc/middle/liveness.rs @@ -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) } diff --git a/src/rustc/middle/resolve3.rs b/src/rustc/middle/resolve3.rs index fc7f079e1724..9f17e38ba540 100644 --- a/src/rustc/middle/resolve3.rs +++ b/src/rustc/middle/resolve3.rs @@ -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 diff --git a/src/rustc/middle/trans/base.rs b/src/rustc/middle/trans/base.rs index 6df45f75adbe..6fecdead0485 100644 --- a/src/rustc/middle/trans/base.rs +++ b/src/rustc/middle/trans/base.rs @@ -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) => { diff --git a/src/rustc/middle/typeck/check.rs b/src/rustc/middle/typeck/check.rs index d4efdbf7b011..c875ac8c3864 100644 --- a/src/rustc/middle/typeck/check.rs +++ b/src/rustc/middle/typeck/check.rs @@ -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. diff --git a/src/test/run-pass/functional-struct-update.rs b/src/test/run-pass/functional-struct-update.rs new file mode 100644 index 000000000000..50f746a436a0 --- /dev/null +++ b/src/test/run-pass/functional-struct-update.rs @@ -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)); +} +