about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTim Chevalier <chevalier@alum.wellesley.edu>2012-10-08 09:00:23 -0700
committerTim Chevalier <chevalier@alum.wellesley.edu>2012-10-08 09:00:23 -0700
commit7bdab1e4a45bf734e4b25a2d58f4ee21ee5c9fdc (patch)
treeb24c818211cffeb1f2b8cc7bf7bc31fe3eecfdc9
parent79603f573e504163db7b5c2afa0917c27e3f98ed (diff)
downloadrust-7bdab1e4a45bf734e4b25a2d58f4ee21ee5c9fdc.tar.gz
rust-7bdab1e4a45bf734e4b25a2d58f4ee21ee5c9fdc.zip
Revert "remove ctor from ast"
This reverts commit ed3689d57c988e1dd477930d957c4308c37d1a64.
-rw-r--r--src/libsyntax/ast.rs9
-rw-r--r--src/libsyntax/ast_map.rs20
-rw-r--r--src/libsyntax/ast_util.rs13
-rw-r--r--src/libsyntax/fold.rs19
-rw-r--r--src/libsyntax/parse/parser.rs65
-rw-r--r--src/libsyntax/print/pprust.rs12
-rw-r--r--src/libsyntax/visit.rs33
-rw-r--r--src/rustc/metadata/decoder.rs6
-rw-r--r--src/rustc/metadata/encoder.rs22
-rw-r--r--src/rustc/middle/astencode.rs23
-rw-r--r--src/rustc/middle/borrowck/check_loans.rs60
-rw-r--r--src/rustc/middle/borrowck/gather_loans.rs10
-rw-r--r--src/rustc/middle/kind.rs6
-rw-r--r--src/rustc/middle/liveness.rs202
-rw-r--r--src/rustc/middle/region.rs20
-rw-r--r--src/rustc/middle/resolve.rs79
-rw-r--r--src/rustc/middle/trans/base.rs79
-rw-r--r--src/rustc/middle/trans/common.rs5
-rw-r--r--src/rustc/middle/trans/debuginfo.rs4
-rw-r--r--src/rustc/middle/trans/inline.rs4
-rw-r--r--src/rustc/middle/trans/monomorphize.rs13
-rw-r--r--src/rustc/middle/trans/reachable.rs11
-rw-r--r--src/rustc/middle/trans/type_use.rs3
-rw-r--r--src/rustc/middle/ty.rs5
-rw-r--r--src/rustc/middle/typeck/astconv.rs2
-rw-r--r--src/rustc/middle/typeck/check.rs16
-rw-r--r--src/rustc/middle/typeck/check/alt.rs2
-rw-r--r--src/rustc/middle/typeck/collect.rs24
-rw-r--r--src/test/compile-fail/issue-2590.rs2
-rw-r--r--src/test/compile-fail/regions-glb-free-free.rs2
30 files changed, 651 insertions, 120 deletions
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index ced64daa8d6..1c279f81cc3 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -135,7 +135,7 @@ enum def {
               @def,     // closed over def
               node_id,  // expr node that creates the closure
               node_id), // id for the block/body of the closure expr
-    def_class(def_id),
+    def_class(def_id, bool /* has constructor */),
     def_typaram_binder(node_id), /* class, impl or trait that has ty params */
     def_region(node_id),
     def_label(node_id)
@@ -235,9 +235,9 @@ impl def : cmp::Eq {
                     _ => false
                 }
             }
-            def_class(e0a) => {
+            def_class(e0a, e1a) => {
                 match (*other) {
-                    def_class(e0b) => e0a == e0b,
+                    def_class(e0b, e1b) => e0a == e0b && e1a == e1b,
                     _ => false
                 }
             }
@@ -1462,6 +1462,8 @@ type struct_def = {
     fields: ~[@struct_field], /* fields */
     methods: ~[@method],    /* methods */
     /* (not including ctor or dtor) */
+    /* ctor is optional, and will soon go away */
+    ctor: Option<class_ctor>,
     /* dtor is optional */
     dtor: Option<class_dtor>
 };
@@ -1561,6 +1563,7 @@ enum inlined_item {
     ii_item(@item),
     ii_method(def_id /* impl id */, @method),
     ii_foreign(@foreign_item),
+    ii_ctor(class_ctor, ident, ~[ty_param], def_id /* parent id */),
     ii_dtor(class_dtor, ident, ~[ty_param], def_id /* parent id */)
 }
 
diff --git a/src/libsyntax/ast_map.rs b/src/libsyntax/ast_map.rs
index 8555ceed2db..d05c6eadaf6 100644
--- a/src/libsyntax/ast_map.rs
+++ b/src/libsyntax/ast_map.rs
@@ -71,6 +71,9 @@ enum ast_node {
     // order they are introduced.
     node_arg(arg, uint),
     node_local(uint),
+    // Constructor for a class
+    // def_id is parent id
+    node_ctor(ident, ~[ty_param], @class_ctor, def_id, @path),
     // Destructor for a class
     node_dtor(~[ty_param], @class_dtor, def_id, @path),
     node_block(blk),
@@ -129,7 +132,7 @@ fn map_decoded_item(diag: span_handler,
     // don't decode and instantiate the impl, but just the method, we have to
     // add it to the table now:
     match ii {
-      ii_item(*) | ii_dtor(*) => { /* fallthrough */ }
+      ii_item(*) | ii_ctor(*) | ii_dtor(*) => { /* fallthrough */ }
       ii_foreign(i) => {
         cx.map.insert(i.id, node_foreign_item(i, foreign_abi_rust_intrinsic,
                                              @path));
@@ -152,6 +155,18 @@ fn map_fn(fk: visit::fn_kind, decl: fn_decl, body: blk,
         cx.local_id += 1u;
     }
     match fk {
+      visit::fk_ctor(nm, attrs, tps, self_id, parent_id) => {
+          let ct = @{node: {id: id,
+                            attrs: attrs,
+                            self_id: self_id,
+                            dec: /* FIXME (#2543) */ copy decl,
+                            body: /* FIXME (#2543) */ copy body},
+                    span: sp};
+          cx.map.insert(id, node_ctor(/* FIXME (#2543) */ copy nm,
+                                      /* FIXME (#2543) */ copy tps,
+                                      ct, parent_id,
+                                      @/* FIXME (#2543) */ copy cx.path));
+      }
       visit::fk_dtor(tps, attrs, self_id, parent_id) => {
           let dt = @{node: {id: id, attrs: attrs, self_id: self_id,
                      body: /* FIXME (#2543) */ copy body}, span: sp};
@@ -367,6 +382,9 @@ fn node_id_to_str(map: map, id: node_id, itr: @ident_interner) -> ~str {
       Some(node_local(_)) => { // add more info here
         fmt!("local (id=%?)", id)
       }
+      Some(node_ctor(*)) => { // add more info here
+        fmt!("node_ctor (id=%?)", id)
+      }
       Some(node_dtor(*)) => { // add more info here
         fmt!("node_dtor (id=%?)", id)
       }
diff --git a/src/libsyntax/ast_util.rs b/src/libsyntax/ast_util.rs
index 9e44c42a702..47cbdb7ac6c 100644
--- a/src/libsyntax/ast_util.rs
+++ b/src/libsyntax/ast_util.rs
@@ -57,7 +57,7 @@ pure fn def_id_of_def(d: def) -> def_id {
       def_fn(id, _) | def_static_method(id, _) | def_mod(id) |
       def_foreign_mod(id) | def_const(id) |
       def_variant(_, id) | def_ty(id) | def_ty_param(id, _) |
-      def_use(id) | def_class(id) => {
+      def_use(id) | def_class(id, _) => {
         id
       }
       def_arg(id, _) | def_local(id, _) | def_self(id) |
@@ -339,6 +339,7 @@ impl inlined_item: inlined_item_utils {
           ii_item(i) => /* FIXME (#2543) */ copy i.ident,
           ii_foreign(i) => /* FIXME (#2543) */ copy i.ident,
           ii_method(_, m) => /* FIXME (#2543) */ copy m.ident,
+          ii_ctor(_, nm, _, _) => /* FIXME (#2543) */ copy nm,
           ii_dtor(_, nm, _, _) => /* FIXME (#2543) */ copy nm
         }
     }
@@ -348,6 +349,7 @@ impl inlined_item: inlined_item_utils {
           ii_item(i) => i.id,
           ii_foreign(i) => i.id,
           ii_method(_, m) => m.id,
+          ii_ctor(ctor, _, _, _) => ctor.node.id,
           ii_dtor(dtor, _, _, _) => dtor.node.id
         }
     }
@@ -357,6 +359,9 @@ impl inlined_item: inlined_item_utils {
           ii_item(i) => v.visit_item(i, e, v),
           ii_foreign(i) => v.visit_foreign_item(i, e, v),
           ii_method(_, m) => visit::visit_method_helper(m, e, v),
+          ii_ctor(ctor, nm, tps, parent_id) => {
+              visit::visit_class_ctor_helper(ctor, nm, tps, parent_id, e, v);
+          }
           ii_dtor(dtor, _, tps, parent_id) => {
               visit::visit_class_dtor_helper(dtor, tps, parent_id, e, v);
           }
@@ -490,6 +495,12 @@ fn id_visitor(vfn: fn@(node_id)) -> visit::vt<()> {
             vfn(id);
 
             match fk {
+                visit::fk_ctor(_, _, tps, self_id, parent_id) => {
+                    for vec::each(tps) |tp| { vfn(tp.id); }
+                    vfn(id);
+                    vfn(self_id);
+                    vfn(parent_id.node);
+                }
                 visit::fk_dtor(tps, _, self_id, parent_id) => {
                     for vec::each(tps) |tp| { vfn(tp.id); }
                     vfn(id);
diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs
index 68d9cd80430..088df01985e 100644
--- a/src/libsyntax/fold.rs
+++ b/src/libsyntax/fold.rs
@@ -271,6 +271,23 @@ fn noop_fold_item_underscore(i: item_, fld: ast_fold) -> item_ {
 
 fn fold_struct_def(struct_def: @ast::struct_def, fld: ast_fold)
                 -> @ast::struct_def {
+    let resulting_optional_constructor;
+    match struct_def.ctor {
+        None => {
+            resulting_optional_constructor = None;
+        }
+        Some(constructor) => {
+            resulting_optional_constructor = Some({
+                node: {
+                    body: fld.fold_block(constructor.node.body),
+                    dec: fold_fn_decl(constructor.node.dec, fld),
+                    id: fld.new_id(constructor.node.id),
+                    .. constructor.node
+                },
+                .. constructor
+            });
+        }
+    }
     let dtor = do option::map(&struct_def.dtor) |dtor| {
         let dtor_body = fld.fold_block(dtor.node.body);
         let dtor_id   = fld.new_id(dtor.node.id);
@@ -281,6 +298,7 @@ fn fold_struct_def(struct_def: @ast::struct_def, fld: ast_fold)
         traits: vec::map(struct_def.traits, |p| fold_trait_ref(*p, fld)),
         fields: vec::map(struct_def.fields, |f| fold_struct_field(*f, fld)),
         methods: vec::map(struct_def.methods, |m| fld.fold_method(*m)),
+        ctor: resulting_optional_constructor,
         dtor: dtor
     };
 }
@@ -567,6 +585,7 @@ fn noop_fold_variant(v: variant_, fld: ast_fold) -> variant_ {
                                  |f| fld.fold_struct_field(*f)),
                 methods: vec::map(struct_def.methods,
                                   |m| fld.fold_method(*m)),
+                ctor: None,
                 dtor: dtor
             })
         }
diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs
index f2e17c0a7e6..22c25186c91 100644
--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -115,7 +115,8 @@ enum class_member {
   So that we can distinguish a class ctor or dtor
   from other class members
  */
-enum class_contents { dtor_decl(blk, ~[attribute], codemap::span),
+enum class_contents { ctor_decl(fn_decl, ~[attribute], blk, codemap::span),
+                      dtor_decl(blk, ~[attribute], codemap::span),
                       members(~[@class_member]) }
 
 type arg_or_capture_item = Either<arg, capture_item>;
@@ -2682,13 +2683,30 @@ impl parser {
 
         let mut fields: ~[@struct_field];
         let mut methods: ~[@method] = ~[];
+        let mut the_ctor: Option<(fn_decl, ~[attribute], blk, codemap::span)>
+            = None;
         let mut the_dtor: Option<(blk, ~[attribute], codemap::span)> = None;
+        let ctor_id = self.get_id();
 
         if self.eat(token::LBRACE) {
             // It's a record-like struct.
             fields = ~[];
             while self.token != token::RBRACE {
                 match self.parse_class_item() {
+                  ctor_decl(a_fn_decl, attrs, blk, s) => {
+                      match the_ctor {
+                        Some((_, _, _, s_first)) => {
+                          self.span_note(s, #fmt("Duplicate constructor \
+                                     declaration for class %s",
+                                     *self.interner.get(class_name)));
+                           self.span_fatal(copy s_first, ~"First constructor \
+                                                          declared here");
+                        }
+                        None    => {
+                          the_ctor = Some((a_fn_decl, attrs, blk, s));
+                        }
+                      }
+                  }
                   dtor_decl(blk, attrs, s) => {
                       match the_dtor {
                         Some((_, _, s_first)) => {
@@ -2746,14 +2764,36 @@ impl parser {
                     self_id: self.get_id(),
                     body: d_body},
              span: d_s}};
-        (class_name,
-         item_class(@{
-             traits: traits,
-             fields: move fields,
-             methods: move methods,
-             dtor: actual_dtor
-         }, ty_params),
-         None)
+        match the_ctor {
+          Some((ct_d, ct_attrs, ct_b, ct_s)) => {
+            (class_name,
+             item_class(@{
+                traits: traits,
+                fields: move fields,
+                methods: move methods,
+                ctor: Some({
+                 node: {id: ctor_id,
+                        attrs: ct_attrs,
+                        self_id: self.get_id(),
+                        dec: ct_d,
+                        body: ct_b},
+                 span: ct_s}),
+                dtor: actual_dtor
+             }, ty_params),
+             None)
+          }
+          None => {
+            (class_name,
+             item_class(@{
+                    traits: traits,
+                    fields: move fields,
+                    methods: move methods,
+                    ctor: None,
+                    dtor: actual_dtor
+             }, ty_params),
+             None)
+          }
+        }
     }
 
     fn token_is_pound_or_doc_comment(++tok: token::token) -> bool {
@@ -3057,6 +3097,12 @@ impl parser {
         let mut methods: ~[@method] = ~[];
         while self.token != token::RBRACE {
             match self.parse_class_item() {
+                ctor_decl(*) => {
+                    self.span_fatal(copy self.span,
+                                    ~"deprecated explicit \
+                                      constructors are not allowed \
+                                      here");
+                }
                 dtor_decl(blk, attrs, s) => {
                     match the_dtor {
                         Some((_, _, s_first)) => {
@@ -3097,6 +3143,7 @@ impl parser {
             traits: ~[],
             fields: move fields,
             methods: move methods,
+            ctor: None,
             dtor: actual_dtor
         };
     }
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index b98014f421b..bff356e5cb7 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -653,6 +653,18 @@ fn print_struct(s: ps, struct_def: @ast::struct_def, tps: ~[ast::ty_param],
     }
     bopen(s);
     hardbreak_if_not_bol(s);
+    do struct_def.ctor.iter |ctor| {
+      maybe_print_comment(s, ctor.span.lo);
+      print_outer_attributes(s, ctor.node.attrs);
+      // Doesn't call head because there shouldn't be a space after new.
+      cbox(s, indent_unit);
+      ibox(s, 4);
+      word(s.s, ~"new(");
+      print_fn_args(s, ctor.node.dec, ~[], None);
+      word(s.s, ~")");
+      space(s.s);
+      print_block(s, ctor.node.body);
+    }
     do struct_def.dtor.iter |dtor| {
       hardbreak_if_not_bol(s);
       maybe_print_comment(s, dtor.span.lo);
diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs
index 50fbd21f7b8..e6fd65eb458 100644
--- a/src/libsyntax/visit.rs
+++ b/src/libsyntax/visit.rs
@@ -17,6 +17,8 @@ enum fn_kind {
     fk_method(ident, ~[ty_param], @method),
     fk_anon(proto, capture_clause),  //< an anonymous function like fn@(...)
     fk_fn_block(capture_clause),     //< a block {||...}
+    fk_ctor(ident, ~[attribute], ~[ty_param], node_id /* self id */,
+            def_id /* parent class id */), // class constructor
     fk_dtor(~[ty_param], ~[attribute], node_id /* self id */,
             def_id /* parent class id */) // class destructor
 
@@ -24,9 +26,8 @@ enum fn_kind {
 
 fn name_of_fn(fk: fn_kind) -> ident {
     match fk {
-      fk_item_fn(name, _, _) | fk_method(name, _, _) => {
-          /* FIXME (#2543) */ copy name
-      }
+      fk_item_fn(name, _, _) | fk_method(name, _, _)
+          | fk_ctor(name, _, _, _, _) =>  /* FIXME (#2543) */ copy name,
       fk_anon(*) | fk_fn_block(*) => parse::token::special_idents::anon,
       fk_dtor(*)                  => parse::token::special_idents::dtor
     }
@@ -34,11 +35,11 @@ fn name_of_fn(fk: fn_kind) -> ident {
 
 fn tps_of_fn(fk: fn_kind) -> ~[ty_param] {
     match fk {
-        fk_item_fn(_, tps, _) | fk_method(_, tps, _) |
-        fk_dtor(tps, _, _, _) => {
-            /* FIXME (#2543) */ copy tps
-        }
-        fk_anon(*) | fk_fn_block(*) => ~[]
+      fk_item_fn(_, tps, _) | fk_method(_, tps, _)
+          | fk_ctor(_, _, tps, _, _) | fk_dtor(tps, _, _, _) => {
+          /* FIXME (#2543) */ copy tps
+      }
+      fk_anon(*) | fk_fn_block(*) => ~[]
     }
 }
 
@@ -290,6 +291,17 @@ fn visit_method_helper<E>(m: @method, e: E, v: vt<E>) {
                m.decl, m.body, m.span, m.id, e, v);
 }
 
+// Similar logic to the comment on visit_method_helper - Tim
+fn visit_class_ctor_helper<E>(ctor: class_ctor, nm: ident, tps: ~[ty_param],
+                              parent_id: def_id, e: E, v: vt<E>) {
+    v.visit_fn(fk_ctor(/* FIXME (#2543) */ copy nm,
+                       ctor.node.attrs,
+                       /* FIXME (#2543) */ copy tps,
+                       ctor.node.self_id, parent_id),
+        ctor.node.dec, ctor.node.body, ctor.span, ctor.node.id, e, v)
+
+}
+
 fn visit_class_dtor_helper<E>(dtor: class_dtor, tps: ~[ty_param],
                               parent_id: def_id, e: E, v: vt<E>) {
     v.visit_fn(fk_dtor(/* FIXME (#2543) */ copy tps, dtor.node.attrs,
@@ -318,7 +330,7 @@ fn visit_trait_method<E>(m: trait_method, e: E, v: vt<E>) {
     }
 }
 
-fn visit_struct_def<E>(sd: @struct_def, _nm: ast::ident, tps: ~[ty_param],
+fn visit_struct_def<E>(sd: @struct_def, nm: ast::ident, tps: ~[ty_param],
                        id: node_id, e: E, v: vt<E>) {
     for sd.fields.each |f| {
         v.visit_struct_field(*f, e, v);
@@ -329,6 +341,9 @@ fn visit_struct_def<E>(sd: @struct_def, _nm: ast::ident, tps: ~[ty_param],
     for sd.traits.each |p| {
         visit_path(p.path, e, v);
     }
+    do option::iter(&sd.ctor) |ctor| {
+      visit_class_ctor_helper(*ctor, nm, tps, ast_util::local_def(id), e, v);
+    };
     do option::iter(&sd.dtor) |dtor| {
       visit_class_dtor_helper(*dtor, tps, ast_util::local_def(id), e, v)
     };
diff --git a/src/rustc/metadata/decoder.rs b/src/rustc/metadata/decoder.rs
index 70a47522f37..197e567ab2f 100644
--- a/src/rustc/metadata/decoder.rs
+++ b/src/rustc/metadata/decoder.rs
@@ -123,6 +123,7 @@ enum Family {
     Variant,               // v
     Impl,                  // i
     Trait,                 // I
+    Class,                 // C
     Struct,                // S
     PublicField,           // g
     PrivateField,          // j
@@ -155,6 +156,7 @@ fn item_family(item: ebml2::Doc) -> Family {
       'v' => Variant,
       'i' => Impl,
       'I' => Trait,
+      'C' => Class,
       'S' => Struct,
       'g' => PublicField,
       'j' => PrivateField,
@@ -298,7 +300,8 @@ fn item_to_def_like(item: ebml2::Doc, did: ast::def_id, cnum: ast::crate_num)
     let fam = item_family(item);
     match fam {
       Const     => dl_def(ast::def_const(did)),
-      Struct    => dl_def(ast::def_class(did)),
+      Class     => dl_def(ast::def_class(did, true)),
+      Struct    => dl_def(ast::def_class(did, false)),
       UnsafeFn  => dl_def(ast::def_fn(did, ast::unsafe_fn)),
       Fn        => dl_def(ast::def_fn(did, ast::impure_fn)),
       PureFn    => dl_def(ast::def_fn(did, ast::pure_fn)),
@@ -819,6 +822,7 @@ fn item_family_to_str(fam: Family) -> ~str {
       Variant => ~"variant",
       Impl => ~"impl",
       Trait => ~"trait",
+      Class => ~"class",
       Struct => ~"struct",
       PublicField => ~"public field",
       PrivateField => ~"private field",
diff --git a/src/rustc/metadata/encoder.rs b/src/rustc/metadata/encoder.rs
index ce3f50e7184..95696a14156 100644
--- a/src/rustc/metadata/encoder.rs
+++ b/src/rustc/metadata/encoder.rs
@@ -635,7 +635,12 @@ fn encode_info_for_item(ecx: @encode_ctxt, ebml_w: ebml2::Serializer,
         /* Now, make an item for the class itself */
         ebml_w.start_tag(tag_items_data_item);
         encode_def_id(ebml_w, local_def(item.id));
-        encode_family(ebml_w, 'S');
+
+        match struct_def.ctor {
+            None => encode_family(ebml_w, 'S'),
+            Some(_) => encode_family(ebml_w, 'C')
+        }
+
         encode_type_param_bounds(ebml_w, ecx, tps);
         encode_type(ecx, ebml_w, node_id_to_type(tcx, item.id));
         encode_name(ecx, ebml_w, item.ident);
@@ -694,6 +699,21 @@ fn encode_info_for_item(ecx: @encode_ctxt, ebml_w: ebml2::Serializer,
         let bkts = create_index(idx);
         encode_index(ebml_w, bkts, write_int);
         ebml_w.end_tag();
+
+        /* Encode the constructor */
+        for struct_def.ctor.each |ctor| {
+            debug!("encoding info for ctor %s %d",
+                   ecx.tcx.sess.str_of(item.ident), ctor.node.id);
+            index.push({
+                val: ctor.node.id,
+                pos: ebml_w.writer.tell()
+            });
+            encode_info_for_ctor(ecx, ebml_w, ctor.node.id, item.ident,
+                                 path, if tps.len() > 0u {
+                                     Some(ii_ctor(*ctor, item.ident, tps,
+                                                  local_def(item.id))) }
+                                 else { None }, tps);
+        }
       }
       item_impl(tps, opt_trait, _, methods) => {
         add_to_index();
diff --git a/src/rustc/middle/astencode.rs b/src/rustc/middle/astencode.rs
index 95c3d08f60e..39ec58c079e 100644
--- a/src/rustc/middle/astencode.rs
+++ b/src/rustc/middle/astencode.rs
@@ -262,6 +262,13 @@ fn simplify_ast(ii: ast::inlined_item) -> ast::inlined_item {
       ast::ii_foreign(i) => {
         ast::ii_foreign(fld.fold_foreign_item(i))
       }
+      ast::ii_ctor(ctor, nm, tps, parent_id) => {
+        let ctor_body = fld.fold_block(ctor.node.body);
+        let ctor_decl = fold::fold_fn_decl(ctor.node.dec, fld);
+        ast::ii_ctor({node: {body: ctor_body, dec: ctor_decl,
+                              .. ctor.node},
+            .. ctor}, nm, tps, parent_id)
+      }
       ast::ii_dtor(dtor, nm, tps, parent_id) => {
         let dtor_body = fld.fold_block(dtor.node.body);
         ast::ii_dtor({node: {body: dtor_body,
@@ -295,6 +302,18 @@ fn renumber_ast(xcx: extended_decode_ctxt, ii: ast::inlined_item)
       ast::ii_foreign(i) => {
         ast::ii_foreign(fld.fold_foreign_item(i))
       }
+      ast::ii_ctor(ctor, nm, tps, parent_id) => {
+        let ctor_body = fld.fold_block(ctor.node.body);
+        let ctor_attrs = fld.fold_attributes(ctor.node.attrs);
+        let ctor_decl = fold::fold_fn_decl(ctor.node.dec, fld);
+        let new_params = fold::fold_ty_params(tps, fld);
+        let ctor_id = fld.new_id(ctor.node.id);
+        let new_parent = xcx.tr_def_id(parent_id);
+        ast::ii_ctor({node: {body: ctor_body, attrs: ctor_attrs,
+                dec: ctor_decl, id: ctor_id,
+                              .. ctor.node},
+            .. ctor}, nm, new_params, new_parent)
+      }
       ast::ii_dtor(dtor, nm, tps, parent_id) => {
         let dtor_body = fld.fold_block(dtor.node.body);
         let dtor_attrs = fld.fold_attributes(dtor.node.attrs);
@@ -350,8 +369,8 @@ impl ast::def: tr {
                            xcx.tr_id(nid2),
                            xcx.tr_id(nid3))
           }
-          ast::def_class(did) => {
-            ast::def_class(did.tr(xcx))
+          ast::def_class(did, has_constructor) => {
+            ast::def_class(did.tr(xcx), has_constructor)
           }
           ast::def_region(nid) => ast::def_region(xcx.tr_id(nid)),
           ast::def_typaram_binder(nid) => {
diff --git a/src/rustc/middle/borrowck/check_loans.rs b/src/rustc/middle/borrowck/check_loans.rs
index 6a9195b4509..cc8d89a8ace 100644
--- a/src/rustc/middle/borrowck/check_loans.rs
+++ b/src/rustc/middle/borrowck/check_loans.rs
@@ -17,6 +17,10 @@ enum check_loan_ctxt = @{
 
     reported: HashMap<ast::node_id, ()>,
 
+    // Keep track of whether we're inside a ctor, so as to
+    // allow mutating immutable fields in the same class if
+    // we are in a ctor, we track the self id
+    mut in_ctor: bool,
     mut declared_purity: ast::purity,
     mut fn_args: @~[ast::node_id]
 };
@@ -58,6 +62,7 @@ fn check_loans(bccx: borrowck_ctxt,
     let clcx = check_loan_ctxt(@{bccx: bccx,
                                  req_maps: req_maps,
                                  reported: HashMap(),
+                                 mut in_ctor: false,
                                  mut declared_purity: ast::impure_fn,
                                  mut fn_args: @~[]});
     let vt = visit::mk_vt(@{visit_expr: check_loans_in_expr,
@@ -315,7 +320,10 @@ impl check_loan_ctxt {
         debug!("check_assignment(cmt=%s)",
                self.bccx.cmt_to_repr(cmt));
 
-        if self.is_local_variable(cmt) && at.checked_by_liveness() {
+        if self.in_ctor && self.is_self_field(cmt)
+            && at.checked_by_liveness() {
+            // assigning to self.foo in a ctor is always allowed.
+        } else if self.is_local_variable(cmt) && at.checked_by_liveness() {
             // liveness guarantees that immutable local variables
             // are only assigned once
         } else {
@@ -534,28 +542,42 @@ fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk,
                      visitor: visit::vt<check_loan_ctxt>) {
 
     debug!("purity on entry=%?", copy self.declared_purity);
-    do save_and_restore(&mut(self.declared_purity)) {
-        do save_and_restore(&mut(self.fn_args)) {
-            let is_stack_closure = self.is_stack_closure(id);
-            let fty = ty::node_id_to_type(self.tcx(), id);
-            self.declared_purity = ty::determine_inherited_purity(
-                copy self.declared_purity,
-                ty::ty_fn_purity(fty),
-                ty::ty_fn_proto(fty));
-
-            match fk {
-                visit::fk_anon(*) |
-                visit::fk_fn_block(*) if is_stack_closure => {
+    do save_and_restore(&mut(self.in_ctor)) {
+        do save_and_restore(&mut(self.declared_purity)) {
+            do save_and_restore(&mut(self.fn_args)) {
+                let is_stack_closure = self.is_stack_closure(id);
+                let fty = ty::node_id_to_type(self.tcx(), id);
+                self.declared_purity = ty::determine_inherited_purity(
+                    copy self.declared_purity,
+                    ty::ty_fn_purity(fty),
+                    ty::ty_fn_proto(fty));
+
+                // In principle, we could consider fk_anon(*) or
+                // fk_fn_block(*) to be in a ctor, I suppose, but the
+                // purpose of the in_ctor flag is to allow modifications
+                // of otherwise immutable fields and typestate wouldn't be
+                // able to "see" into those functions anyway, so it
+                // wouldn't be very helpful.
+                match fk {
+                  visit::fk_ctor(*) => {
+                    self.in_ctor = true;
+                    self.fn_args = @decl.inputs.map(|i| i.id );
+                  }
+                  visit::fk_anon(*) |
+                  visit::fk_fn_block(*) if is_stack_closure => {
+                    self.in_ctor = false;
                     // inherits the fn_args from enclosing ctxt
-                }
-                visit::fk_anon(*) | visit::fk_fn_block(*) |
-                visit::fk_method(*) | visit::fk_item_fn(*) |
-                visit::fk_dtor(*) => {
+                  }
+                  visit::fk_anon(*) | visit::fk_fn_block(*) |
+                  visit::fk_method(*) | visit::fk_item_fn(*) |
+                  visit::fk_dtor(*) => {
+                    self.in_ctor = false;
                     self.fn_args = @decl.inputs.map(|i| i.id );
+                  }
                 }
-            }
 
-            visit::visit_fn(fk, decl, body, sp, id, self, visitor);
+                visit::visit_fn(fk, decl, body, sp, id, self, visitor);
+            }
         }
     }
     debug!("purity on exit=%?", copy self.declared_purity);
diff --git a/src/rustc/middle/borrowck/gather_loans.rs b/src/rustc/middle/borrowck/gather_loans.rs
index 2a23ec86c51..5dfde8c9af6 100644
--- a/src/rustc/middle/borrowck/gather_loans.rs
+++ b/src/rustc/middle/borrowck/gather_loans.rs
@@ -71,11 +71,11 @@ fn req_loans_in_fn(fk: visit::fn_kind,
     self.root_ub = body.node.id;
 
     match fk {
-        visit::fk_anon(*) | visit::fk_fn_block(*) => {}
-        visit::fk_item_fn(*) | visit::fk_method(*) |
-        visit::fk_dtor(*) => {
-            self.item_ub = body.node.id;
-        }
+      visit::fk_anon(*) | visit::fk_fn_block(*) => {}
+      visit::fk_item_fn(*) | visit::fk_method(*) |
+      visit::fk_ctor(*) | visit::fk_dtor(*) => {
+        self.item_ub = body.node.id;
+      }
     }
 
     visit::visit_fn(fk, decl, body, sp, id, self, v);
diff --git a/src/rustc/middle/kind.rs b/src/rustc/middle/kind.rs
index 7b473cedb4c..e2b85441a8f 100644
--- a/src/rustc/middle/kind.rs
+++ b/src/rustc/middle/kind.rs
@@ -181,9 +181,9 @@ fn check_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span,
         // variables.  This list is used below to avoid checking and reporting
         // on a given variable twice.
         let cap_clause = match fk {
-            visit::fk_anon(_, cc) | visit::fk_fn_block(cc) => cc,
-            visit::fk_item_fn(*) | visit::fk_method(*) |
-            visit::fk_dtor(*) => @~[]
+          visit::fk_anon(_, cc) | visit::fk_fn_block(cc) => cc,
+          visit::fk_item_fn(*) | visit::fk_method(*) |
+          visit::fk_ctor(*) | visit::fk_dtor(*) => @~[]
         };
         let captured_vars = do (*cap_clause).map |cap_item| {
             let cap_def = cx.tcx.def_map.get(cap_item.id);
diff --git a/src/rustc/middle/liveness.rs b/src/rustc/middle/liveness.rs
index 86f786e7706..12d63cdacbf 100644
--- a/src/rustc/middle/liveness.rs
+++ b/src/rustc/middle/liveness.rs
@@ -35,6 +35,12 @@
  * Any use of the variable where the variable is dead afterwards is a
  * last use.
  *
+ * # Extension to handle constructors
+ *
+ * Each field is assigned an index just as with local variables.  A use of
+ * `self` is considered a use of all fields.  A use of `self.f` is just a use
+ * of `f`.
+ *
  * # Implementation details
  *
  * The actual implementation contains two (nested) walks over the AST.
@@ -90,6 +96,8 @@
  * - `no_ret_var`: a synthetic variable that is only 'read' from, the
  *   fallthrough node.  This allows us to detect functions where we fail
  *   to return explicitly.
+ *
+ * - `self_var`: a variable representing 'self'
  */
 
 use dvec::DVec;
@@ -222,7 +230,7 @@ impl LiveNode {
 
 fn invalid_node() -> LiveNode { LiveNode(uint::max_value) }
 
-enum RelevantDef { RelevantVar(node_id) }
+enum RelevantDef { RelevantVar(node_id), RelevantSelf }
 
 type CaptureInfo = {ln: LiveNode, is_move: bool, rv: RelevantDef};
 
@@ -242,12 +250,15 @@ struct LocalInfo {
 enum VarKind {
     Arg(node_id, ident, rmode),
     Local(LocalInfo),
+    Field(ident),
     Self,
     ImplicitRet
 }
 
 fn relevant_def(def: def) -> Option<RelevantDef> {
     match def {
+      def_self(_) => Some(RelevantSelf),
+
       def_binding(nid, _) |
       def_arg(nid, _) |
       def_local(nid, _) => Some(RelevantVar(nid)),
@@ -265,6 +276,7 @@ struct IrMaps {
     mut num_vars: uint,
     live_node_map: HashMap<node_id, LiveNode>,
     variable_map: HashMap<node_id, Variable>,
+    field_map: HashMap<ident, Variable>,
     capture_map: HashMap<node_id, @~[CaptureInfo]>,
     mut var_kinds: ~[VarKind],
     mut lnks: ~[LiveNodeKind],
@@ -281,6 +293,7 @@ fn IrMaps(tcx: ty::ctxt, method_map: typeck::method_map,
         live_node_map: HashMap(),
         variable_map: HashMap(),
         capture_map: HashMap(),
+        field_map: HashMap(),
         var_kinds: ~[],
         lnks: ~[]
     }
@@ -310,12 +323,15 @@ impl IrMaps {
         self.num_vars += 1u;
 
         match vk {
-            Local(LocalInfo {id:node_id, _}) |
-            Arg(node_id, _, _) => {
-                self.variable_map.insert(node_id, v);
-            }
-            Self | ImplicitRet => {
-            }
+          Local(LocalInfo {id:node_id, _}) |
+          Arg(node_id, _, _) => {
+            self.variable_map.insert(node_id, v);
+          }
+          Field(name) => {
+            self.field_map.insert(name, v);
+          }
+          Self | ImplicitRet => {
+          }
         }
 
         debug!("%s is %?", v.to_str(), vk);
@@ -335,10 +351,11 @@ impl IrMaps {
 
     fn variable_name(var: Variable) -> ~str {
         match copy self.var_kinds[*var] {
-            Local(LocalInfo {ident: nm, _}) |
-            Arg(_, nm, _) => self.tcx.sess.str_of(nm),
-            Self => ~"self",
-            ImplicitRet => ~"<implicit-ret>"
+          Local(LocalInfo {ident: nm, _}) |
+          Arg(_, nm, _) => self.tcx.sess.str_of(nm),
+          Field(nm) => ~"self." + self.tcx.sess.str_of(nm),
+          Self => ~"self",
+          ImplicitRet => ~"<implicit-ret>"
         }
     }
 
@@ -382,7 +399,7 @@ impl IrMaps {
             (*v).push(id);
           }
           Arg(_, _, by_ref) |
-          Arg(_, _, by_val) | Self | ImplicitRet |
+          Arg(_, _, by_val) | Self | Field(_) | ImplicitRet |
           Local(LocalInfo {kind: FromMatch(bind_by_implicit_ref), _}) => {
             debug!("--but it is not owned");
           }
@@ -411,6 +428,13 @@ fn visit_fn(fk: visit::fn_kind, decl: fn_decl, body: blk,
     // and so forth:
     visit::visit_fn(fk, decl, body, sp, id, fn_maps, v);
 
+    match fk {
+      visit::fk_ctor(_, _, _, _, class_did) => {
+        add_class_fields(fn_maps, class_did);
+      }
+      _ => {}
+    }
+
     // Special nodes and variables:
     // - exit_ln represents the end of the fn, either by return or fail
     // - implicit_ret_var is a pseudo-variable that represents
@@ -418,7 +442,8 @@ fn visit_fn(fk: visit::fn_kind, decl: fn_decl, body: blk,
     let specials = {
         exit_ln: (*fn_maps).add_live_node(ExitNode),
         fallthrough_ln: (*fn_maps).add_live_node(ExitNode),
-        no_ret_var: (*fn_maps).add_variable(ImplicitRet)
+        no_ret_var: (*fn_maps).add_variable(ImplicitRet),
+        self_var: (*fn_maps).add_variable(Self)
     };
 
     // compute liveness
@@ -435,9 +460,18 @@ fn visit_fn(fk: visit::fn_kind, decl: fn_decl, body: blk,
     });
     check_vt.visit_block(body, lsets, check_vt);
     lsets.check_ret(id, sp, fk, entry_ln);
+    lsets.check_fields(sp, entry_ln);
     lsets.warn_about_unused_args(sp, decl, entry_ln);
 }
 
+fn add_class_fields(self: @IrMaps, did: def_id) {
+    for ty::lookup_class_fields(self.tcx, did).each |field_ty| {
+        assert field_ty.id.crate == local_crate;
+        let var = self.add_variable(Field(field_ty.ident));
+        self.field_map.insert(field_ty.ident, var);
+    }
+}
+
 fn visit_local(local: @local, &&self: @IrMaps, vt: vt<@IrMaps>) {
     let def_map = self.tcx.def_map;
     do pat_util::pat_bindings(def_map, local.node.pat) |_bm, p_id, sp, path| {
@@ -561,7 +595,8 @@ fn invalid_users() -> users {
 type Specials = {
     exit_ln: LiveNode,
     fallthrough_ln: LiveNode,
-    no_ret_var: Variable
+    no_ret_var: Variable,
+    self_var: Variable
 };
 
 const ACC_READ: uint = 1u;
@@ -614,6 +649,7 @@ impl Liveness {
 
     fn variable_from_rdef(rv: RelevantDef, span: span) -> Variable {
         match rv {
+          RelevantSelf => self.s.self_var,
           RelevantVar(nid) => self.variable(nid, span)
         }
     }
@@ -898,6 +934,14 @@ impl Liveness {
             }
         }
 
+        // as above, the "self" variable is a non-owned variable
+        self.acc(self.s.exit_ln, self.s.self_var, ACC_READ);
+
+        // in a ctor, there is an implicit use of self.f for all fields f:
+        for self.ir.field_map.each_value |var| {
+            self.acc(self.s.exit_ln, var, ACC_READ|ACC_USE);
+        }
+
         // the fallthrough exit is only for those cases where we do not
         // explicitly return:
         self.init_from_succ(self.s.fallthrough_ln, self.s.exit_ln);
@@ -979,11 +1023,24 @@ impl Liveness {
           // Interesting cases with control flow or which gen/kill
 
           expr_path(_) => {
-              self.access_path(expr, succ, ACC_READ | ACC_USE)
+            self.access_path(expr, succ, ACC_READ | ACC_USE)
           }
 
-          expr_field(e, _, _) => {
-              self.propagate_through_expr(e, succ)
+          expr_field(e, nm, _) => {
+            // If this is a reference to `self.f` inside of a ctor,
+            // then we treat it as a read of that variable.
+            // Otherwise, we ignore it and just propagate down to
+            // process `e`.
+            match self.as_self_field(e, nm) {
+              Some((ln, var)) => {
+                self.init_from_succ(ln, succ);
+                self.acc(ln, var, ACC_READ | ACC_USE);
+                ln
+              }
+              None => {
+                self.propagate_through_expr(e, succ)
+              }
+            }
           }
 
           expr_fn(*) | expr_fn_block(*) => {
@@ -1212,8 +1269,8 @@ impl Liveness {
         // In general, the full flow graph structure for an
         // assignment/move/etc can be handled in one of two ways,
         // depending on whether what is being assigned is a "tracked
-        // value" or not. A tracked value is basically a local
-        // variable or argument.
+        // value" or not. A tracked value is basically a local variable
+        // or argument, or a self-field (`self.f`) in a ctor.
         //
         // The two kinds of graphs are:
         //
@@ -1236,11 +1293,12 @@ impl Liveness {
         //
         // # Tracked lvalues
         //
-        // A tracked lvalue is a local variable/argument `x`.  In
+        // A tracked lvalue is either a local variable/argument `x` or
+        // else it is a self-field `self.f` in a constructor.  In
         // these cases, the link_node where the write occurs is linked
-        // to node id of `x`.  The `write_lvalue()` routine generates
-        // the contents of this node.  There are no subcomponents to
-        // consider.
+        // to node id of `x` or `self`, respectively.  The
+        // `write_lvalue()` routine generates the contents of this
+        // node.  There are no subcomponents to consider.
         //
         // # Non-tracked lvalues
         //
@@ -1257,9 +1315,12 @@ impl Liveness {
         // just ignore such cases and treat them as reads.
 
         match expr.node {
-            expr_path(_) => succ,
-            expr_field(e, _, _) => self.propagate_through_expr(e, succ),
-            _ => self.propagate_through_expr(expr, succ)
+          expr_path(_) => succ,
+          expr_field(e, nm, _) => match self.as_self_field(e, nm) {
+            Some(_) => succ,
+            None => self.propagate_through_expr(e, succ)
+          },
+          _ => self.propagate_through_expr(expr, succ)
         }
     }
 
@@ -1269,6 +1330,14 @@ impl Liveness {
                     acc: uint) -> LiveNode {
         match expr.node {
           expr_path(_) => self.access_path(expr, succ, acc),
+          expr_field(e, nm, _) => match self.as_self_field(e, nm) {
+            Some((ln, var)) => {
+                self.init_from_succ(ln, succ);
+                self.acc(ln, var, acc);
+                ln
+            }
+            None => succ
+          },
 
           // We do not track other lvalues, so just propagate through
           // to their subcomponents.  Also, it may happen that
@@ -1281,6 +1350,26 @@ impl Liveness {
     fn access_path(expr: @expr, succ: LiveNode, acc: uint) -> LiveNode {
         let def = self.tcx.def_map.get(expr.id);
         match relevant_def(def) {
+          Some(RelevantSelf) => {
+            // Accessing `self` is like accessing every field of
+            // the current object. This allows something like
+            // `self = ...;` (it will be considered a write to
+            // every field, sensibly enough), though the borrowck
+            // pass will reject it later on.
+            //
+            // Also, note that, within a ctor at least, an
+            // expression like `self.f` is "shortcircuiting"
+            // before it reaches this point by the code for
+            // expr_field.
+            let ln = self.live_node(expr.id, expr.span);
+            if acc != 0u {
+                self.init_from_succ(ln, succ);
+                for self.ir.field_map.each_value |var| {
+                    self.acc(ln, var, acc);
+                }
+            }
+            ln
+          }
           Some(RelevantVar(nid)) => {
             let ln = self.live_node(expr.id, expr.span);
             if acc != 0u {
@@ -1294,6 +1383,29 @@ impl Liveness {
         }
     }
 
+    fn as_self_field(expr: @expr,
+                     fld: ident) -> Option<(LiveNode,Variable)> {
+        // If we checking a constructor, then we treat self.f as a
+        // variable.  we use the live_node id that will be assigned to
+        // the reference to self but the variable id for `f`.
+        match expr.node {
+          expr_path(_) => {
+            let def = self.tcx.def_map.get(expr.id);
+            match def {
+              def_self(_) => {
+                // Note: the field_map is empty unless we are in a ctor
+                return self.ir.field_map.find(fld).map(|var| {
+                    let ln = self.live_node(expr.id, expr.span);
+                    (ln, *var)
+                });
+              }
+              _ => return None
+            }
+          }
+          _ => return None
+        }
+    }
+
     fn propagate_through_loop(expr: @expr,
                               cond: Option<@expr>,
                               body: blk,
@@ -1501,7 +1613,24 @@ enum ReadKind {
 }
 
 impl @Liveness {
-    fn check_ret(id: node_id, sp: span, _fk: visit::fn_kind,
+    fn check_fields(sp: span, entry_ln: LiveNode) {
+        for self.ir.field_map.each |nm, var| {
+            match self.live_on_entry(entry_ln, var) {
+              None => { /* ok */ }
+              Some(ExitNode) => {
+                self.tcx.sess.span_err(
+                    sp, fmt!("field `self.%s` is never initialized",
+                             self.tcx.sess.str_of(nm)));
+              }
+              Some(lnk) => {
+                self.report_illegal_read(
+                    sp, lnk, var, PossiblyUninitializedField);
+              }
+            }
+        }
+    }
+
+    fn check_ret(id: node_id, sp: span, fk: visit::fn_kind,
                  entry_ln: LiveNode) {
         if self.live_on_entry(entry_ln, self.s.no_ret_var).is_some() {
             // if no_ret_var is live, then we fall off the end of the
@@ -1515,8 +1644,15 @@ impl @Liveness {
                 self.tcx.sess.span_err(
                     sp, ~"some control paths may return");
             } else {
-                self.tcx.sess.span_err(
-                    sp, ~"not all control paths return a value");
+                match fk {
+                  visit::fk_ctor(*) => {
+                    // ctors are written as though they are unit.
+                  }
+                  _ => {
+                    self.tcx.sess.span_err(
+                        sp, ~"not all control paths return a value");
+                  }
+                }
             }
         }
     }
@@ -1601,6 +1737,7 @@ impl @Liveness {
                     let var = self.variable(nid, expr.span);
                     self.warn_about_dead_assign(expr.span, ln, var);
                   }
+                  Some(RelevantSelf) => {}
                   None => {}
                 }
               }
@@ -1659,6 +1796,13 @@ impl @Liveness {
                           copy or move mode", self.tcx.sess.str_of(name)));
                 return;
               }
+              Field(name) => {
+                self.tcx.sess.span_err(
+                    move_span,
+                    fmt!("illegal move from field `%s`",
+                         self.tcx.sess.str_of(name)));
+                return;
+              }
               Self => {
                 self.tcx.sess.span_err(
                     move_span,
diff --git a/src/rustc/middle/region.rs b/src/rustc/middle/region.rs
index eb0bf8796f0..ff708b7f4ef 100644
--- a/src/rustc/middle/region.rs
+++ b/src/rustc/middle/region.rs
@@ -305,16 +305,16 @@ fn resolve_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk,
               visitor: visit::vt<ctxt>) {
 
     let fn_cx = match fk {
-        visit::fk_item_fn(*) | visit::fk_method(*) |
-        visit::fk_dtor(*) => {
-            // Top-level functions are a root scope.
-            ctxt {parent: Some(id),.. cx}
-        }
+      visit::fk_item_fn(*) | visit::fk_method(*) |
+      visit::fk_ctor(*) | visit::fk_dtor(*) => {
+        // Top-level functions are a root scope.
+        ctxt {parent: Some(id),.. cx}
+      }
 
-        visit::fk_anon(*) | visit::fk_fn_block(*) => {
-            // Closures continue with the inherited scope.
-            cx
-        }
+      visit::fk_anon(*) | visit::fk_fn_block(*) => {
+        // Closures continue with the inherited scope.
+        cx
+      }
     };
 
     debug!("visiting fn with body %d. cx.parent: %? \
@@ -641,7 +641,7 @@ fn determine_rp_in_ty(ty: @ast::ty,
     match ty.node {
       ast::ty_path(path, id) => {
         match cx.def_map.get(id) {
-          ast::def_ty(did) | ast::def_class(did) => {
+          ast::def_ty(did) | ast::def_class(did, _) => {
             if did.crate == ast::local_crate {
                 if cx.opt_region_is_relevant(path.rp) {
                     cx.add_dep(did.node);
diff --git a/src/rustc/middle/resolve.rs b/src/rustc/middle/resolve.rs
index b455cc257ef..5f30346a28e 100644
--- a/src/rustc/middle/resolve.rs
+++ b/src/rustc/middle/resolve.rs
@@ -757,7 +757,7 @@ struct Resolver {
     unused_import_lint_level: level,
 
     trait_info: HashMap<def_id,@HashMap<ident,()>>,
-    structs: HashMap<def_id,()>,
+    structs: HashMap<def_id,bool>,
 
     // The number of imports that are currently unresolved.
     mut unresolved_imports: uint,
@@ -1069,15 +1069,36 @@ impl Resolver {
             }
 
             // These items live in both the type and value namespaces.
-            item_class(*) => {
-                let (name_bindings, new_parent) =
-                    self.add_child(ident, parent, ~[TypeNS], sp);
+            item_class(struct_definition, _) => {
+                let new_parent =
+                    match struct_definition.ctor {
+                    None => {
+                        let (name_bindings, new_parent) =
+                            self.add_child(ident, parent, ~[TypeNS], sp);
 
-                (*name_bindings).define_type
-                    (privacy, def_ty(local_def(item.id)), sp);
+                        (*name_bindings).define_type
+                            (privacy, def_ty(local_def(item.id)), sp);
+                        new_parent
+                    }
+                    Some(ctor) => {
+                        let (name_bindings, new_parent) =
+                            self.add_child(ident, parent, ~[ValueNS, TypeNS],
+                                           sp);
+
+                        (*name_bindings).define_type
+                            (privacy, def_ty(local_def(item.id)), sp);
+
+                        let purity = impure_fn;
+                        let ctor_def = def_fn(local_def(ctor.node.id),
+                                              purity);
+                        (*name_bindings).define_value(privacy, ctor_def, sp);
+                        new_parent
+                    }
+                };
 
                 // Record the def ID of this struct.
-                self.structs.insert(local_def(item.id), ());
+                self.structs.insert(local_def(item.id),
+                                    struct_definition.ctor.is_some());
 
                 visit_item(item, new_parent, visitor);
             }
@@ -1162,7 +1183,7 @@ impl Resolver {
                                      def_variant(item_id,
                                                  local_def(variant.node.id)),
                                      variant.span);
-                self.structs.insert(local_def(variant.node.id), ());
+                self.structs.insert(local_def(variant.node.id), false);
             }
             enum_variant_kind(enum_definition) => {
                 (*child).define_type(privacy,
@@ -1499,12 +1520,18 @@ impl Resolver {
 
             child_name_bindings.define_type(Public, def, dummy_sp());
           }
-          def_class(def_id) => {
+          def_class(def_id, has_constructor) => {
             debug!("(building reduced graph for external \
-                    crate) building type %s",
-                   final_ident);
+                    crate) building type %s (value? %d)",
+                   final_ident,
+                   if has_constructor { 1 } else { 0 });
             child_name_bindings.define_type(Public, def, dummy_sp());
-            self.structs.insert(def_id, ());
+
+            if has_constructor {
+                child_name_bindings.define_value(Public, def, dummy_sp());
+            }
+
+            self.structs.insert(def_id, has_constructor);
           }
           def_self(*) | def_arg(*) | def_local(*) |
           def_prim_ty(*) | def_ty_param(*) | def_binding(*) |
@@ -3279,6 +3306,7 @@ impl Resolver {
                                    struct_def.traits,
                                    struct_def.fields,
                                    struct_def.methods,
+                                   struct_def.ctor,
                                    struct_def.dtor,
                                    visitor);
             }
@@ -3520,6 +3548,7 @@ impl Resolver {
                      traits: ~[@trait_ref],
                      fields: ~[@struct_field],
                      methods: ~[@method],
+                     optional_constructor: Option<class_ctor>,
                      optional_destructor: Option<class_dtor>,
                      visitor: ResolveVisitor) {
 
@@ -3571,6 +3600,23 @@ impl Resolver {
                 self.resolve_type(field.node.ty, visitor);
             }
 
+            // Resolve the constructor, if applicable.
+            match optional_constructor {
+                None => {
+                    // Nothing to do.
+                }
+                Some(constructor) => {
+                    self.resolve_function(NormalRibKind,
+                                          Some(@constructor.node.dec),
+                                          NoTypeParameters,
+                                          constructor.node.body,
+                                          HasSelfBinding(constructor.node.
+                                                         self_id),
+                                          NoCaptureClause,
+                                          visitor);
+                }
+            }
+
             // Resolve the destructor, if applicable.
             match optional_destructor {
                 None => {
@@ -4043,7 +4089,9 @@ impl Resolver {
                     match self.resolve_path(path, TypeNS, false, visitor) {
                         Some(def_ty(class_id))
                                 if self.structs.contains_key(class_id) => {
-                            let class_def = def_class(class_id);
+                            let has_constructor = self.structs.get(class_id);
+                            let class_def = def_class(class_id,
+                                                      has_constructor);
                             self.record_def(pattern.id, class_def);
                         }
                         Some(definition @ def_variant(_, variant_id))
@@ -4511,9 +4559,10 @@ impl Resolver {
                 //    let bar = Bar { ... } // no type parameters
 
                 match self.resolve_path(path, TypeNS, false, visitor) {
-                    Some(def_ty(class_id)) | Some(def_class(class_id))
+                    Some(def_ty(class_id)) | Some(def_class(class_id, _))
                             if self.structs.contains_key(class_id) => {
-                        let class_def = def_class(class_id);
+                        let has_constructor = self.structs.get(class_id);
+                        let class_def = def_class(class_id, has_constructor);
                         self.record_def(expr.id, class_def);
                     }
                     Some(definition @ def_variant(_, class_id))
diff --git a/src/rustc/middle/trans/base.rs b/src/rustc/middle/trans/base.rs
index c605fe39873..95711f8da36 100644
--- a/src/rustc/middle/trans/base.rs
+++ b/src/rustc/middle/trans/base.rs
@@ -1596,14 +1596,18 @@ fn trans_closure(ccx: @crate_ctxt, path: path, decl: ast::fn_decl,
     // translation calls that don't have a return value (trans_crate,
     // trans_mod, trans_item, et cetera) and those that do
     // (trans_block, trans_expr, et cetera).
-    if body.node.expr.is_none() || ty::type_is_bot(block_ty) ||
-        ty::type_is_nil(block_ty)
-    {
+
+    if !ccx.class_ctors.contains_key(id) // hack --
+       /* avoids the need for special cases to assign a type to
+          the constructor body (since it has no explicit return) */
+      &&
+      (body.node.expr.is_none() ||
+       ty::type_is_bot(block_ty) ||
+       ty::type_is_nil(block_ty))  {
         bcx = controlflow::trans_block(bcx, body, expr::Ignore);
     } else {
         bcx = controlflow::trans_block(bcx, body, expr::SaveIn(fcx.llretptr));
     }
-
     finish(bcx);
     cleanup_and_Br(bcx, bcx_top, fcx.llreturn);
 
@@ -1694,6 +1698,60 @@ fn trans_enum_variant(ccx: @crate_ctxt,
     finish_fn(fcx, lltop);
 }
 
+fn trans_class_ctor(ccx: @crate_ctxt, path: path, decl: ast::fn_decl,
+                    body: ast::blk, llctor_decl: ValueRef,
+                    psubsts: param_substs, ctor_id: ast::node_id,
+                    parent_id: ast::def_id, sp: span) {
+    // Add ctor to the ctor map
+    ccx.class_ctors.insert(ctor_id, parent_id);
+
+    // Translate the ctor
+
+    // Set up the type for the result of the ctor
+    // kludgy -- this wouldn't be necessary if the typechecker
+    // special-cased constructors, then we could just look up
+    // the ctor's return type.
+    let rslt_ty =  ty::mk_class(ccx.tcx, parent_id,
+                                dummy_substs(psubsts.tys));
+
+    // Make the fn context
+    let fcx = new_fn_ctxt_w_id(ccx, path, llctor_decl, ctor_id,
+                               Some(psubsts), Some(sp));
+    let raw_llargs = create_llargs_for_fn_args(fcx, no_self, decl.inputs);
+    let mut bcx_top = top_scope_block(fcx, body.info());
+    let lltop = bcx_top.llbb;
+    let arg_tys = ty::ty_fn_args(node_id_type(bcx_top, ctor_id));
+    bcx_top = copy_args_to_allocas(fcx, bcx_top, decl.inputs,
+                                   raw_llargs, arg_tys);
+
+    // Create a temporary for `self` that we will return at the end
+    let selfdatum = datum::scratch_datum(bcx_top, rslt_ty, true);
+
+    // Initialize dtor flag (if any) to 1
+    if ty::ty_dtor(bcx_top.tcx(), parent_id).is_some() {
+        let flag = GEPi(bcx_top, selfdatum.val, [0, 1]);
+        Store(bcx_top, C_u8(1), flag);
+    }
+
+    // initialize fields to zero
+    let mut bcx = bcx_top;
+
+    // note we don't want to take *or* drop self.
+    fcx.llself = Some(ValSelfData {v: selfdatum.val,
+                                   t: rslt_ty,
+                                   is_owned: false});
+
+    // Translate the body of the ctor
+    bcx = controlflow::trans_block(bcx, body, expr::Ignore);
+
+    // Generate the return expression
+    bcx = selfdatum.move_to(bcx, datum::INIT, fcx.llretptr);
+
+    cleanup_and_leave(bcx, None, Some(fcx.llreturn));
+    Unreachable(bcx);
+    finish_fn(fcx, lltop);
+}
+
 fn trans_class_dtor(ccx: @crate_ctxt, path: path,
     body: ast::blk, dtor_id: ast::node_id,
     psubsts: Option<param_substs>,
@@ -1863,6 +1921,14 @@ fn trans_struct_def(ccx: @crate_ctxt, struct_def: @ast::struct_def,
                     tps: ~[ast::ty_param], path: @ast_map::path,
                     ident: ast::ident, id: ast::node_id) {
     if tps.len() == 0u {
+      let psubsts = {tys: ty::ty_params_to_tys(ccx.tcx, tps),
+                     vtables: None,
+                     bounds: @~[]};
+      do option::iter(&struct_def.ctor) |ctor| {
+        trans_class_ctor(ccx, *path, ctor.node.dec, ctor.node.body,
+                         get_item_val(ccx, ctor.node.id), psubsts,
+                         ctor.node.id, local_def(id), ctor.span);
+      }
       do option::iter(&struct_def.dtor) |dtor| {
          trans_class_dtor(ccx, *path, dtor.node.body,
            dtor.node.id, None, None, local_def(id));
@@ -2118,6 +2184,10 @@ fn get_item_val(ccx: @crate_ctxt, id: ast::node_id) -> ValueRef {
                 }
             }
           }
+          ast_map::node_ctor(nm, _, ctor, _, pt) => {
+            let my_path = vec::append(*pt, ~[path_name(nm)]);
+            register_fn(ccx, ctor.span, my_path, ctor.node.id)
+          }
           ast_map::node_dtor(_, dt, parent_id, pt) => {
             /*
                 Don't just call register_fn, since we don't want to add
@@ -2642,6 +2712,7 @@ fn trans_crate(sess: session::session,
           crate_map: crate_map,
           mut uses_gc: false,
           dbg_cx: dbg_cx,
+          class_ctors: HashMap(),
           mut do_not_commit_warning_issued: false};
 
 
diff --git a/src/rustc/middle/trans/common.rs b/src/rustc/middle/trans/common.rs
index b87d690a1c3..68e957bfe70 100644
--- a/src/rustc/middle/trans/common.rs
+++ b/src/rustc/middle/trans/common.rs
@@ -170,6 +170,11 @@ type crate_ctxt = {
      // is not emitted by LLVM's GC pass when no functions use GC.
      mut uses_gc: bool,
      dbg_cx: Option<debuginfo::debug_ctxt>,
+     // Mapping from class constructors to parent class --
+     // used in base::trans_closure
+     // parent_class must be a def_id because ctors can be
+     // inlined, so the parent may be in a different crate
+     class_ctors: HashMap<ast::node_id, ast::def_id>,
      mut do_not_commit_warning_issued: bool};
 
 // Types used for llself.
diff --git a/src/rustc/middle/trans/debuginfo.rs b/src/rustc/middle/trans/debuginfo.rs
index 068ec49d6c7..6cd4b49fa3b 100644
--- a/src/rustc/middle/trans/debuginfo.rs
+++ b/src/rustc/middle/trans/debuginfo.rs
@@ -732,6 +732,10 @@ fn create_function(fcx: fn_ctxt) -> @metadata<subprogram_md> {
       ast_map::node_method(method, _, _) => {
           (method.ident, method.decl.output, method.id)
       }
+      ast_map::node_ctor(nm, _, ctor, _, _) => {
+        // FIXME: output type may be wrong (#2194)
+        (nm, ctor.node.dec.output, ctor.node.id)
+      }
       ast_map::node_expr(expr) => {
         match expr.node {
           ast::expr_fn(_, decl, _, _) => {
diff --git a/src/rustc/middle/trans/inline.rs b/src/rustc/middle/trans/inline.rs
index ce9088d4b55..76888471bf9 100644
--- a/src/rustc/middle/trans/inline.rs
+++ b/src/rustc/middle/trans/inline.rs
@@ -34,6 +34,10 @@ fn maybe_instantiate_inline(ccx: @crate_ctxt, fn_id: ast::def_id)
             trans_item(ccx, *item);
             local_def(item.id)
           }
+          csearch::found(ast::ii_ctor(ctor, _, _, _)) => {
+            ccx.external.insert(fn_id, Some(ctor.node.id));
+            local_def(ctor.node.id)
+          }
           csearch::found(ast::ii_foreign(item)) => {
             ccx.external.insert(fn_id, Some(item.id));
             local_def(item.id)
diff --git a/src/rustc/middle/trans/monomorphize.rs b/src/rustc/middle/trans/monomorphize.rs
index 32a581454d3..17eaf591c9f 100644
--- a/src/rustc/middle/trans/monomorphize.rs
+++ b/src/rustc/middle/trans/monomorphize.rs
@@ -5,7 +5,7 @@ use syntax::ast_map::{path, path_mod, path_name};
 use base::{trans_item, get_item_val, no_self, self_arg, trans_fn,
               impl_self, decl_internal_cdecl_fn,
               set_inline_hint_if_appr, set_inline_hint,
-              trans_enum_variant, trans_class_dtor,
+              trans_enum_variant, trans_class_ctor, trans_class_dtor,
               get_insn_ctxt};
 use syntax::parse::token::special_idents;
 use type_of::type_of_fn_from_ty;
@@ -71,6 +71,7 @@ fn monomorphic_fn(ccx: @crate_ctxt,
         return {val: get_item_val(ccx, fn_id.node),
                 must_cast: true};
       }
+      ast_map::node_ctor(nm, _, ct, _, pt) => (pt, nm, ct.span),
       ast_map::node_dtor(_, dtor, _, pt) =>
           (pt, special_idents::dtor, dtor.span),
       ast_map::node_trait_method(*) => {
@@ -161,6 +162,16 @@ fn monomorphic_fn(ccx: @crate_ctxt,
         meth::trans_method(ccx, pt, mth, psubsts, None, d);
         d
       }
+      ast_map::node_ctor(_, tps, ctor, parent_id, _) => {
+        // ctors don't have attrs, at least not right now
+        let d = mk_lldecl();
+        let tp_tys = ty::ty_params_to_tys(ccx.tcx, tps);
+        trans_class_ctor(ccx, pt, ctor.node.dec, ctor.node.body, d,
+               option::get_default(&psubsts,
+                        {tys:tp_tys, vtables: None, bounds: @~[]}),
+                         fn_id.node, parent_id, ctor.span);
+        d
+      }
       ast_map::node_dtor(_, dtor, _, pt) => {
         let parent_id = match ty::ty_to_def_id(ty::node_id_to_type(ccx.tcx,
                                               dtor.node.self_id)) {
diff --git a/src/rustc/middle/trans/reachable.rs b/src/rustc/middle/trans/reachable.rs
index bfb8de76a6c..3c4439c918f 100644
--- a/src/rustc/middle/trans/reachable.rs
+++ b/src/rustc/middle/trans/reachable.rs
@@ -59,6 +59,10 @@ fn traverse_def_id(cx: ctx, did: def_id) {
         cx.rmap.insert(item.id, ());
       }
       ast_map::node_variant(v, _, _) => { cx.rmap.insert(v.node.id, ()); }
+      // If it's a ctor, consider the parent reachable
+      ast_map::node_ctor(_, _, _, parent_id, _) => {
+        traverse_def_id(cx, parent_id);
+      }
       _ => ()
     }
 }
@@ -100,6 +104,13 @@ fn traverse_public_item(cx: ctx, item: @item) {
         }
       }
       item_class(struct_def, tps) => {
+        do option::iter(&struct_def.ctor) |ctor| {
+            cx.rmap.insert(ctor.node.id, ());
+            if tps.len() > 0u || attr::find_inline_attr(ctor.node.attrs)
+                     != attr::ia_none {
+                traverse_inline_body(cx, ctor.node.body);
+            }
+        }
         do option::iter(&struct_def.dtor) |dtor| {
             cx.rmap.insert(dtor.node.id, ());
             if tps.len() > 0u || attr::find_inline_attr(dtor.node.attrs)
diff --git a/src/rustc/middle/trans/type_use.rs b/src/rustc/middle/trans/type_use.rs
index 1f9ad20dd03..ee247eb5db7 100644
--- a/src/rustc/middle/trans/type_use.rs
+++ b/src/rustc/middle/trans/type_use.rs
@@ -109,6 +109,9 @@ fn type_uses_for(ccx: @crate_ctxt, fn_id: def_id, n_tps: uint)
             for uint::range(0u, n_tps) |n| { cx.uses[n] |= flags;}
         }
       }
+      ast_map::node_ctor(_, _, ctor, _, _) => {
+        handle_body(cx, ctor.node.body);
+      }
       ast_map::node_dtor(_, dtor, _, _) => {
         handle_body(cx, dtor.node.body);
       }
diff --git a/src/rustc/middle/ty.rs b/src/rustc/middle/ty.rs
index ff916d55b6f..bbc3a06fb67 100644
--- a/src/rustc/middle/ty.rs
+++ b/src/rustc/middle/ty.rs
@@ -3318,7 +3318,7 @@ fn note_and_explain_type_err(cx: ctxt, err: &type_err) {
 
 fn def_has_ty_params(def: ast::def) -> bool {
     match def {
-      ast::def_fn(_, _) | ast::def_variant(_, _) | ast::def_class(_)
+      ast::def_fn(_, _) | ast::def_variant(_, _) | ast::def_class(_, _)
         => true,
       _ => false
     }
@@ -3492,6 +3492,9 @@ fn item_path(cx: ctxt, id: ast::def_id) -> ast_map::path {
                             ast_map::path_name(variant.node.name))
           }
 
+          ast_map::node_ctor(nm, _, _, _, path) => {
+            vec::append_one(*path, ast_map::path_name(nm))
+          }
           ast_map::node_dtor(_, _, _, path) => {
             vec::append_one(*path, ast_map::path_name(
                 syntax::parse::token::special_idents::literally_dtor))
diff --git a/src/rustc/middle/typeck/astconv.rs b/src/rustc/middle/typeck/astconv.rs
index 389c1adb016..14797fcdd6b 100644
--- a/src/rustc/middle/typeck/astconv.rs
+++ b/src/rustc/middle/typeck/astconv.rs
@@ -323,7 +323,7 @@ fn ast_ty_to_ty<AC: ast_conv, RS: region_scope Copy Owned>(
           Some(d) => d
         };
         match a_def {
-          ast::def_ty(did) | ast::def_class(did) => {
+          ast::def_ty(did) | ast::def_class(did, _) => {
             ast_path_to_ty(self, rscope, did, path, id).ty
           }
           ast::def_prim_ty(nty) => {
diff --git a/src/rustc/middle/typeck/check.rs b/src/rustc/middle/typeck/check.rs
index 1d921497ccf..7cc2c8b0ad7 100644
--- a/src/rustc/middle/typeck/check.rs
+++ b/src/rustc/middle/typeck/check.rs
@@ -451,6 +451,18 @@ fn check_struct(ccx: @crate_ctxt, struct_def: @ast::struct_def,
     let tcx = ccx.tcx;
     let self_ty = ty::node_id_to_type(tcx, id);
 
+    do option::iter(&struct_def.ctor) |ctor| {
+        let class_t = {self_ty: self_ty,
+                       self_id: ctor.node.self_id,
+                       def_id: local_def(id),
+                       explicit_self: {node: ast::sty_by_ref,
+                                       span: ast_util::dummy_sp()}};
+        // typecheck the ctor
+        check_bare_fn(ccx, ctor.node.dec,
+                      ctor.node.body, ctor.node.id,
+                      Some(class_t));
+    }
+
     do option::iter(&struct_def.dtor) |dtor| {
         let class_t = {self_ty: self_ty,
                        self_id: dtor.node.self_id,
@@ -1913,7 +1925,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
         // Resolve the path.
         let class_id;
         match tcx.def_map.find(id) {
-            Some(ast::def_class(type_def_id)) => {
+            Some(ast::def_class(type_def_id, _)) => {
                 class_id = type_def_id;
             }
             _ => {
@@ -2400,7 +2412,7 @@ fn ty_param_bounds_and_ty_for_def(fcx: @fn_ctxt, sp: span, defn: ast::def) ->
 
       ast::def_fn(id, _) | ast::def_static_method(id, _) |
       ast::def_const(id) | ast::def_variant(_, id) |
-      ast::def_class(id) => {
+      ast::def_class(id, _) => {
         return ty::lookup_item_type(fcx.ccx.tcx, id);
       }
       ast::def_upvar(_, inner, _, _) => {
diff --git a/src/rustc/middle/typeck/check/alt.rs b/src/rustc/middle/typeck/check/alt.rs
index 24bcc2281fb..0b2e9c8ab3d 100644
--- a/src/rustc/middle/typeck/check/alt.rs
+++ b/src/rustc/middle/typeck/check/alt.rs
@@ -325,7 +325,7 @@ 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)
+            ast::def_class(supplied_def_id, _)
                     if supplied_def_id == class_id => {
                 // OK.
             }
diff --git a/src/rustc/middle/typeck/collect.rs b/src/rustc/middle/typeck/collect.rs
index 472c8a30459..18e29981af3 100644
--- a/src/rustc/middle/typeck/collect.rs
+++ b/src/rustc/middle/typeck/collect.rs
@@ -497,6 +497,30 @@ fn convert_struct(ccx: @crate_ctxt,
                   tpt: ty::ty_param_bounds_and_ty,
                   id: ast::node_id) {
     let tcx = ccx.tcx;
+    do option::iter(&struct_def.ctor) |ctor| {
+        // Write the ctor type
+        let t_args = ctor.node.dec.inputs.map(
+            |a| ty_of_arg(ccx, type_rscope(rp), *a, None) );
+        let t_res = ty::mk_class(
+            tcx, local_def(id),
+            {self_r: rscope::bound_self_region(rp),
+             self_ty: None,
+             tps: ty::ty_params_to_tys(tcx, tps)});
+        let proto = ty::proto_vstore(ty::vstore_slice(ty::re_static));
+        let t_ctor = ty::mk_fn(tcx, FnTyBase {
+            meta: FnMeta {purity: ast::impure_fn,
+                          proto: proto,
+                          bounds: @~[],
+                          ret_style: ast::return_val},
+            sig: FnSig {inputs: t_args,
+                        output: t_res}
+        });
+        write_ty_to_tcx(tcx, ctor.node.id, t_ctor);
+        tcx.tcache.insert(local_def(ctor.node.id),
+                          {bounds: tpt.bounds,
+                           region_param: rp,
+                           ty: t_ctor});
+    }
 
     do option::iter(&struct_def.dtor) |dtor| {
         // Write the dtor type
diff --git a/src/test/compile-fail/issue-2590.rs b/src/test/compile-fail/issue-2590.rs
index 0f4a4804c75..f4ccd901fb3 100644
--- a/src/test/compile-fail/issue-2590.rs
+++ b/src/test/compile-fail/issue-2590.rs
@@ -10,7 +10,7 @@ trait parse {
 
 impl parser: parse {
     fn parse() -> ~[int] {
-        dvec::unwrap(move self.tokens) //~ ERROR moving out of immutable field
+        dvec::unwrap(move self.tokens) //~ ERROR illegal move from self
     }
 }
 
diff --git a/src/test/compile-fail/regions-glb-free-free.rs b/src/test/compile-fail/regions-glb-free-free.rs
index e4913f7056e..223665381da 100644
--- a/src/test/compile-fail/regions-glb-free-free.rs
+++ b/src/test/compile-fail/regions-glb-free-free.rs
@@ -19,7 +19,7 @@ mod argparse {
         fn set_desc(self, s: &str) -> Flag {
             Flag { //~ ERROR cannot infer an appropriate lifetime
                 name: self.name,
-                desc: s, //~ ERROR cannot infer an appropriate lifetime
+                desc: s,
                 max_count: self.max_count,
                 value: self.value
             }