about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustsyntax/ast.rs2
-rw-r--r--src/librustsyntax/parse/parser.rs4
-rw-r--r--src/rustc/driver/driver.rs3
-rw-r--r--src/rustc/middle/borrowck.rs26
-rw-r--r--src/rustc/middle/capture.rs35
-rw-r--r--src/rustc/middle/liveness.rs1502
-rw-r--r--src/rustc/rustc.rc1
-rw-r--r--src/test/compile-fail/and-init.rs8
-rw-r--r--src/test/compile-fail/assign-imm-local-twice.rs9
-rw-r--r--src/test/compile-fail/block-uninit.rs4
-rw-r--r--src/test/compile-fail/break-uninit.rs14
-rw-r--r--src/test/compile-fail/break-uninit2.rs14
-rw-r--r--src/test/compile-fail/fn-expr-type-state.rs7
-rw-r--r--src/test/compile-fail/fn-expr-typestate-2.rs6
-rw-r--r--src/test/compile-fail/fru-typestate.rs12
-rw-r--r--src/test/compile-fail/issue-2163.rs5
-rw-r--r--src/test/compile-fail/liveness-and-init.rs6
-rw-r--r--src/test/compile-fail/liveness-assign-imm-local-in-loop.rs11
-rw-r--r--src/test/compile-fail/liveness-assign-imm-local-in-op-eq.rs9
-rw-r--r--src/test/compile-fail/liveness-assign-imm-local-in-swap.rs18
-rw-r--r--src/test/compile-fail/liveness-bad-bang-2.rs (renamed from src/test/compile-fail/bad-bang-ann-2.rs)0
-rw-r--r--src/test/compile-fail/liveness-block-unint.rs7
-rw-r--r--src/test/compile-fail/liveness-break-uninit-2.rs16
-rw-r--r--src/test/compile-fail/liveness-break-uninit.rs16
-rw-r--r--src/test/compile-fail/liveness-closure-require-ret.rs (renamed from src/test/compile-fail/block-require-return.rs)0
-rw-r--r--src/test/compile-fail/liveness-ctor-access-self-with-uninit-fields.rs11
-rw-r--r--src/test/compile-fail/liveness-ctor-field-never-init.rs7
-rw-r--r--src/test/compile-fail/liveness-ctor-uninit-field.rs14
-rw-r--r--src/test/compile-fail/liveness-ctor-uninit-var.rs (renamed from src/test/compile-fail/ctor-uninit-var.rs)3
-rw-r--r--src/test/compile-fail/liveness-forgot-ret.rs (renamed from src/test/compile-fail/forgot-ret.rs)0
-rw-r--r--src/test/compile-fail/liveness-if-no-else.rs6
-rw-r--r--src/test/compile-fail/liveness-if-with-else.rs (renamed from src/test/compile-fail/use-uninit-3.rs)4
-rw-r--r--src/test/compile-fail/liveness-init-in-called-fn-expr.rs7
-rw-r--r--src/test/compile-fail/liveness-init-in-fn-expr.rs7
-rw-r--r--src/test/compile-fail/liveness-init-in-fru.rs8
-rw-r--r--src/test/compile-fail/liveness-init-op-equal.rs8
-rw-r--r--src/test/compile-fail/liveness-init-plus-equal.rs8
-rw-r--r--src/test/compile-fail/liveness-issue-2163.rs5
-rw-r--r--src/test/compile-fail/liveness-missing-ret2.rs (renamed from src/test/compile-fail/missing-return2.rs)0
-rw-r--r--src/test/compile-fail/liveness-move-from-mode.rs10
-rw-r--r--src/test/compile-fail/liveness-move-in-loop.rs16
-rw-r--r--src/test/compile-fail/liveness-move-in-while.rs (renamed from src/test/compile-fail/while-constraints.rs)5
-rw-r--r--src/test/compile-fail/liveness-or-init.rs6
-rw-r--r--src/test/compile-fail/liveness-return.rs6
-rw-r--r--src/test/compile-fail/liveness-swap-uninit.rs5
-rw-r--r--src/test/compile-fail/liveness-uninit-after-item.rs6
-rw-r--r--src/test/compile-fail/liveness-uninit.rs6
-rw-r--r--src/test/compile-fail/liveness-use-after-move.rs5
-rw-r--r--src/test/compile-fail/liveness-use-after-send.rs (renamed from src/test/compile-fail/use-after-send.rs)5
-rw-r--r--src/test/compile-fail/liveness-use-in-index-lvalue.rs6
-rw-r--r--src/test/compile-fail/liveness-while-break.rs12
-rw-r--r--src/test/compile-fail/liveness-while-cond.rs4
-rw-r--r--src/test/compile-fail/liveness-while.rs7
-rw-r--r--src/test/compile-fail/move-arg.rs4
-rw-r--r--src/test/compile-fail/or-init.rs8
-rw-r--r--src/test/compile-fail/return-uninit.rs5
-rw-r--r--src/test/compile-fail/swap-uninit.rs3
-rw-r--r--src/test/compile-fail/tstate-and-init.rs7
-rw-r--r--src/test/compile-fail/tstate-block-uninit.rs11
-rw-r--r--src/test/compile-fail/tstate-break-uninit-2.rs16
-rw-r--r--src/test/compile-fail/tstate-break-uninit.rs16
-rw-r--r--src/test/compile-fail/tstate-ctor-unsat.rs25
-rw-r--r--src/test/compile-fail/tstate-fru.rs13
-rw-r--r--src/test/compile-fail/tstate-if-no-else.rs10
-rw-r--r--src/test/compile-fail/tstate-if-with-else.rs14
-rw-r--r--src/test/compile-fail/tstate-loop-constraints.rs19
-rw-r--r--src/test/compile-fail/tstate-or-init.rs7
-rw-r--r--src/test/compile-fail/tstate-return.rs9
-rw-r--r--src/test/compile-fail/tstate-unsat-after-item.rs9
-rw-r--r--src/test/compile-fail/tstate-unsat-in-called-fn-expr.rs9
-rw-r--r--src/test/compile-fail/tstate-unsat-in-fn-expr.rs9
-rw-r--r--src/test/compile-fail/tstate-unsat.rs7
-rw-r--r--src/test/compile-fail/tstate-while-break.rs15
-rw-r--r--src/test/compile-fail/tstate-while-cond.rs7
-rw-r--r--src/test/compile-fail/tstate-while-loop-unsat-constriants.rs (renamed from src/test/compile-fail/while-loop-pred-constraints.rs)0
-rw-r--r--src/test/compile-fail/tstate-while.rs10
-rw-r--r--src/test/compile-fail/uninit-after-item.rs7
-rw-r--r--src/test/compile-fail/use-after-move.rs2
-rw-r--r--src/test/compile-fail/use-uninit-2.rs5
-rw-r--r--src/test/compile-fail/use-uninit.rs5
-rw-r--r--src/test/compile-fail/while-bypass.rs5
-rw-r--r--src/test/compile-fail/while-expr.rs3
-rw-r--r--src/test/compile-fail/while-loop-constraints.rs14
-rw-r--r--src/test/compile-fail/writing-through-uninit-vec.rs5
-rw-r--r--src/test/run-pass/liveness-assign-imm-local-after-loop.rs10
-rw-r--r--src/test/run-pass/liveness-assign-imm-local-after-ret.rs9
-rw-r--r--src/test/run-pass/liveness-loop-break.rs14
-rw-r--r--src/test/run-pass/liveness-move-in-loop.rs15
-rw-r--r--src/test/run-pass/tstate-loop-break.rs17
89 files changed, 2104 insertions, 162 deletions
diff --git a/src/librustsyntax/ast.rs b/src/librustsyntax/ast.rs
index 05a5a17b2f7..3404f277e8d 100644
--- a/src/librustsyntax/ast.rs
+++ b/src/librustsyntax/ast.rs
@@ -351,7 +351,7 @@ enum expr_ {
 }
 
 #[auto_serialize]
-type capture_item = {
+type capture_item = @{
     id: int,
     is_move: bool,
     name: ident, // Currently, can only capture a local var.
diff --git a/src/librustsyntax/parse/parser.rs b/src/librustsyntax/parse/parser.rs
index c81d9ace376..51b26373c36 100644
--- a/src/librustsyntax/parse/parser.rs
+++ b/src/librustsyntax/parse/parser.rs
@@ -434,7 +434,7 @@ class parser {
         fn parse_capture_item(p:parser, is_move: bool) -> capture_item {
             let sp = mk_sp(p.span.lo, p.span.hi);
             let ident = parse_ident(p);
-            {id: p.get_id(), is_move: is_move, name: ident, span: sp}
+            @{id: p.get_id(), is_move: is_move, name: ident, span: sp}
         }
 
         if eat_keyword(self, "move") {
@@ -1710,7 +1710,7 @@ class parser {
                     let id = p.get_id();
                     let sp = mk_sp(p.span.lo, p.span.hi);
                     let ident = parse_ident(p);
-                    res += [{id:id, is_move: is_move, name:ident, span:sp}];
+                    res += [@{id:id, is_move: is_move, name:ident, span:sp}];
                     if !eat(p, token::COMMA) {
                         ret res;
                     }
diff --git a/src/rustc/driver/driver.rs b/src/rustc/driver/driver.rs
index 82a42700ea1..0cb99af2141 100644
--- a/src/rustc/driver/driver.rs
+++ b/src/rustc/driver/driver.rs
@@ -192,6 +192,9 @@ fn compile_upto(sess: session, cfg: ast::crate_cfg,
          bind middle::check_loop::check_crate(ty_cx, crate));
     time(time_passes, "alt checking",
          bind middle::check_alt::check_crate(ty_cx, crate));
+    let _last_use_map =
+        time(time_passes, "liveness checking",
+             bind middle::liveness::check_crate(ty_cx, method_map, crate));
     time(time_passes, "typestate checking",
          bind middle::tstate::ck::check_crate(ty_cx, crate));
     let (root_map, mutbl_map) = time(
diff --git a/src/rustc/middle/borrowck.rs b/src/rustc/middle/borrowck.rs
index 85ee5b1c957..8cf6671a3d4 100644
--- a/src/rustc/middle/borrowck.rs
+++ b/src/rustc/middle/borrowck.rs
@@ -541,6 +541,15 @@ enum assignment_type {
 }
 
 impl methods for assignment_type {
+    fn checked_by_liveness() -> bool {
+        // the liveness pass guarantees that immutable local variables
+        // are only assigned once; but it doesn't consider &mut
+        alt self {
+          at_straight_up {true}
+          at_swap {true}
+          at_mutbl_ref {false}
+        }
+    }
     fn ing_form(desc: str) -> str {
         alt self {
           at_straight_up { "assigning to " + desc }
@@ -717,6 +726,13 @@ impl methods for check_loan_ctxt {
         }
     }
 
+    fn is_local_variable(cmt: cmt) -> bool {
+        alt cmt.cat {
+          cat_local(_) {true}
+          _ {false}
+        }
+    }
+
     fn is_self_field(cmt: cmt) -> bool {
         alt cmt.cat {
           cat_comp(cmt_base, comp_field(_)) {
@@ -735,9 +751,13 @@ impl methods for check_loan_ctxt {
         #debug["check_assignment(cmt=%s)",
                self.bccx.cmt_to_repr(cmt)];
 
-        // check that the lvalue `ex` is assignable, but be careful
-        // because assigning to self.foo in a ctor is always allowed.
-        if !self.in_ctor || !self.is_self_field(cmt) {
+        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 {
             alt cmt.mutbl {
               m_mutbl { /*ok*/ }
               m_const | m_imm {
diff --git a/src/rustc/middle/capture.rs b/src/rustc/middle/capture.rs
index 84da4ee2136..d6edd2b935d 100644
--- a/src/rustc/middle/capture.rs
+++ b/src/rustc/middle/capture.rs
@@ -1,5 +1,6 @@
 import syntax::{ast, ast_util};
 import driver::session::session;
+import syntax::codemap::span;
 import std::map;
 import std::map::hashmap;
 
@@ -14,15 +15,17 @@ export cap_drop;
 export cap_ref;
 
 enum capture_mode {
-    cap_copy, //< Copy the value into the closure.
-    cap_move, //< Move the value into the closure.
-    cap_drop, //< Drop value after creating closure.
-    cap_ref,  //< Reference directly from parent stack frame (block fn).
+    cap_copy, // Copy the value into the closure.
+    cap_move, // Move the value into the closure.
+    cap_drop, // Drop value after creating closure.
+    cap_ref,  // Reference directly from parent stack frame (block fn).
 }
 
 type capture_var = {
-    def: ast::def,     //< The variable being accessed free.
-    mode: capture_mode //< How is the variable being accessed.
+    def: ast::def,                       // Variable being accessed free
+    span: span,                          // Location of access or cap item
+    cap_item: option<ast::capture_item>, // Capture item, if any
+    mode: capture_mode                   // How variable is being accessed
 };
 
 type capture_map = map::hashmap<ast::def_id, capture_var>;
@@ -70,15 +73,24 @@ fn compute_capture_vars(tcx: ty::ctxt,
             // if we are moving the value in, but it's not actually used,
             // must drop it.
             if vec::any(*freevars, {|fv| fv.def == cap_def}) {
-                cap_map.insert(cap_def_id, { def:cap_def, mode:cap_move });
+                cap_map.insert(cap_def_id, {def:cap_def,
+                                            span: cap_item.span,
+                                            cap_item: some(cap_item),
+                                            mode:cap_move});
             } else {
-                cap_map.insert(cap_def_id, { def:cap_def, mode:cap_drop });
+                cap_map.insert(cap_def_id, {def:cap_def,
+                                            span: cap_item.span,
+                                            cap_item: some(cap_item),
+                                            mode:cap_drop});
             }
         } else {
             // if we are copying the value in, but it's not actually used,
             // just ignore it.
             if vec::any(*freevars, {|fv| fv.def == cap_def}) {
-                cap_map.insert(cap_def_id, { def:cap_def, mode:cap_copy });
+                cap_map.insert(cap_def_id, {def:cap_def,
+                                            span: cap_item.span,
+                                            cap_item: some(cap_item),
+                                            mode:cap_copy});
             }
         }
     }
@@ -96,7 +108,10 @@ fn compute_capture_vars(tcx: ty::ctxt,
         alt cap_map.find(fvar_def_id) {
           option::some(_) { /* was explicitly named, do nothing */ }
           option::none {
-            cap_map.insert(fvar_def_id, {def:fvar.def, mode:implicit_mode});
+            cap_map.insert(fvar_def_id, {def:fvar.def,
+                                         span: fvar.span,
+                                         cap_item: none,
+                                         mode:implicit_mode});
           }
         }
     }
diff --git a/src/rustc/middle/liveness.rs b/src/rustc/middle/liveness.rs
new file mode 100644
index 00000000000..d95c58c15c8
--- /dev/null
+++ b/src/rustc/middle/liveness.rs
@@ -0,0 +1,1502 @@
+/*
+
+A classic liveness analysis based on dataflow over the AST.  Computes,
+for each local variable in a function, whether that variable is live
+at a given point.  Program execution points are identified by their
+id.
+
+# Basic idea
+
+The basic model is that each local variable is assigned an index.  We
+represent sets of local variables using a vector indexed by this
+index.  The value in the vector is either 0, indicating the variable
+is dead, or the id of an expression that uses the variable.
+
+We conceptually walk over the AST in reverse execution order.  If we
+find a use of a variable, we add it to the set of live variables.  If
+we find an assignment to a variable, we remove it from the set of live
+variables.  When we have to merge two flows, we take the union of
+those two flows---if the variable is live on both paths, we simply
+pick one id.  In the event of loops, we continue doing this until a
+fixed point is reached.
+
+## Checking initialization
+
+At the function entry point, all variables must be dead.  If this is
+not the case, we can report an error using the id found in the set of
+live variables, which identifies a use of the variable which is not
+dominated by an assignment.
+
+## Checking moves
+
+After each explicit move, the variable must be dead.
+
+## Computing last uses
+
+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`.
+
+*/
+
+import dvec::{dvec, extensions};
+import std::map::{hashmap, int_hash, str_hash};
+import syntax::{visit, ast_util};
+import syntax::print::pprust::{expr_to_str};
+import visit::vt;
+import syntax::codemap::span;
+import syntax::ast::*;
+import driver::session::session;
+import io::writer_util;
+import capture::{cap_move, cap_drop, cap_copy, cap_ref};
+
+export check_crate;
+export last_use_map;
+
+type last_use_map = hashmap<node_id, @dvec<node_id>>;
+
+enum variable = uint;
+enum live_node = uint;
+
+enum live_node_kind {
+    lnk_freevar(span),
+    lnk_expr(span),
+    lnk_vdef(span),
+    lnk_exit
+}
+
+fn check_crate(tcx: ty::ctxt,
+               method_map: typeck::method_map,
+               crate: @crate) -> last_use_map {
+    let visitor = visit::mk_vt(@{
+        visit_fn: visit_fn,
+        visit_local: visit_local,
+        visit_expr: visit_expr
+        with *visit::default_visitor()
+    });
+
+    let last_use_map = int_hash();
+    let initial_maps = @ir_maps(tcx, method_map, last_use_map);
+    visit::visit_crate(*crate, initial_maps, visitor);
+    tcx.sess.abort_if_errors();
+    ret last_use_map;
+}
+
+impl of to_str::to_str for live_node {
+    fn to_str() -> str { #fmt["ln(%u)", *self] }
+}
+
+impl of to_str::to_str for variable {
+    fn to_str() -> str { #fmt["v(%u)", *self] }
+}
+
+// ______________________________________________________________________
+// Creating ir_maps
+//
+// This is the first pass and the one that drives the main
+// computation.  It walks up and down the IR once.  On the way down,
+// we count for each function the number of variables as well as
+// liveness nodes.  A liveness node is basically an expression or
+// capture clause that does something of interest: either it has
+// interesting control flow or it uses/defines a local variable.
+//
+// On the way back up, at each function node we create liveness sets
+// (we now know precisely how big to make our various vectors and so
+// forth) and then do the data-flow propagation to compute the set
+// of live variables at each program point.
+//
+// Finally, we run back over the IR one last time and, using the
+// computed liveness, check various safety conditions.  For example,
+// there must be no live nodes at the definition site for a variable
+// unless it has an initializer.  Similarly, each non-mutable local
+// variable must not be assigned if there is some successor
+// assignment.  And so forth.
+
+impl methods for live_node {
+    fn is_valid() -> bool { *self != uint::max_value }
+}
+
+fn invalid_node() -> live_node { live_node(uint::max_value) }
+
+enum relevant_def { rdef_var(node_id), rdef_self }
+
+type capture_info = {ln: live_node, is_move: bool, rv: relevant_def};
+
+type var_info = {id: node_id, name: str};
+
+fn relevant_def(def: def) -> option<relevant_def> {
+    alt def {
+      def_self(_) {some(rdef_self)}
+      def_arg(nid, _) | def_local(nid, _) {some(rdef_var(nid))}
+      _ {none}
+    }
+}
+
+class ir_maps {
+    let tcx: ty::ctxt;
+    let method_map: typeck::method_map;
+    let last_use_map: last_use_map;
+
+    let mut num_live_nodes: uint;
+    let mut num_vars: uint;
+    let live_node_map: hashmap<node_id, live_node>;
+    let variable_map: hashmap<node_id, variable>;
+    let field_map: hashmap<str, variable>;
+    let capture_map: hashmap<node_id, @[capture_info]>;
+    let mut var_infos: [var_info];
+    let mut lnks: [live_node_kind];
+
+    new(tcx: ty::ctxt, method_map: typeck::method_map,
+        last_use_map: hashmap<node_id, @dvec<node_id>>) {
+        self.tcx = tcx;
+        self.method_map = method_map;
+        self.last_use_map = last_use_map;
+
+        self.num_live_nodes = 0u;
+        self.num_vars = 0u;
+        self.live_node_map = int_hash();
+        self.variable_map = int_hash();
+        self.capture_map = int_hash();
+        self.field_map = str_hash();
+        self.var_infos = [];
+        self.lnks = [];
+    }
+
+    fn add_live_node(lnk: live_node_kind) -> live_node {
+        let ln = live_node(self.num_live_nodes);
+        self.lnks += [lnk];
+        self.num_live_nodes += 1u;
+
+        #debug["%s is of kind %?", ln.to_str(), lnk];
+
+        ln
+    }
+
+    fn add_live_node_for_node(node_id: node_id, lnk: live_node_kind) {
+        let ln = self.add_live_node(lnk);
+        self.live_node_map.insert(node_id, ln);
+
+        #debug["%s is node %d", ln.to_str(), node_id];
+    }
+
+    fn add_variable(node_id: node_id, name: str) -> variable {
+        let v = variable(self.num_vars);
+        self.variable_map.insert(node_id, v);
+        self.var_infos += [{id:node_id, name:name}];
+        self.num_vars += 1u;
+
+        #debug["%s is node %d", v.to_str(), node_id];
+
+        v
+    }
+
+    fn variable(node_id: node_id, span: span) -> variable {
+        alt self.variable_map.find(node_id) {
+          some(var) {var}
+          none {
+            self.tcx.sess.span_bug(
+                span, "No variable registered for this id");
+          }
+        }
+    }
+
+    fn set_captures(node_id: node_id, +cs: [capture_info]) {
+        self.capture_map.insert(node_id, @cs);
+    }
+
+    fn captures(expr: @expr) -> @[capture_info] {
+        alt self.capture_map.find(expr.id) {
+          some(caps) {caps}
+          none {
+            self.tcx.sess.span_bug(expr.span, "no registered caps");
+          }
+        }
+    }
+
+    fn lnk(ln: live_node) -> live_node_kind {
+        self.lnks[*ln]
+    }
+
+    fn add_last_use(expr_id: node_id, var: variable) {
+        let v = alt self.last_use_map.find(expr_id) {
+          some(v) { v }
+          none {
+            let v = @dvec();
+            self.last_use_map.insert(expr_id, v);
+            v
+          }
+        };
+        let {id, name} = self.var_infos[*var];
+        #debug["Node %d is a last use of variable %d / %s",
+               expr_id, id, name];
+        (*v).push(id);
+    }
+}
+
+fn visit_fn(fk: visit::fn_kind, decl: fn_decl, body: blk,
+            sp: span, id: node_id, &&self: @ir_maps, v: vt<@ir_maps>) {
+    #debug["visit_fn: id=%d", id];
+    let _i = util::common::indenter();
+
+    // swap in a new set of IR maps for this function body:
+    let fn_maps = @ir_maps(self.tcx, self.method_map,
+                           self.last_use_map);
+
+    #debug["creating fn_maps: %x", ptr::addr_of(*fn_maps) as uint];
+
+    for decl.inputs.each { |arg|
+        #debug["adding argument %d", arg.id];
+        (*fn_maps).add_variable(arg.id, arg.ident);
+    }
+
+    // gather up the various local variables, significant expressions,
+    // and so forth:
+    visit::visit_fn(fk, decl, body, sp, id, fn_maps, v);
+
+    alt 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 ret or fail
+    // - implicit_ret_var is a pseudo-variable that represents
+    //   an implicit return
+    let specials = {
+        exit_ln: (*fn_maps).add_live_node(lnk_exit),
+        fallthrough_ln: (*fn_maps).add_live_node(lnk_exit),
+        no_ret_var: (*fn_maps).add_variable(0, "<no_ret>"),
+        self_var: (*fn_maps).add_variable(0, "self")
+    };
+
+    // compute liveness
+    let lsets = @liveness(fn_maps, specials);
+    let entry_ln = (*lsets).compute(body);
+
+    // check for various error conditions
+    let check_vt = visit::mk_vt(@{
+        visit_fn: check_fn,
+        visit_local: check_local,
+        visit_expr: check_expr
+        with *visit::default_visitor()
+    });
+    check_vt.visit_block(body, lsets, check_vt);
+    lsets.check_ret(id, sp, fk, entry_ln);
+    lsets.check_fields(sp, entry_ln);
+}
+
+fn add_class_fields(self: @ir_maps, 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_ty.id.node, #fmt["self.%s", field_ty.ident]);
+        self.field_map.insert(field_ty.ident, var);
+    }
+}
+
+fn visit_local(local: @local, &&self: @ir_maps, vt: vt<@ir_maps>) {
+    let def_map = self.tcx.def_map;
+    pat_util::pat_bindings(def_map, local.node.pat) { |p_id, sp, path|
+        #debug["adding local variable %d", p_id];
+        let name = ast_util::path_to_ident(path);
+        (*self).add_live_node_for_node(p_id, lnk_vdef(sp));
+        (*self).add_variable(p_id, name);
+    }
+    visit::visit_local(local, self, vt);
+}
+
+fn visit_expr(expr: @expr, &&self: @ir_maps, vt: vt<@ir_maps>) {
+    alt expr.node {
+      // live nodes required for uses or definitions of variables:
+      expr_path(_) {
+        let def = self.tcx.def_map.get(expr.id);
+        #debug["expr %d: path that leads to %?", expr.id, def];
+        if relevant_def(def).is_some() {
+            (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span));
+        }
+        visit::visit_expr(expr, self, vt);
+      }
+      expr_fn(_, _, _, cap_clause) |
+      expr_fn_block(_, _, cap_clause) {
+        // Make a live_node for each captured variable, with the span
+        // being the location that the variable is used.  This results
+        // in better error messages than just pointing at the closure
+        // construction site.
+        let proto = ty::ty_fn_proto(ty::expr_ty(self.tcx, expr));
+        let cvs = capture::compute_capture_vars(self.tcx, expr.id,
+                                                proto, cap_clause);
+        let mut call_caps = [];
+        for cvs.each { |cv|
+            alt relevant_def(cv.def) {
+              some(rv) {
+                let cv_ln = (*self).add_live_node(lnk_freevar(cv.span));
+                let is_move = alt cv.mode {
+                  cap_move | cap_drop {true} // var must be dead afterwards
+                  cap_copy | cap_ref {false} // var can still be used
+                };
+                call_caps += [{ln: cv_ln, is_move: is_move, rv: rv}];
+              }
+              none {}
+            }
+        }
+        (*self).set_captures(expr.id, call_caps);
+
+        visit::visit_expr(expr, self, vt);
+      }
+
+      // live nodes required for interesting control flow:
+      expr_if_check(*) | expr_if(*) | expr_alt(*) |
+      expr_while(*) | expr_loop(*) {
+        (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span));
+        visit::visit_expr(expr, self, vt);
+      }
+      expr_binary(op, _, _) if ast_util::lazy_binop(op) {
+        (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span));
+        visit::visit_expr(expr, self, vt);
+      }
+
+      // otherwise, live nodes are not required:
+      expr_index(*) | expr_field(*) | expr_vstore(*) |
+      expr_vec(*) | expr_rec(*) | expr_call(*) | expr_tup(*) |
+      expr_bind(*) | expr_new(*) | expr_log(*) | expr_binary(*) |
+      expr_assert(*) | expr_check(*) | expr_addr_of(*) | expr_copy(*) |
+      expr_loop_body(*) | expr_cast(*) | expr_unary(*) | expr_fail(*) |
+      expr_break | expr_cont | expr_lit(_) | expr_ret(*) |
+      expr_block(*) | expr_move(*) | expr_assign(*) | expr_swap(*) |
+      expr_assign_op(*) | expr_mac(*) {
+          visit::visit_expr(expr, self, vt);
+      }
+    }
+}
+
+// ______________________________________________________________________
+// Computing liveness sets
+//
+// Actually we compute just a bit more than just liveness, but we use
+// the same basic propagation framework in all cases.
+
+type users = {
+    reader: live_node,
+    writer: live_node
+};
+
+fn invalid_users() -> users {
+    {reader: invalid_node(), writer: invalid_node()}
+}
+
+type specials = {
+    exit_ln: live_node,
+    fallthrough_ln: live_node,
+    no_ret_var: variable,
+    self_var: variable
+};
+
+const ACC_READ: uint = 1u;
+const ACC_WRITE: uint = 2u;
+
+class liveness {
+    let tcx: ty::ctxt;
+    let ir: @ir_maps;
+    let s: specials;
+    let successors: [mut live_node];
+    let users: [mut users];
+    let mut break_ln: live_node;
+    let mut cont_ln: live_node;
+
+    new(ir: @ir_maps, specials: specials) {
+        self.ir = ir;
+        self.tcx = ir.tcx;
+        self.s = specials;
+        self.successors =
+            vec::to_mut(
+                vec::from_elem(self.ir.num_live_nodes,
+                               invalid_node()));
+        self.users =
+            vec::to_mut(
+                vec::from_elem(self.ir.num_live_nodes * self.ir.num_vars,
+                               invalid_users()));
+        self.break_ln = invalid_node();
+        self.cont_ln = invalid_node();
+    }
+
+    // _______________________________________________________________________
+
+    fn live_node(node_id: node_id, span: span) -> live_node {
+        alt self.ir.live_node_map.find(node_id) {
+          some(ln) {ln}
+          none {
+            // This must be a mismatch between the ir_map construction
+            // above and the propagation code below; the two sets of
+            // code have to agree about which AST nodes are worth
+            // creating liveness nodes for.
+            self.tcx.sess.span_bug(
+                span, #fmt["No live node registered for node %d",
+                           node_id]);
+          }
+        }
+    }
+
+    fn variable_from_rdef(rv: relevant_def, span: span) -> variable {
+        alt rv {
+          rdef_self {self.s.self_var}
+          rdef_var(nid) {self.variable(nid, span)}
+        }
+    }
+
+    fn variable(node_id: node_id, span: span) -> variable {
+        (*self.ir).variable(node_id, span)
+    }
+
+    fn variable_from_def_map(node_id: node_id,
+                             span: span) -> option<variable> {
+        alt self.tcx.def_map.find(node_id) {
+          some(def) {
+            relevant_def(def).map { |rdef|
+              self.variable_from_rdef(rdef, span)
+            }
+          }
+          none {
+            self.tcx.sess.span_bug(
+                span, "Not present in def map")
+          }
+        }
+    }
+
+    fn idx(ln: live_node, var: variable) -> uint {
+        *ln * self.ir.num_vars + *var
+    }
+
+    fn live_on_entry(ln: live_node, var: variable)
+        -> option<live_node_kind> {
+
+        assert ln.is_valid();
+        let reader = self.users[self.idx(ln, var)].reader;
+        if reader.is_valid() {some((*self.ir).lnk(reader))} else {none}
+    }
+
+    fn live_on_exit(ln: live_node, var: variable)
+        -> option<live_node_kind> {
+
+        self.live_on_entry(self.successors[*ln], var)
+    }
+
+    fn assigned_on_entry(ln: live_node, var: variable)
+        -> option<live_node_kind> {
+
+        assert ln.is_valid();
+        let writer = self.users[self.idx(ln, var)].writer;
+        if writer.is_valid() {some((*self.ir).lnk(writer))} else {none}
+    }
+
+    fn assigned_on_exit(ln: live_node, var: variable)
+        -> option<live_node_kind> {
+
+        self.assigned_on_entry(self.successors[*ln], var)
+    }
+
+    fn indices(ln: live_node, op: fn(uint)) {
+        let node_base_idx = self.idx(ln, variable(0u));
+        uint::range(0u, self.ir.num_vars) { |var_idx|
+            op(node_base_idx + var_idx)
+        }
+    }
+
+    fn indices2(ln: live_node, succ_ln: live_node,
+                op: fn(uint, uint)) {
+        let node_base_idx = self.idx(ln, variable(0u));
+        let succ_base_idx = self.idx(succ_ln, variable(0u));
+        uint::range(0u, self.ir.num_vars) { |var_idx|
+            op(node_base_idx + var_idx, succ_base_idx + var_idx);
+        }
+    }
+
+    fn write_vars(wr: io::writer,
+                  ln: live_node,
+                  test: fn(uint) -> live_node) {
+        let node_base_idx = self.idx(ln, variable(0u));
+        uint::range(0u, self.ir.num_vars) { |var_idx|
+            let idx = node_base_idx + var_idx;
+            if test(idx).is_valid() {
+                wr.write_str(" ");
+                wr.write_str(variable(var_idx).to_str());
+            }
+        }
+    }
+
+    fn ln_str(ln: live_node) -> str {
+        io::with_str_writer { |wr|
+            wr.write_str("[ln(");
+            wr.write_uint(*ln);
+            wr.write_str(") of kind ");
+            wr.write_str(#fmt["%?", self.ir.lnks[*ln]]);
+            wr.write_str(" reads");
+            self.write_vars(wr, ln, {|idx| self.users[idx].reader});
+            wr.write_str("  writes");
+            self.write_vars(wr, ln, {|idx| self.users[idx].writer});
+            wr.write_str(" ");
+            wr.write_str(" precedes ");
+            wr.write_str(self.successors[*ln].to_str());
+            wr.write_str("]");
+        }
+    }
+
+    fn init_empty(ln: live_node, succ_ln: live_node) {
+        self.successors[*ln] = succ_ln;
+
+        // It is not necessary to initialize the
+        // values to empty because this is the value
+        // they have when they are created, and the sets
+        // only grow during iterations.
+        //
+        // self.indices(ln) { |idx|
+        //     self.users[idx] = invalid_users();
+        // }
+    }
+
+    fn init_from_succ(ln: live_node, succ_ln: live_node) {
+        // more efficient version of init_empty() / merge_from_succ()
+        self.successors[*ln] = succ_ln;
+        self.indices2(ln, succ_ln) { |idx, succ_idx|
+            self.users[idx] = self.users[succ_idx];
+        }
+        #debug["init_from_succ(ln=%s, succ=%s)",
+               self.ln_str(ln), self.ln_str(succ_ln)];
+    }
+
+    fn merge_from_succ(ln: live_node, succ_ln: live_node,
+                       first_merge: bool) -> bool {
+        if ln == succ_ln { ret false; }
+
+        let mut changed = false;
+        self.indices2(ln, succ_ln) { |idx, succ_idx|
+            changed |= copy_if_invalid(self.users[succ_idx].reader,
+                                       self.users[idx].reader);
+            changed |= copy_if_invalid(self.users[succ_idx].writer,
+                                       self.users[idx].writer);
+        }
+
+        #debug["merge_from_succ(ln=%s, succ=%s, first_merge=%b, changed=%b)",
+               ln.to_str(), self.ln_str(succ_ln), first_merge, changed];
+        ret changed;
+
+        fn copy_if_invalid(src: live_node, &dst: live_node) -> bool {
+            if src.is_valid() {
+                if !dst.is_valid() {
+                    dst = src;
+                    ret true;
+                }
+            }
+            ret false;
+        }
+    }
+
+    // Indicates that a local variable was *defined*; we know that no
+    // uses of the variable can precede the definition (resolve checks
+    // this) so we just clear out all the data.
+    fn define(writer: live_node, var: variable) {
+        let idx = self.idx(writer, var);
+        self.users[idx].reader = invalid_node();
+        self.users[idx].writer = invalid_node();
+
+        #debug["%s defines %s (idx=%u): %s", writer.to_str(), var.to_str(),
+               idx, self.ln_str(writer)];
+    }
+
+    // Indicates that a new value for the local variable was assigned.
+    fn write(writer: live_node, var: variable) {
+        let idx = self.idx(writer, var);
+        self.users[idx].reader = invalid_node();
+        self.users[idx].writer = writer;
+
+        #debug["%s writes %s (idx=%u): %s", writer.to_str(), var.to_str(),
+               idx, self.ln_str(writer)];
+    }
+
+    // Indicates that the current value of the local variable was used.
+    fn read(reader: live_node, var: variable) {
+        let idx = self.idx(reader, var);
+        self.users[idx].reader = reader;
+
+        #debug["%s reads %s (idx=%u): %s", reader.to_str(), var.to_str(),
+               idx, self.ln_str(reader)];
+    }
+
+    // Either read, write, or both depending on the acc bitset
+    fn acc(ln: live_node, var: variable, acc: uint) {
+        if (acc & ACC_WRITE) != 0u { self.write(ln, var) }
+
+        // Important: if we both read/write, must do read second
+        // or else the write will override.
+        if (acc & ACC_READ) != 0u { self.read(ln, var) }
+    }
+
+    // _______________________________________________________________________
+
+    fn compute(body: blk) -> live_node {
+        // if there is a `break` or `cont` at the top level, then it's
+        // effectively a return---this only occurs in `for` loops,
+        // where the body is really a closure.
+        let entry_ln: live_node =
+            self.with_loop_nodes(self.s.exit_ln, self.s.exit_ln) {||
+                self.propagate_through_fn_block(body)
+            };
+
+        // hack to skip the loop unless #debug is enabled:
+        #debug["^^ liveness computation results for body %d (entry=%s)",
+               {
+                   uint::range(0u, self.ir.num_live_nodes) { |ln_idx|
+                       #debug["%s", self.ln_str(live_node(ln_idx))];
+                   }
+                   body.node.id
+               },
+               entry_ln.to_str()];
+
+        entry_ln
+    }
+
+    fn propagate_through_fn_block(blk: blk) -> live_node {
+        if blk.node.expr.is_none() {
+            self.read(self.s.fallthrough_ln, self.s.no_ret_var)
+        }
+
+        // in a ctor, there is an implicit use of self.f for all fields f:
+        for self.ir.field_map.each_value { |var|
+            self.read(self.s.fallthrough_ln, var);
+        }
+
+        self.propagate_through_block(blk, self.s.fallthrough_ln)
+    }
+
+    fn propagate_through_block(blk: blk, succ: live_node) -> live_node {
+        let succ = self.propagate_through_opt_expr(blk.node.expr, succ);
+        blk.node.stmts.foldr(succ) { |stmt, succ|
+            self.propagate_through_stmt(stmt, succ)
+        }
+    }
+
+    fn propagate_through_stmt(stmt: @stmt, succ: live_node) -> live_node {
+        alt stmt.node {
+          stmt_decl(decl, _) {
+            ret self.propagate_through_decl(decl, succ);
+          }
+
+          stmt_expr(expr, _) | stmt_semi(expr, _) {
+            ret self.propagate_through_expr(expr, succ);
+          }
+        }
+    }
+
+    fn propagate_through_decl(decl: @decl, succ: live_node) -> live_node {
+        alt decl.node {
+          decl_local(locals) {
+            locals.foldr(succ) { |local, succ|
+                self.propagate_through_local(local, succ)
+            }
+          }
+          decl_item(_) {
+            succ
+          }
+        }
+    }
+
+    fn propagate_through_local(local: @local, succ: live_node) -> live_node {
+        // Note: we mark the variable as defined regardless of whether
+        // there is an initializer.  Initially I had thought to only mark
+        // the live variable as defined if it was initialized, and then we
+        // could check for uninit variables just by scanning what is live
+        // at the start of the function. But that doesn't work so well for
+        // immutable variables defined in a loop:
+        //     loop { let x; x = 5; }
+        // because the "assignment" loops back around and generates an error.
+        //
+        // So now we just check that variables defined w/o an
+        // initializer are not live at the point of their
+        // initialization, which is mildly more complex than checking
+        // once at the func header but otherwise equivalent.
+
+        let opt_init = local.node.init.map { |i| i.expr };
+        let mut succ = self.propagate_through_opt_expr(opt_init, succ);
+        let def_map = self.tcx.def_map;
+        pat_util::pat_bindings(def_map, local.node.pat) { |p_id, sp, _n|
+            let ln = self.live_node(p_id, sp);
+            let var = self.variable(p_id, sp);
+            self.init_from_succ(ln, succ);
+            self.define(ln, var);
+            succ = ln;
+        }
+        succ
+    }
+
+    fn propagate_through_exprs(exprs: [@expr], succ: live_node) -> live_node {
+        exprs.foldr(succ) { |expr, succ|
+            self.propagate_through_expr(expr, succ)
+        }
+    }
+
+    fn propagate_through_opt_expr(opt_expr: option<@expr>,
+                                  succ: live_node) -> live_node {
+        opt_expr.foldl(succ) { |succ, expr|
+            self.propagate_through_expr(expr, succ)
+        }
+    }
+
+    fn propagate_through_expr(expr: @expr, succ: live_node) -> live_node {
+        alt expr.node {
+          // Interesting cases with control flow or which gen/kill
+
+          expr_path(_) {
+            self.access_path(expr, succ, ACC_READ)
+          }
+
+          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`.
+            alt self.as_self_field(e, nm) {
+              some((ln, var)) {
+                self.init_from_succ(ln, succ);
+                self.read(ln, var);
+                ln
+              }
+              none {
+                self.propagate_through_expr(e, succ)
+              }
+            }
+          }
+
+          expr_fn(*) | expr_fn_block(*) {
+            // the construction of a closure itself is not important,
+            // but we have to consider the closed over variables.
+            let caps = (*self.ir).captures(expr);
+            (*caps).foldr(succ) { |cap, succ|
+                self.init_from_succ(cap.ln, succ);
+                let var = self.variable_from_rdef(cap.rv, expr.span);
+                self.read(cap.ln, var);
+                cap.ln
+            }
+          }
+
+          expr_if_check(cond, then, els) |
+          expr_if(cond, then, els) {
+            //
+            //     (cond)
+            //       |
+            //       v
+            //     (expr)
+            //     /   \
+            //    |     |
+            //    v     v
+            //  (then)(els)
+            //    |     |
+            //    v     v
+            //   (  succ  )
+            //
+            let else_ln = self.propagate_through_opt_expr(els, succ);
+            let then_ln = self.propagate_through_block(then, succ);
+            let ln = self.live_node(expr.id, expr.span);
+            self.init_from_succ(ln, else_ln);
+            self.merge_from_succ(ln, then_ln, false);
+            self.propagate_through_expr(cond, ln)
+          }
+
+          expr_while(cond, blk) {
+            self.propagate_through_loop(expr, some(cond), blk, succ)
+          }
+
+          expr_loop(blk) {
+            self.propagate_through_loop(expr, none, blk, succ)
+          }
+
+          expr_alt(e, arms, _) {
+            //
+            //      (e)
+            //       |
+            //       v
+            //     (expr)
+            //     / | \
+            //    |  |  |
+            //    v  v  v
+            //   (..arms..)
+            //    |  |  |
+            //    v  v  v
+            //   (  succ  )
+            //
+            //
+            let ln = self.live_node(expr.id, expr.span);
+            self.init_empty(ln, succ);
+            let mut first_merge = true;
+            for arms.each { |arm|
+                let arm_succ =
+                    self.propagate_through_opt_expr(
+                        arm.guard,
+                        self.propagate_through_block(arm.body, succ));
+                self.merge_from_succ(ln, arm_succ, first_merge);
+                first_merge = false;
+            };
+            self.propagate_through_expr(e, ln)
+          }
+
+          expr_ret(o_e) | expr_fail(o_e) { // ignore succ and subst exit_ln:
+            self.propagate_through_opt_expr(o_e, self.s.exit_ln)
+          }
+
+          expr_break {
+            if !self.break_ln.is_valid() {
+                self.tcx.sess.span_bug(
+                    expr.span, "break with invalid break_ln");
+            }
+
+            self.break_ln
+          }
+
+          expr_cont {
+            if !self.cont_ln.is_valid() {
+                self.tcx.sess.span_bug(
+                    expr.span, "cont with invalid cont_ln");
+            }
+
+            self.cont_ln
+          }
+
+          expr_move(l, r) | expr_assign(l, r) {
+            // see comment on lvalues in
+            // propagate_through_lvalue_components()
+            let succ = self.write_lvalue(l, succ, ACC_WRITE);
+            let succ = self.propagate_through_expr(r, succ);
+            self.propagate_through_lvalue_components(l, succ)
+          }
+
+          expr_swap(l, r) {
+            // see comment on lvalues in
+            // propagate_through_lvalue_components()
+            let succ = self.write_lvalue(r, succ, ACC_WRITE|ACC_READ);
+            let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ);
+            let succ = self.propagate_through_lvalue_components(r, succ);
+            self.propagate_through_lvalue_components(l, succ)
+          }
+
+          expr_assign_op(_, l, r) {
+            // see comment on lvalues in
+            // propagate_through_lvalue_components()
+            let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ);
+            let succ = self.propagate_through_expr(r, succ);
+            self.propagate_through_lvalue_components(l, succ)
+          }
+
+          // Uninteresting cases: just propagate in rev exec order
+
+          expr_vstore(expr, _) {
+            self.propagate_through_expr(expr, succ)
+          }
+
+          expr_vec(exprs, _) {
+            self.propagate_through_exprs(exprs, succ)
+          }
+
+          expr_rec(fields, with_expr) {
+            let succ = self.propagate_through_opt_expr(with_expr, succ);
+            fields.foldr(succ) { |field, succ|
+                self.propagate_through_expr(field.node.expr, succ)
+            }
+          }
+
+          expr_call(f, args, _) {
+            // calling a fn with bot return type means that the fn
+            // will fail, and hence the successors can be ignored
+            let t_ret = ty::ty_fn_ret(ty::expr_ty(self.tcx, f));
+            let succ = if ty::type_is_bot(t_ret) {self.s.exit_ln}
+                       else {succ};
+            let succ = self.propagate_through_exprs(args, succ);
+            self.propagate_through_expr(f, succ)
+          }
+
+          expr_tup(exprs) {
+            self.propagate_through_exprs(exprs, succ)
+          }
+
+          expr_bind(f, args) {
+            let succ = args.foldr(succ) { |arg, succ|
+                alt arg {
+                  none {succ}
+                  some(e) {self.propagate_through_expr(e, succ)}
+                }
+            };
+            self.propagate_through_expr(f, succ)
+          }
+
+          expr_binary(op, l, r) if ast_util::lazy_binop(op) {
+            let r_succ = self.propagate_through_expr(r, succ);
+
+            let ln = self.live_node(expr.id, expr.span);
+            self.init_from_succ(ln, succ);
+            self.merge_from_succ(ln, r_succ, false);
+
+            self.propagate_through_expr(l, ln)
+          }
+
+          expr_new(l, _, r) |
+          expr_log(_, l, r) |
+          expr_index(l, r) |
+          expr_binary(_, l, r) {
+            self.propagate_through_exprs([l, r], succ)
+          }
+
+          expr_assert(e) |
+          expr_check(_, e) |
+          expr_addr_of(_, e) |
+          expr_copy(e) |
+          expr_loop_body(e) |
+          expr_cast(e, _) |
+          expr_unary(_, e) {
+            self.propagate_through_expr(e, succ)
+          }
+
+          expr_lit(*) {
+            succ
+          }
+
+          expr_block(blk) {
+            self.propagate_through_block(blk, succ)
+          }
+
+          expr_mac(*) {
+            self.tcx.sess.span_bug(expr.span, "unexpanded macro");
+          }
+        }
+    }
+
+    fn propagate_through_lvalue_components(expr: @expr,
+                                           succ: live_node) -> live_node {
+        // # Lvalues
+        //
+        // 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, or a self-field (`self.f`) in a ctor.
+        //
+        // The two kinds of graphs are:
+        //
+        //    Tracked lvalue          Untracked lvalue
+        // ----------------------++-----------------------
+        //                       ||
+        //         |             ||           |
+        //         |             ||           v
+        //         |             ||   (lvalue components)
+        //         |             ||           |
+        //         v             ||           v
+        //     (rvalue)          ||       (rvalue)
+        //         |             ||           |
+        //         v             ||           |
+        // (write of lvalue)     ||           |
+        //         |             ||           |
+        //         v             ||           v
+        //      (succ)           ||        (succ)
+        //                       ||
+        // ----------------------++-----------------------
+        //
+        // I will cover the two cases in turn:
+        //
+        // # Tracked lvalues
+        //
+        // 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` or `self`, respectively.  The
+        // `write_lvalue()` routine generates the contents of this
+        // node.  There are no subcomponents to consider.
+        //
+        // # Non-tracked lvalues
+        //
+        // These are lvalues like `x[5]` or `x.f`.  In that case, we
+        // basically ignore the value which is written to but generate
+        // reads for the components---`x` in these two examples.  The
+        // components reads are generated by
+        // `propagate_through_lvalue_components()` (this fn).
+        //
+        // # Illegal lvalues
+        //
+        // It is still possible to observe assignments to non-lvalues;
+        // these errors are detected in the later pass borrowck.  We
+        // just ignore such cases and treat them as reads.
+
+        alt expr.node {
+          expr_path(_) {
+            succ
+          }
+
+          expr_field(e, nm, _) {
+            alt self.as_self_field(e, nm) {
+              some(_) {succ}
+              none {self.propagate_through_expr(e, succ)}
+            }
+          }
+
+          _ {
+            self.propagate_through_expr(expr, succ)
+          }
+        }
+    }
+
+    // see comment on propagate_through_lvalue()
+    fn write_lvalue(expr: @expr,
+                    succ: live_node,
+                    acc: uint) -> live_node {
+        alt expr.node {
+          expr_path(_) {
+            self.access_path(expr, succ, acc)
+          }
+
+          expr_field(e, nm, _) {
+            alt 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
+          // non-lvalues occur here, because those are detected in the
+          // later pass borrowck.
+          _ {succ}
+        }
+    }
+
+    fn access_path(expr: @expr, succ: live_node, acc: uint) -> live_node {
+        let def = self.tcx.def_map.get(expr.id);
+        alt relevant_def(def) {
+          some(rdef_self) {
+            // 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(rdef_var(nid)) {
+            let ln = self.live_node(expr.id, expr.span);
+            if acc != 0u {
+                self.init_from_succ(ln, succ);
+                let var = self.variable(nid, expr.span);
+                self.acc(ln, var, acc);
+            }
+            ln
+          }
+          none {
+            succ
+          }
+        }
+    }
+
+    fn as_self_field(expr: @expr, fld: str) -> option<(live_node,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`.
+        alt expr.node {
+          expr_path(_) {
+            let def = self.tcx.def_map.get(expr.id);
+            alt def {
+              def_self(_) {
+                // Note: the field_map is empty unless we are in a ctor
+                ret self.ir.field_map.find(fld).map { |var|
+                    let ln = self.live_node(expr.id, expr.span);
+                    (ln, var)
+                };
+              }
+              _ { ret none; }
+            }
+          }
+          _ { ret none; }
+        }
+    }
+
+    fn propagate_through_loop(expr: @expr,
+                              cond: option<@expr>,
+                              body: blk,
+                              succ: live_node) -> live_node {
+
+        /*
+
+        We model control flow like this:
+
+              (cond) <--+
+                |       |
+                v       |
+          +-- (expr)    |
+          |     |       |
+          |     v       |
+          |   (body) ---+
+          |
+          |
+          v
+        (succ)
+
+        */
+
+        // first iteration:
+        let mut first_merge = true;
+        let ln = self.live_node(expr.id, expr.span);
+        self.init_empty(ln, succ);
+        if cond.is_some() {
+            // if there is a condition, then it's possible we bypass
+            // the body altogether.  otherwise, the only way is via a
+            // break in the loop body.
+            self.merge_from_succ(ln, succ, first_merge);
+            first_merge = false;
+        }
+        let cond_ln = self.propagate_through_opt_expr(cond, ln);
+        let body_ln = self.with_loop_nodes(succ, ln) {||
+            self.propagate_through_block(body, cond_ln)
+        };
+
+        // repeat until fixed point is reached:
+        while self.merge_from_succ(ln, body_ln, first_merge) {
+            first_merge = false;
+            assert cond_ln == self.propagate_through_opt_expr(cond, ln);
+            assert body_ln == self.with_loop_nodes(succ, ln) {||
+                self.propagate_through_block(body, cond_ln)
+            };
+        }
+
+        cond_ln
+    }
+
+    fn with_loop_nodes<R>(break_ln: live_node,
+                          cont_ln: live_node,
+                          f: fn() -> R) -> R {
+        let bl = self.break_ln, cl = self.cont_ln;
+        self.break_ln = break_ln;
+        self.cont_ln = cont_ln;
+        let r <- f();
+        self.break_ln = bl;
+        self.cont_ln = cl;
+        ret r;
+    }
+}
+
+// _______________________________________________________________________
+// Checking for error conditions
+
+fn check_local(local: @local, &&self: @liveness, vt: vt<@liveness>) {
+    alt local.node.init {
+      some({op: init_move, expr: expr}) {
+        // can never be accessed uninitialized, but the move might
+        // be invalid
+        #debug["check_local() with move initializer"];
+        self.check_move_from_expr(expr, vt);
+      }
+      some({op: init_op, expr: _}) {
+        // can never be accessed uninitialized
+        #debug["check_local() with initializer"];
+      }
+      none {
+        #debug["check_local() with no initializer"];
+        let def_map = self.tcx.def_map;
+        pat_util::pat_bindings(def_map, local.node.pat) {|p_id, sp, _n|
+            let ln = (*self).live_node(p_id, sp);
+            let var = (*self).variable(p_id, sp);
+            alt (*self).live_on_exit(ln, var) {
+              none { /* not live: good */ }
+              some(lnk) {
+                self.report_illegal_read(
+                    local.span, lnk, var, possibly_uninitialized_variable);
+              }
+            }
+        }
+      }
+    }
+
+    visit::visit_local(local, self, vt);
+}
+
+fn check_expr(expr: @expr, &&self: @liveness, vt: vt<@liveness>) {
+    alt expr.node {
+      expr_path(_) {
+        for (*self).variable_from_def_map(expr.id, expr.span).each { |var|
+            let ln = (*self).live_node(expr.id, expr.span);
+            self.consider_last_use(expr, ln, var);
+        }
+
+        visit::visit_expr(expr, self, vt);
+      }
+
+      expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
+        let caps = (*self.ir).captures(expr);
+        for (*caps).each { |cap|
+            let var = (*self).variable_from_rdef(cap.rv, expr.span);
+            self.consider_last_use(expr, cap.ln, var);
+            if cap.is_move {
+                self.check_move_from_var(expr.span, cap.ln, var);
+            }
+        }
+
+        visit::visit_expr(expr, self, vt);
+      }
+
+      expr_assign(l, r) {
+        self.check_lvalue(l, vt);
+        vt.visit_expr(r, self, vt);
+      }
+
+      expr_move(l, r) {
+        self.check_lvalue(l, vt);
+        self.check_move_from_expr(r, vt);
+      }
+
+      expr_call(f, args, _) {
+        let targs = ty::ty_fn_args(ty::expr_ty(self.tcx, f));
+        vt.visit_expr(f, self, vt);
+        vec::iter2(args, targs) { |arg_expr, arg_ty|
+            alt ty::resolved_mode(self.tcx, arg_ty.mode) {
+              by_val | by_ref | by_mutbl_ref | by_copy {
+                vt.visit_expr(arg_expr, self, vt);
+              }
+              by_move {
+                self.check_move_from_expr(arg_expr, vt);
+              }
+            }
+        }
+      }
+
+      // no correctness conditions related to liveness
+      expr_if_check(*) | expr_if(*) | expr_alt(*) |
+      expr_while(*) | expr_loop(*) |
+      expr_index(*) | expr_field(*) | expr_vstore(*) |
+      expr_vec(*) | expr_rec(*) | expr_tup(*) |
+      expr_bind(*) | expr_new(*) | expr_log(*) | expr_binary(*) |
+      expr_assert(*) | expr_check(*) | expr_addr_of(*) | expr_copy(*) |
+      expr_loop_body(*) | expr_cast(*) | expr_unary(*) | expr_fail(*) |
+      expr_ret(*) | expr_break | expr_cont | expr_lit(_) |
+      expr_block(*) | expr_swap(*) | expr_assign_op(*) | expr_mac(*) {
+        visit::visit_expr(expr, self, vt);
+      }
+    }
+}
+
+fn check_fn(_fk: visit::fn_kind, _decl: fn_decl,
+            _body: blk, _sp: span, _id: node_id,
+            &&_self: @liveness, _v: vt<@liveness>) {
+    // do not check contents of nested fns
+}
+
+enum read_kind {
+    possibly_uninitialized_variable,
+    possibly_uninitialized_field,
+    moved_variable
+}
+
+impl check_methods for @liveness {
+    fn check_fields(sp: span, entry_ln: live_node) {
+        for self.ir.field_map.each { |nm, var|
+            alt (*self).live_on_entry(entry_ln, var) {
+              none { /* ok */ }
+              some(lnk_exit) {
+                self.tcx.sess.span_err(
+                    sp, #fmt["field `self.%s` is never initialized", nm]);
+              }
+              some(lnk) {
+                self.report_illegal_read(
+                    sp, lnk, var, possibly_uninitialized_field);
+              }
+            }
+        }
+    }
+
+    fn check_ret(id: node_id, sp: span, fk: visit::fn_kind,
+                 entry_ln: live_node) {
+        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
+            // function without any kind of return expression:
+
+            let t_ret = ty::ty_fn_ret(ty::node_id_to_type(self.tcx, id));
+            if ty::type_is_nil(t_ret) {
+                // for nil return types, it is ok to not return a value expl.
+            } else if ty::type_is_bot(t_ret) {
+                // for bot return types, not ok.  Function should fail.
+                self.tcx.sess.span_err(
+                    sp, "some control paths may return");
+            } else {
+                alt 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");
+                  }
+                }
+            }
+        }
+    }
+
+    fn check_move_from_var(span: span, ln: live_node, var: variable) {
+        #debug["check_move_from_var(%s, %s)",
+               ln.to_str(), var.to_str()];
+
+        alt (*self).live_on_exit(ln, var) {
+          none {}
+          some(lnk) {
+            self.report_illegal_read(span, lnk, var, moved_variable);
+            self.tcx.sess.span_note(
+                span,
+                "move of variable occurred here");
+          }
+        }
+    }
+
+    fn consider_last_use(expr: @expr, ln: live_node, var: variable) {
+        alt (*self).live_on_exit(ln, var) {
+          some(_) {}
+          none {
+            (*self.ir).add_last_use(expr.id, var);
+          }
+       }
+    }
+
+    fn check_move_from_expr(expr: @expr, vt: vt<@liveness>) {
+        #debug["check_move_from_expr(node %d: %s)",
+               expr.id, expr_to_str(expr)];
+
+        if self.ir.method_map.contains_key(expr.id) {
+            // actually an rvalue, since this calls a method
+            ret vt.visit_expr(expr, self, vt);
+        }
+
+        alt expr.node {
+          expr_path(_) {
+            let def = self.tcx.def_map.get(expr.id);
+            alt relevant_def(def) {
+              some(rdef) {
+                // Moving from a variable is allowed if is is not live.
+                let ln = (*self).live_node(expr.id, expr.span);
+                let var = (*self).variable_from_rdef(rdef, expr.span);
+                self.check_move_from_var(expr.span, ln, var);
+              }
+              none {}
+            }
+          }
+
+          expr_field(base, _, _) {
+            // Moving from x.y is allowed if x is never used later.
+            // (Note that the borrowck guarantees that anything
+            //  being moved from is uniquely tied to the stack frame)
+            self.check_move_from_expr(base, vt);
+          }
+
+          expr_index(base, idx) {
+            // Moving from x[y] is allowed if x is never used later.
+            // (Note that the borrowck guarantees that anything
+            //  being moved from is uniquely tied to the stack frame)
+            self.check_move_from_expr(base, vt);
+            vt.visit_expr(idx, self, vt);
+          }
+
+          _ {
+            // For other kinds of lvalues, no checks are required,
+            // and any embedded expressions are actually rvalues
+            vt.visit_expr(expr, self, vt);
+          }
+       }
+    }
+
+    fn check_lvalue(expr: @expr, vt: vt<@liveness>) {
+        alt expr.node {
+          expr_path(_) {
+            alt self.tcx.def_map.get(expr.id) {
+              def_local(nid, false) {
+                // Assignment to an immutable variable or argument:
+                // only legal if there is no later assignment.
+                let ln = (*self).live_node(expr.id, expr.span);
+                let var = (*self).variable(nid, expr.span);
+                alt (*self).assigned_on_exit(ln, var) {
+                  some(lnk_expr(span)) {
+                    self.tcx.sess.span_err(
+                        span,
+                        "re-assignment of immutable variable");
+
+                    self.tcx.sess.span_note(
+                        expr.span,
+                        "prior assignment occurs here");
+                  }
+                  some(lnk) {
+                    self.tcx.sess.span_bug(
+                        expr.span,
+                        #fmt["illegal writer: %?", lnk]);
+                   }
+                  none {}
+                }
+              }
+              def_arg(*) | def_local(_, true) {
+                // Assignment to a mutable variable; no conditions
+                // req'd.  In the case of arguments, the mutability is
+                // enforced by borrowck.
+              }
+              _ {
+                // Not a variable, don't care
+              }
+            }
+          }
+
+          _ {
+            // For other kinds of lvalues, no checks are required,
+            // and any embedded expressions are actually rvalues
+            visit::visit_expr(expr, self, vt);
+          }
+       }
+    }
+
+    fn report_illegal_read(chk_span: span,
+                           lnk: live_node_kind,
+                           var: variable,
+                           rk: read_kind) {
+        let msg = alt rk {
+          possibly_uninitialized_variable {"possibly uninitialized variable"}
+          possibly_uninitialized_field {"possibly uninitialized field"}
+          moved_variable {"moved variable"}
+        };
+        let name = self.ir.var_infos[*var].name;
+        alt lnk {
+          lnk_freevar(span) {
+            self.tcx.sess.span_err(
+                span,
+                #fmt["capture of %s: `%s`", msg, name]);
+          }
+          lnk_expr(span) {
+            self.tcx.sess.span_err(
+                span,
+                #fmt["use of %s: `%s`", msg, name]);
+          }
+          lnk_exit | lnk_vdef(_) {
+            self.tcx.sess.span_bug(
+                chk_span,
+                #fmt["illegal reader: %?", lnk]);
+          }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/rustc/rustc.rc b/src/rustc/rustc.rc
index b3956c2ab02..4ba01859cdd 100644
--- a/src/rustc/rustc.rc
+++ b/src/rustc/rustc.rc
@@ -72,6 +72,7 @@ mod middle {
     mod borrowck;
     mod alias;
     mod last_use;
+    mod liveness;
     mod block_use;
     mod kind;
     mod freevars;
diff --git a/src/test/compile-fail/and-init.rs b/src/test/compile-fail/and-init.rs
deleted file mode 100644
index c053180bf43..00000000000
--- a/src/test/compile-fail/and-init.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-// error-pattern:unsatisfied precondition constraint (for example, init(i
-
-fn main() {
-    let i: int;
-
-    log(debug, false && { i = 5; true });
-    log(debug, i);
-}
diff --git a/src/test/compile-fail/assign-imm-local-twice.rs b/src/test/compile-fail/assign-imm-local-twice.rs
new file mode 100644
index 00000000000..13ec17e0585
--- /dev/null
+++ b/src/test/compile-fail/assign-imm-local-twice.rs
@@ -0,0 +1,9 @@
+fn test(cond: bool) {
+    let v: int;
+    v = 1; //! NOTE prior assignment occurs here
+    v = 2; //! ERROR re-assignment of immutable variable
+}
+
+fn main() {
+    test(true);
+}
diff --git a/src/test/compile-fail/block-uninit.rs b/src/test/compile-fail/block-uninit.rs
deleted file mode 100644
index 99b8346166a..00000000000
--- a/src/test/compile-fail/block-uninit.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-// error-pattern:unsatisfied precondition constraint
-
-fn force(f: fn()) { f(); }
-fn main() { let x: int; force(fn&() { log(error, x); }); }
diff --git a/src/test/compile-fail/break-uninit.rs b/src/test/compile-fail/break-uninit.rs
deleted file mode 100644
index 8babc95f0dc..00000000000
--- a/src/test/compile-fail/break-uninit.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-// error-pattern:unsatisfied precondition
-
-fn foo() -> int {
-    let x: int;
-    let i: int;
-
-    loop { i = 0; break; x = 0; }
-
-    log(debug, x);
-
-    ret 17;
-}
-
-fn main() { log(debug, foo()); }
diff --git a/src/test/compile-fail/break-uninit2.rs b/src/test/compile-fail/break-uninit2.rs
deleted file mode 100644
index 1229e0dc0ee..00000000000
--- a/src/test/compile-fail/break-uninit2.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-// error-pattern:unsatisfied precondition
-
-fn foo() -> int {
-    let x: int;
-    let i: int;
-
-    while 1 != 2  { i = 0; break; x = 0; }
-
-    log(debug, x);
-
-    ret 17;
-}
-
-fn main() { log(debug, foo()); }
diff --git a/src/test/compile-fail/fn-expr-type-state.rs b/src/test/compile-fail/fn-expr-type-state.rs
deleted file mode 100644
index ab3f1dd1087..00000000000
--- a/src/test/compile-fail/fn-expr-type-state.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-// error-pattern:unsatisfied precondition
-
-fn main() {
-    // Typestate should work even in a fn@. we should reject this program.
-    let f = fn@() -> int { let i: int; ret i; };
-    log(error, f());
-}
diff --git a/src/test/compile-fail/fn-expr-typestate-2.rs b/src/test/compile-fail/fn-expr-typestate-2.rs
deleted file mode 100644
index 75682c8ddf8..00000000000
--- a/src/test/compile-fail/fn-expr-typestate-2.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-// error-pattern:unsatisfied precondition
-
-fn main() {
-    let j = fn@() -> int { let i: int; ret i; }();
-    log(error, j);
-}
diff --git a/src/test/compile-fail/fru-typestate.rs b/src/test/compile-fail/fru-typestate.rs
deleted file mode 100644
index 447f6f44dc5..00000000000
--- a/src/test/compile-fail/fru-typestate.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// -*- rust -*-
-
-// error-pattern: precondition
-
-type point = {x: int, y: int};
-
-fn main() {
-    let origin: point;
-
-    let right: point = {x: 10 with origin};
-    origin = {x: 0, y: 0};
-}
diff --git a/src/test/compile-fail/issue-2163.rs b/src/test/compile-fail/issue-2163.rs
deleted file mode 100644
index 15b3d6d96c7..00000000000
--- a/src/test/compile-fail/issue-2163.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-fn main(s: [str]) {
-    let a: [int] = [];
-    vec::each(a) { |x| //! ERROR in function `anon`, not all control paths
-    }                  //! ERROR see function return type of `bool`
-}
diff --git a/src/test/compile-fail/liveness-and-init.rs b/src/test/compile-fail/liveness-and-init.rs
new file mode 100644
index 00000000000..aea30e3465c
--- /dev/null
+++ b/src/test/compile-fail/liveness-and-init.rs
@@ -0,0 +1,6 @@
+fn main() {
+    let i: int;
+
+    log(debug, false && { i = 5; true });
+    log(debug, i); //! ERROR use of possibly uninitialized variable: `i`
+}
diff --git a/src/test/compile-fail/liveness-assign-imm-local-in-loop.rs b/src/test/compile-fail/liveness-assign-imm-local-in-loop.rs
new file mode 100644
index 00000000000..a68528418e6
--- /dev/null
+++ b/src/test/compile-fail/liveness-assign-imm-local-in-loop.rs
@@ -0,0 +1,11 @@
+fn test(cond: bool) {
+    let v: int;
+    loop {
+        v = 1; //! ERROR re-assignment of immutable variable
+        //!^ NOTE prior assignment occurs here
+    }
+}
+
+fn main() {
+    test(true);
+}
diff --git a/src/test/compile-fail/liveness-assign-imm-local-in-op-eq.rs b/src/test/compile-fail/liveness-assign-imm-local-in-op-eq.rs
new file mode 100644
index 00000000000..2f880e03c74
--- /dev/null
+++ b/src/test/compile-fail/liveness-assign-imm-local-in-op-eq.rs
@@ -0,0 +1,9 @@
+fn test(cond: bool) {
+    let v: int;
+    v = 2;  //! NOTE prior assignment occurs here
+    v += 1; //! ERROR re-assignment of immutable variable
+}
+
+fn main() {
+    test(true);
+}
diff --git a/src/test/compile-fail/liveness-assign-imm-local-in-swap.rs b/src/test/compile-fail/liveness-assign-imm-local-in-swap.rs
new file mode 100644
index 00000000000..c432d03bed3
--- /dev/null
+++ b/src/test/compile-fail/liveness-assign-imm-local-in-swap.rs
@@ -0,0 +1,18 @@
+fn test1() {
+    let v: int;
+    let mut w: int;
+    v = 1; //! NOTE prior assignment occurs here
+    w = 2;
+    v <-> w; //! ERROR re-assignment of immutable variable
+}
+
+fn test2() {
+    let v: int;
+    let mut w: int;
+    v = 1; //! NOTE prior assignment occurs here
+    w = 2;
+    w <-> v; //! ERROR re-assignment of immutable variable
+}
+
+fn main() {
+}
diff --git a/src/test/compile-fail/bad-bang-ann-2.rs b/src/test/compile-fail/liveness-bad-bang-2.rs
index 8fb6a9e8737..8fb6a9e8737 100644
--- a/src/test/compile-fail/bad-bang-ann-2.rs
+++ b/src/test/compile-fail/liveness-bad-bang-2.rs
diff --git a/src/test/compile-fail/liveness-block-unint.rs b/src/test/compile-fail/liveness-block-unint.rs
new file mode 100644
index 00000000000..698f9f7cf79
--- /dev/null
+++ b/src/test/compile-fail/liveness-block-unint.rs
@@ -0,0 +1,7 @@
+fn force(f: fn()) { f(); }
+fn main() {
+    let x: int;
+    force(fn&() {
+        log(debug, x); //! ERROR capture of possibly uninitialized variable: `x`
+    });
+}
diff --git a/src/test/compile-fail/liveness-break-uninit-2.rs b/src/test/compile-fail/liveness-break-uninit-2.rs
new file mode 100644
index 00000000000..30038dbe96e
--- /dev/null
+++ b/src/test/compile-fail/liveness-break-uninit-2.rs
@@ -0,0 +1,16 @@
+fn foo() -> int {
+    let x: int;
+    let i: int;
+
+    while 1 != 2  {
+        i = 0;
+        break;
+        x = 0; //! WARNING unreachable statement
+    }
+
+    log(debug, x); //! ERROR use of possibly uninitialized variable: `x`
+
+    ret 17;
+}
+
+fn main() { log(debug, foo()); }
diff --git a/src/test/compile-fail/liveness-break-uninit.rs b/src/test/compile-fail/liveness-break-uninit.rs
new file mode 100644
index 00000000000..55041daa7fb
--- /dev/null
+++ b/src/test/compile-fail/liveness-break-uninit.rs
@@ -0,0 +1,16 @@
+fn foo() -> int {
+    let x: int;
+    let i: int;
+
+    loop {
+        i = 0;
+        break;
+        x = 0;  //! WARNING unreachable statement
+    }
+
+    log(debug, x); //! ERROR use of possibly uninitialized variable: `x`
+
+    ret 17;
+}
+
+fn main() { log(debug, foo()); }
diff --git a/src/test/compile-fail/block-require-return.rs b/src/test/compile-fail/liveness-closure-require-ret.rs
index 6a05ccf0605..6a05ccf0605 100644
--- a/src/test/compile-fail/block-require-return.rs
+++ b/src/test/compile-fail/liveness-closure-require-ret.rs
diff --git a/src/test/compile-fail/liveness-ctor-access-self-with-uninit-fields.rs b/src/test/compile-fail/liveness-ctor-access-self-with-uninit-fields.rs
new file mode 100644
index 00000000000..9c982292816
--- /dev/null
+++ b/src/test/compile-fail/liveness-ctor-access-self-with-uninit-fields.rs
@@ -0,0 +1,11 @@
+class cat {
+  let how_hungry : int;
+  fn meow() {}
+  new() {
+     self.meow();
+     //!^ ERROR use of possibly uninitialized field: `self.how_hungry`
+  }
+}
+
+fn main() {
+}
diff --git a/src/test/compile-fail/liveness-ctor-field-never-init.rs b/src/test/compile-fail/liveness-ctor-field-never-init.rs
new file mode 100644
index 00000000000..b979bdd9255
--- /dev/null
+++ b/src/test/compile-fail/liveness-ctor-field-never-init.rs
@@ -0,0 +1,7 @@
+class cat {
+  let how_hungry : int;
+  new() {} //! ERROR field `self.how_hungry` is never initialized
+}
+
+fn main() {
+}
diff --git a/src/test/compile-fail/liveness-ctor-uninit-field.rs b/src/test/compile-fail/liveness-ctor-uninit-field.rs
new file mode 100644
index 00000000000..080d4678a2f
--- /dev/null
+++ b/src/test/compile-fail/liveness-ctor-uninit-field.rs
@@ -0,0 +1,14 @@
+class cat {
+  let mut a: int;
+  let mut b: int;
+  let mut c: int;
+
+  new() {
+     self.a = 3;
+     self.b = self.a;
+     self.a += self.c; //! ERROR use of possibly uninitialized field: `self.c`
+  }
+}
+
+fn main() {
+}
diff --git a/src/test/compile-fail/ctor-uninit-var.rs b/src/test/compile-fail/liveness-ctor-uninit-var.rs
index b6f94c11fe5..3bbccd62ae3 100644
--- a/src/test/compile-fail/ctor-uninit-var.rs
+++ b/src/test/compile-fail/liveness-ctor-uninit-var.rs
@@ -1,4 +1,3 @@
-// error-pattern:unsatisfied precondition
 class cat {
   priv {
     let mut meows : uint;
@@ -13,7 +12,7 @@ class cat {
   new(in_x : uint, in_y : int) {
     let foo;
     self.meows = in_x + (in_y as uint);
-    self.how_hungry = foo;
+    self.how_hungry = foo; //! ERROR use of possibly uninitialized variable: `foo`
   }
 }
 
diff --git a/src/test/compile-fail/forgot-ret.rs b/src/test/compile-fail/liveness-forgot-ret.rs
index 4d78b7a7aff..4d78b7a7aff 100644
--- a/src/test/compile-fail/forgot-ret.rs
+++ b/src/test/compile-fail/liveness-forgot-ret.rs
diff --git a/src/test/compile-fail/liveness-if-no-else.rs b/src/test/compile-fail/liveness-if-no-else.rs
new file mode 100644
index 00000000000..5cbd24a6a5b
--- /dev/null
+++ b/src/test/compile-fail/liveness-if-no-else.rs
@@ -0,0 +1,6 @@
+fn foo(x: int) { log(debug, x); }
+
+fn main() {
+	let x: int; if 1 > 2 { x = 10; }
+	foo(x); //! ERROR use of possibly uninitialized variable: `x`
+}
diff --git a/src/test/compile-fail/use-uninit-3.rs b/src/test/compile-fail/liveness-if-with-else.rs
index 2a6522ab407..dbb3e6b1645 100644
--- a/src/test/compile-fail/use-uninit-3.rs
+++ b/src/test/compile-fail/liveness-if-with-else.rs
@@ -1,5 +1,3 @@
-// error-pattern:unsatisfied precondition
-
 fn foo(x: int) { log(debug, x); }
 
 fn main() {
@@ -9,5 +7,5 @@ fn main() {
     } else {
         x = 10;
     }
-    foo(x);
+    foo(x); //! ERROR use of possibly uninitialized variable: `x`
 }
diff --git a/src/test/compile-fail/liveness-init-in-called-fn-expr.rs b/src/test/compile-fail/liveness-init-in-called-fn-expr.rs
new file mode 100644
index 00000000000..28351ceeb08
--- /dev/null
+++ b/src/test/compile-fail/liveness-init-in-called-fn-expr.rs
@@ -0,0 +1,7 @@
+fn main() {
+    let j = fn@() -> int {
+        let i: int;
+        ret i; //! ERROR use of possibly uninitialized variable: `i`
+    };
+    j();
+}
diff --git a/src/test/compile-fail/liveness-init-in-fn-expr.rs b/src/test/compile-fail/liveness-init-in-fn-expr.rs
new file mode 100644
index 00000000000..8c68ba750a8
--- /dev/null
+++ b/src/test/compile-fail/liveness-init-in-fn-expr.rs
@@ -0,0 +1,7 @@
+fn main() {
+    let f = fn@() -> int {
+        let i: int;
+        ret i; //! ERROR use of possibly uninitialized variable: `i`
+    };
+    log(error, f());
+}
diff --git a/src/test/compile-fail/liveness-init-in-fru.rs b/src/test/compile-fail/liveness-init-in-fru.rs
new file mode 100644
index 00000000000..1c17a2503dc
--- /dev/null
+++ b/src/test/compile-fail/liveness-init-in-fru.rs
@@ -0,0 +1,8 @@
+// -*- rust -*-
+
+type point = {x: int, y: int};
+
+fn main() {
+    let mut origin: point;
+    origin = {x: 10 with origin}; //! ERROR use of possibly uninitialized variable: `origin`
+}
diff --git a/src/test/compile-fail/liveness-init-op-equal.rs b/src/test/compile-fail/liveness-init-op-equal.rs
new file mode 100644
index 00000000000..2f37dc5070f
--- /dev/null
+++ b/src/test/compile-fail/liveness-init-op-equal.rs
@@ -0,0 +1,8 @@
+fn test(cond: bool) {
+    let v: int;
+    v += 1; //! ERROR use of possibly uninitialized variable: `v`
+}
+
+fn main() {
+    test(true);
+}
diff --git a/src/test/compile-fail/liveness-init-plus-equal.rs b/src/test/compile-fail/liveness-init-plus-equal.rs
new file mode 100644
index 00000000000..56c5b3b2717
--- /dev/null
+++ b/src/test/compile-fail/liveness-init-plus-equal.rs
@@ -0,0 +1,8 @@
+fn test(cond: bool) {
+    let mut v: int;
+    v = v + 1; //! ERROR use of possibly uninitialized variable: `v`
+}
+
+fn main() {
+    test(true);
+}
diff --git a/src/test/compile-fail/liveness-issue-2163.rs b/src/test/compile-fail/liveness-issue-2163.rs
new file mode 100644
index 00000000000..dea27e585ef
--- /dev/null
+++ b/src/test/compile-fail/liveness-issue-2163.rs
@@ -0,0 +1,5 @@
+fn main(s: [str]) {
+    let a: [int] = [];
+    vec::each(a) { |x| //! ERROR not all control paths return a value
+    }
+}
diff --git a/src/test/compile-fail/missing-return2.rs b/src/test/compile-fail/liveness-missing-ret2.rs
index 54d8de63014..54d8de63014 100644
--- a/src/test/compile-fail/missing-return2.rs
+++ b/src/test/compile-fail/liveness-missing-ret2.rs
diff --git a/src/test/compile-fail/liveness-move-from-mode.rs b/src/test/compile-fail/liveness-move-from-mode.rs
new file mode 100644
index 00000000000..01483f033eb
--- /dev/null
+++ b/src/test/compile-fail/liveness-move-from-mode.rs
@@ -0,0 +1,10 @@
+fn take(-x: int) {}
+
+fn main() {
+
+    let x: int = 25;
+    loop {
+        take(x); //! ERROR use of moved variable: `x`
+        //!^ NOTE move of variable occurred here
+    }
+}
diff --git a/src/test/compile-fail/liveness-move-in-loop.rs b/src/test/compile-fail/liveness-move-in-loop.rs
new file mode 100644
index 00000000000..81467d8a2b4
--- /dev/null
+++ b/src/test/compile-fail/liveness-move-in-loop.rs
@@ -0,0 +1,16 @@
+fn main() {
+
+    let y: int = 42;
+    let mut x: int;
+    loop {
+        log(debug, y);
+        loop {
+            loop {
+                loop {
+                    x <- y; //! ERROR use of moved variable
+                    //!^ NOTE move of variable occurred here
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/compile-fail/while-constraints.rs b/src/test/compile-fail/liveness-move-in-while.rs
index ca8d0140108..6acbc5a9f35 100644
--- a/src/test/compile-fail/while-constraints.rs
+++ b/src/test/compile-fail/liveness-move-in-while.rs
@@ -1,10 +1,11 @@
-// error-pattern:unsatisfied precondition constraint (for example, init(y
 fn main() {
 
     let y: int = 42;
-    let x: int;
+    let mut x: int;
     loop {
         log(debug, y);
         while true { while true { while true { x <- y; } } }
+        //!^ ERROR use of moved variable: `y`
+        //!^^ NOTE move of variable occurred here
     }
 }
diff --git a/src/test/compile-fail/liveness-or-init.rs b/src/test/compile-fail/liveness-or-init.rs
new file mode 100644
index 00000000000..5912378cf42
--- /dev/null
+++ b/src/test/compile-fail/liveness-or-init.rs
@@ -0,0 +1,6 @@
+fn main() {
+    let i: int;
+
+    log(debug, false || { i = 5; true });
+    log(debug, i); //! ERROR use of possibly uninitialized variable: `i`
+}
diff --git a/src/test/compile-fail/liveness-return.rs b/src/test/compile-fail/liveness-return.rs
new file mode 100644
index 00000000000..cee1444ca63
--- /dev/null
+++ b/src/test/compile-fail/liveness-return.rs
@@ -0,0 +1,6 @@
+fn f() -> int {
+	let x: int;
+	ret x; //! ERROR use of possibly uninitialized variable: `x`
+}
+
+fn main() { f(); }
diff --git a/src/test/compile-fail/liveness-swap-uninit.rs b/src/test/compile-fail/liveness-swap-uninit.rs
new file mode 100644
index 00000000000..53714c1f740
--- /dev/null
+++ b/src/test/compile-fail/liveness-swap-uninit.rs
@@ -0,0 +1,5 @@
+fn main() {
+	let x = 3;
+	let y;
+	x <-> y; //! ERROR use of possibly uninitialized variable: `y`
+}
diff --git a/src/test/compile-fail/liveness-uninit-after-item.rs b/src/test/compile-fail/liveness-uninit-after-item.rs
new file mode 100644
index 00000000000..7c619804c3c
--- /dev/null
+++ b/src/test/compile-fail/liveness-uninit-after-item.rs
@@ -0,0 +1,6 @@
+fn main() {
+    let bar;
+    fn baz(x: int) { }
+    bind baz(bar); //! ERROR use of possibly uninitialized variable: `bar`
+}
+
diff --git a/src/test/compile-fail/liveness-uninit.rs b/src/test/compile-fail/liveness-uninit.rs
new file mode 100644
index 00000000000..1930a2e3352
--- /dev/null
+++ b/src/test/compile-fail/liveness-uninit.rs
@@ -0,0 +1,6 @@
+fn foo(x: int) { log(debug, x); }
+
+fn main() {
+	let x: int;
+	foo(x); //! ERROR use of possibly uninitialized variable: `x`
+}
diff --git a/src/test/compile-fail/liveness-use-after-move.rs b/src/test/compile-fail/liveness-use-after-move.rs
new file mode 100644
index 00000000000..48d7a9a303e
--- /dev/null
+++ b/src/test/compile-fail/liveness-use-after-move.rs
@@ -0,0 +1,5 @@
+fn main() {
+    let x = @5;
+    let y <- x; //! NOTE move of variable occurred here
+    log(debug, *x); //! ERROR use of moved variable: `x`
+}
diff --git a/src/test/compile-fail/use-after-send.rs b/src/test/compile-fail/liveness-use-after-send.rs
index 4fe164878a8..63e42bee3c9 100644
--- a/src/test/compile-fail/use-after-send.rs
+++ b/src/test/compile-fail/liveness-use-after-send.rs
@@ -1,4 +1,3 @@
-// error-pattern:unsatisfied precondition constraint
 fn send<T: send>(ch: _chan<T>, -data: T) {
     log(debug, ch);
     log(debug, data);
@@ -10,8 +9,8 @@ enum _chan<T> = int;
 // Tests that "log(debug, message);" is flagged as using
 // message after the send deinitializes it
 fn test00_start(ch: _chan<int>, message: int, count: int) {
-    send(ch, message);
-    log(debug, message);
+    send(ch, message); //! NOTE move of variable occurred here
+    log(debug, message); //! ERROR use of moved variable: `message`
 }
 
 fn main() { fail; }
diff --git a/src/test/compile-fail/liveness-use-in-index-lvalue.rs b/src/test/compile-fail/liveness-use-in-index-lvalue.rs
new file mode 100644
index 00000000000..50d0662f803
--- /dev/null
+++ b/src/test/compile-fail/liveness-use-in-index-lvalue.rs
@@ -0,0 +1,6 @@
+fn test() {
+    let w: [int];
+    w[5] = 0; //! ERROR use of possibly uninitialized variable: `w`
+}
+
+fn main() { test(); }
diff --git a/src/test/compile-fail/liveness-while-break.rs b/src/test/compile-fail/liveness-while-break.rs
new file mode 100644
index 00000000000..755deb31fba
--- /dev/null
+++ b/src/test/compile-fail/liveness-while-break.rs
@@ -0,0 +1,12 @@
+fn test(cond: bool) {
+    let v;
+    while cond {
+        v = 3;
+        break;
+    }
+    #debug["%d", v]; //! ERROR use of possibly uninitialized variable: `v`
+}
+
+fn main() {
+    test(true);
+}
diff --git a/src/test/compile-fail/liveness-while-cond.rs b/src/test/compile-fail/liveness-while-cond.rs
new file mode 100644
index 00000000000..a0a90e9550c
--- /dev/null
+++ b/src/test/compile-fail/liveness-while-cond.rs
@@ -0,0 +1,4 @@
+fn main() {
+    let x: bool;
+    while x { } //! ERROR use of possibly uninitialized variable: `x`
+}
diff --git a/src/test/compile-fail/liveness-while.rs b/src/test/compile-fail/liveness-while.rs
new file mode 100644
index 00000000000..b69012bc3f2
--- /dev/null
+++ b/src/test/compile-fail/liveness-while.rs
@@ -0,0 +1,7 @@
+fn f() -> int {
+    let mut x: int;
+    while 1 == 1 { x = 10; }
+    ret x; //! ERROR use of possibly uninitialized variable: `x`
+}
+
+fn main() { f(); }
diff --git a/src/test/compile-fail/move-arg.rs b/src/test/compile-fail/move-arg.rs
deleted file mode 100644
index 512cd44e206..00000000000
--- a/src/test/compile-fail/move-arg.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-// error-pattern:unsatisfied precondition constraint
-fn test(-foo: int) { assert (foo == 10); }
-
-fn main() { let x = 10; test(x); log(debug, x); }
diff --git a/src/test/compile-fail/or-init.rs b/src/test/compile-fail/or-init.rs
deleted file mode 100644
index 0bc339904ba..00000000000
--- a/src/test/compile-fail/or-init.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-// error-pattern:unsatisfied precondition constraint (for example, init(i
-
-fn main() {
-    let i: int;
-
-    log(debug, false || { i = 5; true });
-    log(debug, i);
-}
diff --git a/src/test/compile-fail/return-uninit.rs b/src/test/compile-fail/return-uninit.rs
deleted file mode 100644
index 1978ec5f420..00000000000
--- a/src/test/compile-fail/return-uninit.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-// error-pattern: precondition constraint
-
-fn f() -> int { let x: int; ret x; }
-
-fn main() { f(); }
diff --git a/src/test/compile-fail/swap-uninit.rs b/src/test/compile-fail/swap-uninit.rs
deleted file mode 100644
index fbf400db08a..00000000000
--- a/src/test/compile-fail/swap-uninit.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-// error-pattern:unsatisfied precondition
-
-fn main() { let x = 3; let y; x <-> y; }
diff --git a/src/test/compile-fail/tstate-and-init.rs b/src/test/compile-fail/tstate-and-init.rs
new file mode 100644
index 00000000000..7b254fe2e19
--- /dev/null
+++ b/src/test/compile-fail/tstate-and-init.rs
@@ -0,0 +1,7 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+fn main() {
+    let i: int = 4;
+    log(debug, false && { check is_even(i); true });
+    even(i); //! ERROR unsatisfied precondition
+}
diff --git a/src/test/compile-fail/tstate-block-uninit.rs b/src/test/compile-fail/tstate-block-uninit.rs
new file mode 100644
index 00000000000..3fbf812a9dc
--- /dev/null
+++ b/src/test/compile-fail/tstate-block-uninit.rs
@@ -0,0 +1,11 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn force(f: fn()) { f(); }
+
+fn main() {
+    let x: int = 4;
+    force(fn&() {
+        even(x); //! ERROR unsatisfied precondition
+    });
+}
diff --git a/src/test/compile-fail/tstate-break-uninit-2.rs b/src/test/compile-fail/tstate-break-uninit-2.rs
new file mode 100644
index 00000000000..8ea5446c809
--- /dev/null
+++ b/src/test/compile-fail/tstate-break-uninit-2.rs
@@ -0,0 +1,16 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn foo() -> int {
+    let x: int = 4;
+
+    while 1 != 2 {
+        break;
+        check is_even(x); //! WARNING unreachable statement
+    }
+
+    even(x); //! ERROR unsatisfied precondition
+    ret 17;
+}
+
+fn main() { log(debug, foo()); }
diff --git a/src/test/compile-fail/tstate-break-uninit.rs b/src/test/compile-fail/tstate-break-uninit.rs
new file mode 100644
index 00000000000..55146447b44
--- /dev/null
+++ b/src/test/compile-fail/tstate-break-uninit.rs
@@ -0,0 +1,16 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn foo() -> int {
+    let x: int = 4;
+
+    loop {
+        break;
+        check is_even(x); //! WARNING unreachable statement
+    }
+
+    even(x); //! ERROR unsatisfied precondition
+    ret 17;
+}
+
+fn main() { log(debug, foo()); }
diff --git a/src/test/compile-fail/tstate-ctor-unsat.rs b/src/test/compile-fail/tstate-ctor-unsat.rs
new file mode 100644
index 00000000000..d249865cd49
--- /dev/null
+++ b/src/test/compile-fail/tstate-ctor-unsat.rs
@@ -0,0 +1,25 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+class cat {
+  priv {
+    let mut meows : uint;
+  }
+
+  let how_hungry : int;
+
+  fn eat() {
+    self.how_hungry -= 5;
+  }
+
+  new(in_x : uint, in_y : int) {
+    let foo = 3;
+    self.meows = in_x + (in_y as uint);
+    self.how_hungry = even(foo); //! ERROR unsatisfied precondition
+  }
+}
+
+fn main() {
+  let nyan : cat = cat(52u, 99);
+  nyan.eat();
+}
diff --git a/src/test/compile-fail/tstate-fru.rs b/src/test/compile-fail/tstate-fru.rs
new file mode 100644
index 00000000000..a4989d86ab4
--- /dev/null
+++ b/src/test/compile-fail/tstate-fru.rs
@@ -0,0 +1,13 @@
+// -*- rust -*-
+
+type point = {x: int, y: int};
+
+pure fn test(_p: point) -> bool { true }
+fn tested(p: point) : test(p) -> point { p }
+
+fn main() {
+    let origin: point;
+    origin = {x: 0, y: 0};
+    let right: point = {x: 10 with tested(origin)};
+        //!^ ERROR precondition
+}
diff --git a/src/test/compile-fail/tstate-if-no-else.rs b/src/test/compile-fail/tstate-if-no-else.rs
new file mode 100644
index 00000000000..fbc02bf591a
--- /dev/null
+++ b/src/test/compile-fail/tstate-if-no-else.rs
@@ -0,0 +1,10 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn foo(x: int) { log(debug, x); }
+
+fn main() {
+	let x: int = 10;
+        if 1 > 2 { check is_even(x); }
+        even(x); //! ERROR unsatisfied precondition
+}
diff --git a/src/test/compile-fail/tstate-if-with-else.rs b/src/test/compile-fail/tstate-if-with-else.rs
new file mode 100644
index 00000000000..74edb4b8d8b
--- /dev/null
+++ b/src/test/compile-fail/tstate-if-with-else.rs
@@ -0,0 +1,14 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn foo(x: int) { log(debug, x); }
+
+fn main() {
+    let x: int = 10;
+    if 1 > 2 {
+        #debug("whoops");
+    } else {
+        check is_even(x);
+    }
+    even(x); //! ERROR unsatisfied precondition
+}
diff --git a/src/test/compile-fail/tstate-loop-constraints.rs b/src/test/compile-fail/tstate-loop-constraints.rs
new file mode 100644
index 00000000000..34ff0753d86
--- /dev/null
+++ b/src/test/compile-fail/tstate-loop-constraints.rs
@@ -0,0 +1,19 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn main() {
+
+    let mut x: int = 42;
+    loop {
+        loop {
+            loop {
+                check is_even(x);
+                even(x); // OK
+                loop {
+                    even(x); //! ERROR unsatisfied precondition
+                    x = 11; 
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/compile-fail/tstate-or-init.rs b/src/test/compile-fail/tstate-or-init.rs
new file mode 100644
index 00000000000..c26925929a6
--- /dev/null
+++ b/src/test/compile-fail/tstate-or-init.rs
@@ -0,0 +1,7 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+fn main() {
+    let i: int = 4;
+    log(debug, false || { check is_even(i); true });
+    even(i); //! ERROR unsatisfied precondition
+}
diff --git a/src/test/compile-fail/tstate-return.rs b/src/test/compile-fail/tstate-return.rs
new file mode 100644
index 00000000000..6d786bacd7b
--- /dev/null
+++ b/src/test/compile-fail/tstate-return.rs
@@ -0,0 +1,9 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn f() -> int {
+	let x: int = 4;
+	ret even(x); //! ERROR unsatisfied precondition
+}
+
+fn main() { f(); }
diff --git a/src/test/compile-fail/tstate-unsat-after-item.rs b/src/test/compile-fail/tstate-unsat-after-item.rs
new file mode 100644
index 00000000000..b82df3a8657
--- /dev/null
+++ b/src/test/compile-fail/tstate-unsat-after-item.rs
@@ -0,0 +1,9 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn main() {
+    let x = 4;
+    fn baz(_x: int) { }
+    bind baz(even(x)); //! ERROR unsatisfied precondition
+}
+
diff --git a/src/test/compile-fail/tstate-unsat-in-called-fn-expr.rs b/src/test/compile-fail/tstate-unsat-in-called-fn-expr.rs
new file mode 100644
index 00000000000..010ac92f7b0
--- /dev/null
+++ b/src/test/compile-fail/tstate-unsat-in-called-fn-expr.rs
@@ -0,0 +1,9 @@
+fn foo(v: [int]) : vec::is_empty(v) { #debug("%d", v[0]); }
+
+fn main() {
+    let f = fn@() {
+        let v = [1];
+        foo(v); //! ERROR unsatisfied precondition constraint
+    }();
+    log(error, f);
+}
diff --git a/src/test/compile-fail/tstate-unsat-in-fn-expr.rs b/src/test/compile-fail/tstate-unsat-in-fn-expr.rs
new file mode 100644
index 00000000000..b9cd2582537
--- /dev/null
+++ b/src/test/compile-fail/tstate-unsat-in-fn-expr.rs
@@ -0,0 +1,9 @@
+fn foo(v: [int]) : vec::is_empty(v) { #debug("%d", v[0]); }
+
+fn main() {
+    let f = fn@() {
+        let v = [1];
+        foo(v); //! ERROR unsatisfied precondition constraint
+    };
+    log(error, f());
+}
diff --git a/src/test/compile-fail/tstate-unsat.rs b/src/test/compile-fail/tstate-unsat.rs
new file mode 100644
index 00000000000..44a3e88fdee
--- /dev/null
+++ b/src/test/compile-fail/tstate-unsat.rs
@@ -0,0 +1,7 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn main() {
+    let x: int = 4;
+    even(x); //! ERROR unsatisfied precondition
+}
diff --git a/src/test/compile-fail/tstate-while-break.rs b/src/test/compile-fail/tstate-while-break.rs
new file mode 100644
index 00000000000..6a25929a85b
--- /dev/null
+++ b/src/test/compile-fail/tstate-while-break.rs
@@ -0,0 +1,15 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn test(cond: bool) {
+    let v = 4;
+    while cond {
+        check is_even(v);
+        break;
+    }
+    even(v); //! ERROR unsatisfied precondition
+}
+
+fn main() {
+    test(true);
+}
diff --git a/src/test/compile-fail/tstate-while-cond.rs b/src/test/compile-fail/tstate-while-cond.rs
new file mode 100644
index 00000000000..ae5436aa4e8
--- /dev/null
+++ b/src/test/compile-fail/tstate-while-cond.rs
@@ -0,0 +1,7 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn main() {
+    let x: int = 4;
+    while even(x) != 0 { } //! ERROR unsatisfied precondition
+}
diff --git a/src/test/compile-fail/while-loop-pred-constraints.rs b/src/test/compile-fail/tstate-while-loop-unsat-constriants.rs
index 646d2466c74..646d2466c74 100644
--- a/src/test/compile-fail/while-loop-pred-constraints.rs
+++ b/src/test/compile-fail/tstate-while-loop-unsat-constriants.rs
diff --git a/src/test/compile-fail/tstate-while.rs b/src/test/compile-fail/tstate-while.rs
new file mode 100644
index 00000000000..6091a0237a2
--- /dev/null
+++ b/src/test/compile-fail/tstate-while.rs
@@ -0,0 +1,10 @@
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn f() {
+    let mut x: int = 10;
+    while 1 == 1 { x = 10; }
+    even(x); //! ERROR unsatisfied precondition
+}
+
+fn main() { f(); }
diff --git a/src/test/compile-fail/uninit-after-item.rs b/src/test/compile-fail/uninit-after-item.rs
deleted file mode 100644
index 560c855a7e6..00000000000
--- a/src/test/compile-fail/uninit-after-item.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-// error-pattern:unsatisfied precondition constraint (for example, init(bar
-fn main() {
-    let bar;
-    fn baz(x: int) { }
-    bind baz(bar);
-}
-
diff --git a/src/test/compile-fail/use-after-move.rs b/src/test/compile-fail/use-after-move.rs
deleted file mode 100644
index 177ad0010be..00000000000
--- a/src/test/compile-fail/use-after-move.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-// error-pattern:unsatisfied precondition constraint (for example, init(x
-fn main() { let x = @5; let y <- x; log(debug, *x); }
diff --git a/src/test/compile-fail/use-uninit-2.rs b/src/test/compile-fail/use-uninit-2.rs
deleted file mode 100644
index 82946cf022a..00000000000
--- a/src/test/compile-fail/use-uninit-2.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-// error-pattern:unsatisfied precondition
-
-fn foo(x: int) { log(debug, x); }
-
-fn main() { let x: int; if 1 > 2 { x = 10; } foo(x); }
diff --git a/src/test/compile-fail/use-uninit.rs b/src/test/compile-fail/use-uninit.rs
deleted file mode 100644
index 3ad93fe0a08..00000000000
--- a/src/test/compile-fail/use-uninit.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-// error-pattern:unsatisfied precondition
-
-fn foo(x: int) { log(debug, x); }
-
-fn main() { let x: int; foo(x); }
diff --git a/src/test/compile-fail/while-bypass.rs b/src/test/compile-fail/while-bypass.rs
deleted file mode 100644
index 231fb299898..00000000000
--- a/src/test/compile-fail/while-bypass.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-// error-pattern: precondition constraint
-
-fn f() -> int { let x: int; while 1 == 1 { x = 10; } ret x; }
-
-fn main() { f(); }
diff --git a/src/test/compile-fail/while-expr.rs b/src/test/compile-fail/while-expr.rs
deleted file mode 100644
index 5ff3adf34c3..00000000000
--- a/src/test/compile-fail/while-expr.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-// error-pattern: precondition constraint
-
-fn main() { let x: bool; while x { } }
diff --git a/src/test/compile-fail/while-loop-constraints.rs b/src/test/compile-fail/while-loop-constraints.rs
deleted file mode 100644
index 38c1d471593..00000000000
--- a/src/test/compile-fail/while-loop-constraints.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-// error-pattern:unsatisfied precondition constraint (for example, init(y
-fn main() {
-
-    let y: int = 42;
-    let x: int;
-    loop {
-        log(debug, y);
-        loop {
-            loop {
-                loop { x <- y; }
-            }
-        }
-    }
-}
diff --git a/src/test/compile-fail/writing-through-uninit-vec.rs b/src/test/compile-fail/writing-through-uninit-vec.rs
deleted file mode 100644
index 283e925e207..00000000000
--- a/src/test/compile-fail/writing-through-uninit-vec.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-// error-pattern:unsatisfied precondition constraint
-
-fn test() { let w: [int]; w[5] = 0; }
-
-fn main() { test(); }
diff --git a/src/test/run-pass/liveness-assign-imm-local-after-loop.rs b/src/test/run-pass/liveness-assign-imm-local-after-loop.rs
new file mode 100644
index 00000000000..9fbf5fb447f
--- /dev/null
+++ b/src/test/run-pass/liveness-assign-imm-local-after-loop.rs
@@ -0,0 +1,10 @@
+fn test(cond: bool) {
+    let v: int;
+    v = 1;
+    loop { } // loop never terminates, so no error is reported
+    v = 2;
+}
+
+fn main() {
+	// note: don't call test()... :)
+}
diff --git a/src/test/run-pass/liveness-assign-imm-local-after-ret.rs b/src/test/run-pass/liveness-assign-imm-local-after-ret.rs
new file mode 100644
index 00000000000..4b1cc591713
--- /dev/null
+++ b/src/test/run-pass/liveness-assign-imm-local-after-ret.rs
@@ -0,0 +1,9 @@
+fn test() {
+    let _v: int;
+    _v = 1;
+    ret;
+    _v = 2; //! WARNING: unreachable statement
+}
+
+fn main() {
+}
diff --git a/src/test/run-pass/liveness-loop-break.rs b/src/test/run-pass/liveness-loop-break.rs
new file mode 100644
index 00000000000..58274555202
--- /dev/null
+++ b/src/test/run-pass/liveness-loop-break.rs
@@ -0,0 +1,14 @@
+// xfail-test --- tstate incorrectly fails this
+
+fn test() {
+    let v;
+    loop {
+        v = 3;
+        break;
+    }
+    #debug["%d", v];
+}
+
+fn main() {
+    test();
+}
diff --git a/src/test/run-pass/liveness-move-in-loop.rs b/src/test/run-pass/liveness-move-in-loop.rs
new file mode 100644
index 00000000000..a7f9547c059
--- /dev/null
+++ b/src/test/run-pass/liveness-move-in-loop.rs
@@ -0,0 +1,15 @@
+fn take(-x: int) -> int {x}
+
+fn the_loop() {
+    let mut list = [];
+    loop {
+        let x = 5;
+        if x > 3 {
+            list += [take(x)];
+        } else {
+            break;
+        }
+    }
+}
+
+fn main() {}
diff --git a/src/test/run-pass/tstate-loop-break.rs b/src/test/run-pass/tstate-loop-break.rs
new file mode 100644
index 00000000000..14a276b316a
--- /dev/null
+++ b/src/test/run-pass/tstate-loop-break.rs
@@ -0,0 +1,17 @@
+// xfail-test
+
+pure fn is_even(i: int) -> bool { (i%2) == 0 }
+fn even(i: int) : is_even(i) -> int { i }
+
+fn test() {
+    let v = 4;
+    loop {
+        check is_even(v);
+        break;
+    }
+    even(v);
+}
+
+fn main() {
+    test();
+}