about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2014-11-25 15:48:05 +0000
committerbors <bors@rust-lang.org>2014-11-25 15:48:05 +0000
commit0e06f71747749e33ca590c334658bddde97a7e54 (patch)
tree57a3fc9cca2562ea6595e95c54233915ce5b4c95
parent48ca6d1840818e4a8977d00ed62cf0e8e0e5d193 (diff)
parent5fbe0cac7c568774082755ecc098caba98352a1e (diff)
downloadrust-0e06f71747749e33ca590c334658bddde97a7e54.tar.gz
rust-0e06f71747749e33ca590c334658bddde97a7e54.zip
auto merge of #18234 : pnkfelix/rust/fsk-type-fragments-for-needsdrop-2, r=nikomatsakis
Code to fragment paths into pieces based on subparts being moved around, e.g. moving `x.1` out of a tuple `(A,B,C)` leaves behind the fragments `x.0: A` and `x.2: C`.  Further discussion in borrowck/doc.rs.

Includes differentiation between assigned_fragments and moved_fragments, support for all-but-one array fragments, and instrumentation to print out the moved/assigned/unmmoved/parents for each function, factored out into a separate submodule.

These fragments can then be used by `trans` to inject stack-local dynamic drop flags.  (They also can be hooked up with dataflow to reduce the expected number of injected flags.)
-rw-r--r--src/compiletest/errors.rs65
-rw-r--r--src/librustc/middle/borrowck/check_loans.rs97
-rw-r--r--src/librustc/middle/borrowck/doc.rs170
-rw-r--r--src/librustc/middle/borrowck/fragments.rs491
-rw-r--r--src/librustc/middle/borrowck/gather_loans/gather_moves.rs67
-rw-r--r--src/librustc/middle/borrowck/gather_loans/lifetime.rs4
-rw-r--r--src/librustc/middle/borrowck/gather_loans/mod.rs36
-rw-r--r--src/librustc/middle/borrowck/gather_loans/move_error.rs2
-rw-r--r--src/librustc/middle/borrowck/gather_loans/restrictions.rs34
-rw-r--r--src/librustc/middle/borrowck/graphviz.rs2
-rw-r--r--src/librustc/middle/borrowck/mod.rs231
-rw-r--r--src/librustc/middle/borrowck/move_data.rs199
-rw-r--r--src/librustc/middle/check_match.rs2
-rw-r--r--src/librustc/middle/check_rvalues.rs5
-rw-r--r--src/librustc/middle/check_static.rs8
-rw-r--r--src/librustc/middle/expr_use_visitor.rs259
-rw-r--r--src/librustc/middle/mem_categorization.rs70
-rw-r--r--src/librustc/middle/ty.rs56
-rw-r--r--src/librustc/middle/typeck/check/regionck.rs6
-rw-r--r--src/librustc/session/config.rs3
-rw-r--r--src/librustc/session/mod.rs12
-rw-r--r--src/librustc_trans/trans/_match.rs1
-rw-r--r--src/libsyntax/ast_map/mod.rs59
-rw-r--r--src/test/compile-fail/move-fragments-1.rs58
-rw-r--r--src/test/compile-fail/move-fragments-2.rs85
-rw-r--r--src/test/compile-fail/move-fragments-3.rs47
-rw-r--r--src/test/compile-fail/move-fragments-4.rs39
-rw-r--r--src/test/compile-fail/move-fragments-5.rs92
-rw-r--r--src/test/compile-fail/move-fragments-6.rs59
-rw-r--r--src/test/compile-fail/move-fragments-7.rs46
-rw-r--r--src/test/compile-fail/move-fragments-8.rs39
-rw-r--r--src/test/compile-fail/move-fragments-9.rs100
32 files changed, 2189 insertions, 255 deletions
diff --git a/src/compiletest/errors.rs b/src/compiletest/errors.rs
index c795e69a44d..f15db7d9371 100644
--- a/src/compiletest/errors.rs
+++ b/src/compiletest/errors.rs
@@ -7,6 +7,7 @@
 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
+use self::WhichLine::*;
 
 use std::ascii::AsciiExt;
 use std::io::{BufferedReader, File};
@@ -18,28 +19,74 @@ pub struct ExpectedError {
     pub msg: String,
 }
 
-pub static EXPECTED_PATTERN : &'static str = r"//~(?P<adjusts>\^*)\s*(?P<kind>\S*)\s*(?P<msg>.*)";
+/// Looks for either "//~| KIND MESSAGE" or "//~^^... KIND MESSAGE"
+/// The former is a "follow" that inherits its target from the preceding line;
+/// the latter is an "adjusts" that goes that many lines up.
+///
+/// Goal is to enable tests both like: //~^^^ ERROR go up three
+/// and also //~^ ERROR message one for the preceding line, and
+///          //~| ERROR message two for that same line.
+
+pub static EXPECTED_PATTERN : &'static str =
+    r"//~(?P<follow>\|)?(?P<adjusts>\^*)\s*(?P<kind>\S*)\s*(?P<msg>.*)";
+
+#[deriving(PartialEq, Show)]
+enum WhichLine { ThisLine, FollowPrevious(uint), AdjustBackward(uint) }
 
 // Load any test directives embedded in the file
 pub fn load_errors(re: &Regex, testfile: &Path) -> Vec<ExpectedError> {
     let mut rdr = BufferedReader::new(File::open(testfile).unwrap());
 
+    // `last_nonfollow_error` tracks the most recently seen
+    // line with an error template that did not use the
+    // follow-syntax, "//~| ...".
+    //
+    // (pnkfelix could not find an easy way to compose Iterator::scan
+    // and Iterator::filter_map to pass along this information into
+    // `parse_expected`. So instead I am storing that state here and
+    // updating it in the map callback below.)
+    let mut last_nonfollow_error = None;
+
     rdr.lines().enumerate().filter_map(|(line_no, ln)| {
-        parse_expected(line_no + 1, ln.unwrap().as_slice(), re)
+        parse_expected(last_nonfollow_error,
+                       line_no + 1,
+                       ln.unwrap().as_slice(), re)
+            .map(|(which, error)| {
+                match which {
+                    FollowPrevious(_) => {}
+                    _ => last_nonfollow_error = Some(error.line),
+                }
+                error
+            })
     }).collect()
 }
 
-fn parse_expected(line_num: uint, line: &str, re: &Regex) -> Option<ExpectedError> {
+fn parse_expected(last_nonfollow_error: Option<uint>,
+                  line_num: uint,
+                  line: &str,
+                  re: &Regex) -> Option<(WhichLine, ExpectedError)> {
     re.captures(line).and_then(|caps| {
         let adjusts = caps.name("adjusts").len();
         let kind = caps.name("kind").to_ascii_lower();
         let msg = caps.name("msg").trim().to_string();
+        let follow = caps.name("follow").len() > 0;
+
+        let (which, line) = if follow {
+            assert!(adjusts == 0, "use either //~| or //~^, not both.");
+            let line = last_nonfollow_error.unwrap_or_else(|| {
+                panic!("encountered //~| without preceding //~^ line.")
+            });
+            (FollowPrevious(line), line)
+        } else {
+            let which =
+                if adjusts > 0 { AdjustBackward(adjusts) } else { ThisLine };
+            let line = line_num - adjusts;
+            (which, line)
+        };
 
-        debug!("line={} kind={} msg={}", line_num, kind, msg);
-        Some(ExpectedError {
-            line: line_num - adjusts,
-            kind: kind,
-            msg: msg,
-        })
+        debug!("line={} which={} kind={} msg={}", line_num, which, kind, msg);
+        Some((which, ExpectedError { line: line,
+                                     kind: kind,
+                                     msg: msg, }))
     })
 }
diff --git a/src/librustc/middle/borrowck/check_loans.rs b/src/librustc/middle/borrowck/check_loans.rs
index 238a4ca7bd6..afcc533ffb8 100644
--- a/src/librustc/middle/borrowck/check_loans.rs
+++ b/src/librustc/middle/borrowck/check_loans.rs
@@ -19,6 +19,8 @@
 use self::UseError::*;
 
 use middle::borrowck::*;
+use middle::borrowck::LoanPathElem::*;
+use middle::borrowck::LoanPathKind::*;
 use middle::expr_use_visitor as euv;
 use middle::mem_categorization as mc;
 use middle::region;
@@ -33,49 +35,51 @@ use std::rc::Rc;
 // be less precise in its handling of Box while still allowing moves out of a
 // Box. They should be removed when OwnedPtr is removed from LoanPath.
 
-fn owned_ptr_base_path<'a>(loan_path: &'a LoanPath) -> &'a LoanPath {
+fn owned_ptr_base_path<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> &'a LoanPath<'tcx> {
     //! Returns the base of the leftmost dereference of an OwnedPtr in
     //! `loan_path`. If there is no dereference of an OwnedPtr in `loan_path`,
     //! then it just returns `loan_path` itself.
 
-    return match owned_ptr_base_path_helper(loan_path) {
+    return match helper(loan_path) {
         Some(new_loan_path) => new_loan_path,
         None => loan_path.clone()
     };
 
-    fn owned_ptr_base_path_helper<'a>(loan_path: &'a LoanPath) -> Option<&'a LoanPath> {
-        match *loan_path {
+    fn helper<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> Option<&'a LoanPath<'tcx>> {
+        match loan_path.kind {
             LpVar(_) | LpUpvar(_) => None,
             LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => {
-                match owned_ptr_base_path_helper(&**lp_base) {
+                match helper(&**lp_base) {
                     v @ Some(_) => v,
                     None => Some(&**lp_base)
                 }
             }
-            LpExtend(ref lp_base, _, _) => owned_ptr_base_path_helper(&**lp_base)
+            LpDowncast(ref lp_base, _) |
+            LpExtend(ref lp_base, _, _) => helper(&**lp_base)
         }
     }
 }
 
-fn owned_ptr_base_path_rc(loan_path: &Rc<LoanPath>) -> Rc<LoanPath> {
+fn owned_ptr_base_path_rc<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Rc<LoanPath<'tcx>> {
     //! The equivalent of `owned_ptr_base_path` for an &Rc<LoanPath> rather than
     //! a &LoanPath.
 
-    return match owned_ptr_base_path_helper(loan_path) {
+    return match helper(loan_path) {
         Some(new_loan_path) => new_loan_path,
         None => loan_path.clone()
     };
 
-    fn owned_ptr_base_path_helper(loan_path: &Rc<LoanPath>) -> Option<Rc<LoanPath>> {
-        match **loan_path {
+    fn helper<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Option<Rc<LoanPath<'tcx>>> {
+        match loan_path.kind {
             LpVar(_) | LpUpvar(_) => None,
             LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => {
-                match owned_ptr_base_path_helper(lp_base) {
+                match helper(lp_base) {
                     v @ Some(_) => v,
                     None => Some(lp_base.clone())
                 }
             }
-            LpExtend(ref lp_base, _, _) => owned_ptr_base_path_helper(lp_base)
+            LpDowncast(ref lp_base, _) |
+            LpExtend(ref lp_base, _, _) => helper(lp_base)
         }
     }
 }
@@ -84,7 +88,7 @@ struct CheckLoanCtxt<'a, 'tcx: 'a> {
     bccx: &'a BorrowckCtxt<'a, 'tcx>,
     dfcx_loans: &'a LoanDataFlow<'a, 'tcx>,
     move_data: move_data::FlowedMoveData<'a, 'tcx>,
-    all_loans: &'a [Loan],
+    all_loans: &'a [Loan<'tcx>],
 }
 
 impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> {
@@ -99,6 +103,11 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> {
         self.consume_common(consume_id, consume_span, cmt, mode);
     }
 
+    fn matched_pat(&mut self,
+                   _matched_pat: &ast::Pat,
+                   _cmt: mc::cmt,
+                   _mode: euv::MatchMode) { }
+
     fn consume_pat(&mut self,
                    consume_pat: &ast::Pat,
                    cmt: mc::cmt<'tcx>,
@@ -183,7 +192,7 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> {
 pub fn check_loans<'a, 'b, 'c, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
                                      dfcx_loans: &LoanDataFlow<'b, 'tcx>,
                                      move_data: move_data::FlowedMoveData<'c, 'tcx>,
-                                     all_loans: &[Loan],
+                                     all_loans: &[Loan<'tcx>],
                                      decl: &ast::FnDecl,
                                      body: &ast::Block) {
     debug!("check_loans(body id={})", body.id);
@@ -202,9 +211,9 @@ pub fn check_loans<'a, 'b, 'c, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
 }
 
 #[deriving(PartialEq)]
-enum UseError {
+enum UseError<'tcx> {
     UseOk,
-    UseWhileBorrowed(/*loan*/Rc<LoanPath>, /*loan*/Span)
+    UseWhileBorrowed(/*loan*/Rc<LoanPath<'tcx>>, /*loan*/Span)
 }
 
 fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind,
@@ -216,7 +225,7 @@ fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind,
 impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
     pub fn tcx(&self) -> &'a ty::ctxt<'tcx> { self.bccx.tcx }
 
-    pub fn each_issued_loan(&self, scope: region::CodeExtent, op: |&Loan| -> bool)
+    pub fn each_issued_loan(&self, scope: region::CodeExtent, op: |&Loan<'tcx>| -> bool)
                             -> bool {
         //! Iterates over each loan that has been issued
         //! on entrance to `scope`, regardless of whether it is
@@ -232,7 +241,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
 
     pub fn each_in_scope_loan(&self,
                               scope: region::CodeExtent,
-                              op: |&Loan| -> bool)
+                              op: |&Loan<'tcx>| -> bool)
                               -> bool {
         //! Like `each_issued_loan()`, but only considers loans that are
         //! currently in scope.
@@ -249,8 +258,8 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
 
     fn each_in_scope_loan_affecting_path(&self,
                                          scope: region::CodeExtent,
-                                         loan_path: &LoanPath,
-                                         op: |&Loan| -> bool)
+                                         loan_path: &LoanPath<'tcx>,
+                                         op: |&Loan<'tcx>| -> bool)
                                          -> bool {
         //! Iterates through all of the in-scope loans affecting `loan_path`,
         //! calling `op`, and ceasing iteration if `false` is returned.
@@ -294,10 +303,11 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
 
         let mut loan_path = loan_path;
         loop {
-            match *loan_path {
+            match loan_path.kind {
                 LpVar(_) | LpUpvar(_) => {
                     break;
                 }
+                LpDowncast(ref lp_base, _) |
                 LpExtend(ref lp_base, _, _) => {
                     loan_path = &**lp_base;
                 }
@@ -363,8 +373,8 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
     }
 
     pub fn report_error_if_loans_conflict(&self,
-                                          old_loan: &Loan,
-                                          new_loan: &Loan) {
+                                          old_loan: &Loan<'tcx>,
+                                          new_loan: &Loan<'tcx>) {
         //! Checks whether `old_loan` and `new_loan` can safely be issued
         //! simultaneously.
 
@@ -383,10 +393,10 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
     }
 
     pub fn report_error_if_loan_conflicts_with_restriction(&self,
-                                                           loan1: &Loan,
-                                                           loan2: &Loan,
-                                                           old_loan: &Loan,
-                                                           new_loan: &Loan)
+                                                           loan1: &Loan<'tcx>,
+                                                           loan2: &Loan<'tcx>,
+                                                           old_loan: &Loan<'tcx>,
+                                                           new_loan: &Loan<'tcx>)
                                                            -> bool {
         //! Checks whether the restrictions introduced by `loan1` would
         //! prohibit `loan2`. Returns false if an error is reported.
@@ -549,7 +559,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
         true
     }
 
-    fn is_local_variable_or_arg(&self, cmt: mc::cmt) -> bool {
+    fn is_local_variable_or_arg(&self, cmt: mc::cmt<'tcx>) -> bool {
         match cmt.cat {
           mc::cat_local(_) => true,
           _ => false
@@ -559,7 +569,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
     fn consume_common(&self,
                       id: ast::NodeId,
                       span: Span,
-                      cmt: mc::cmt,
+                      cmt: mc::cmt<'tcx>,
                       mode: euv::ConsumeMode) {
         match opt_loan_path(&cmt) {
             Some(lp) => {
@@ -600,7 +610,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
     fn check_for_copy_of_frozen_path(&self,
                                      id: ast::NodeId,
                                      span: Span,
-                                     copy_path: &LoanPath) {
+                                     copy_path: &LoanPath<'tcx>) {
         match self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow) {
             UseOk => { }
             UseWhileBorrowed(loan_path, loan_span) => {
@@ -621,7 +631,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
     fn check_for_move_of_borrowed_path(&self,
                                        id: ast::NodeId,
                                        span: Span,
-                                       move_path: &LoanPath,
+                                       move_path: &LoanPath<'tcx>,
                                        move_kind: move_data::MoveKind) {
         // We want to detect if there are any loans at all, so we search for
         // any loans incompatible with MutBorrrow, since all other kinds of
@@ -652,9 +662,9 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
 
     pub fn analyze_restrictions_on_use(&self,
                                        expr_id: ast::NodeId,
-                                       use_path: &LoanPath,
+                                       use_path: &LoanPath<'tcx>,
                                        borrow_kind: ty::BorrowKind)
-                                       -> UseError {
+                                       -> UseError<'tcx> {
         debug!("analyze_restrictions_on_use(expr_id={}, use_path={})",
                self.tcx().map.node_to_string(expr_id),
                use_path.repr(self.tcx()));
@@ -678,7 +688,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
                               id: ast::NodeId,
                               span: Span,
                               use_kind: MovedValueUseKind,
-                              lp: &Rc<LoanPath>) {
+                              lp: &Rc<LoanPath<'tcx>>) {
         /*!
          * Reports an error if `expr` (which should be a path)
          * is using a moved/uninitialized value
@@ -702,7 +712,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
                                        id: ast::NodeId,
                                        span: Span,
                                        use_kind: MovedValueUseKind,
-                                       lp: &Rc<LoanPath>)
+                                       lp: &Rc<LoanPath<'tcx>>)
     {
         /*!
          * Reports an error if assigning to `lp` will use a
@@ -722,10 +732,15 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
          *     (*p).x = 22; // not ok, p is uninitialized, can't deref
          */
 
-        match **lp {
+        match lp.kind {
             LpVar(_) | LpUpvar(_) => {
                 // assigning to `x` does not require that `x` is initialized
             }
+            LpDowncast(ref lp_base, _) => {
+                // assigning to `(P->Variant).f` is ok if assigning to `P` is ok
+                self.check_if_assigned_path_is_moved(id, span,
+                                                     use_kind, lp_base);
+            }
             LpExtend(ref lp_base, _, LpInterior(_)) => {
                 // assigning to `P.f` is ok if assigning to `P` is ok
                 self.check_if_assigned_path_is_moved(id, span,
@@ -864,7 +879,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
                         cmt = b;
                     }
 
-                    mc::cat_downcast(b) |
+                    mc::cat_downcast(b, _) |
                     mc::cat_interior(b, _) => {
                         assert_eq!(cmt.mutbl, mc::McInherited);
                         cmt = b;
@@ -915,11 +930,11 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
             }
         }
 
-        fn check_for_assignment_to_borrowed_path(
-            this: &CheckLoanCtxt,
+        fn check_for_assignment_to_borrowed_path<'a, 'tcx>(
+            this: &CheckLoanCtxt<'a, 'tcx>,
             assignment_id: ast::NodeId,
             assignment_span: Span,
-            assignee_cmt: mc::cmt)
+            assignee_cmt: mc::cmt<'tcx>)
         {
             //! Check for assignments that violate the terms of an
             //! outstanding loan.
@@ -939,7 +954,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
 
     pub fn report_illegal_mutation(&self,
                                    span: Span,
-                                   loan_path: &LoanPath,
+                                   loan_path: &LoanPath<'tcx>,
                                    loan: &Loan) {
         self.bccx.span_err(
             span,
diff --git a/src/librustc/middle/borrowck/doc.rs b/src/librustc/middle/borrowck/doc.rs
index 882b6bc8426..5b70d97b402 100644
--- a/src/librustc/middle/borrowck/doc.rs
+++ b/src/librustc/middle/borrowck/doc.rs
@@ -27,6 +27,7 @@ These docs are long. Search for the section you are interested in.
 - Formal model
 - Borrowing and loans
 - Moves and initialization
+- Drop flags and structural fragments
 - Future work
 
 # Overview
@@ -1019,6 +1020,175 @@ walk back over, identify all uses, assignments, and captures, and
 check that they are legal given the set of dataflow bits we have
 computed for that program point.
 
+# Drop flags and structural fragments
+
+In addition to the job of enforcing memory safety, the borrow checker
+code is also responsible for identifying the *structural fragments* of
+data in the function, to support out-of-band dynamic drop flags
+allocated on the stack. (For background, see [RFC PR #320].)
+
+[RFC PR #320]: https://github.com/rust-lang/rfcs/pull/320
+
+Semantically, each piece of data that has a destructor may need a
+boolean flag to indicate whether or not its destructor has been run
+yet. However, in many cases there is no need to actually maintain such
+a flag: It can be apparent from the code itself that a given path is
+always initialized (or always deinitialized) when control reaches the
+end of its owner's scope, and thus we can unconditionally emit (or
+not) the destructor invocation for that path.
+
+A simple example of this is the following:
+
+```rust
+struct D { p: int }
+impl D { fn new(x: int) -> D { ... }
+impl Drop for D { ... }
+
+fn foo(a: D, b: D, t: || -> bool) {
+    let c: D;
+    let d: D;
+    if t() { c = b; }
+}
+```
+
+At the end of the body of `foo`, the compiler knows that `a` is
+initialized, introducing a drop obligation (deallocating the boxed
+integer) for the end of `a`'s scope that is run unconditionally.
+Likewise the compiler knows that `d` is not initialized, and thus it
+leave out the drop code for `d`.
+
+The compiler cannot statically know the drop-state of `b` nor `c` at
+the end of their scope, since that depends on the value of
+`t`. Therefore, we need to insert boolean flags to track whether we
+need to drop `b` and `c`.
+
+However, the matter is not as simple as just mapping local variables
+to their corresponding drop flags when necessary. In particular, in
+addition to being able to move data out of local variables, Rust
+allows one to move values in and out of structured data.
+
+Consider the following:
+
+```rust
+struct S { x: D, y: D, z: D }
+
+fn foo(a: S, mut b: S, t: || -> bool) {
+    let mut c: S;
+    let d: S;
+    let e: S = a.clone();
+    if t() {
+        c = b;
+        b.x = e.y;
+    }
+    if t() { c.y = D::new(4); }
+}
+```
+
+As before, the drop obligations of `a` and `d` can be statically
+determined, and again the state of `b` and `c` depend on dynamic
+state. But additionally, the dynamic drop obligations introduced by
+`b` and `c` are not just per-local boolean flags. For example, if the
+first call to `t` returns `false` and the second call `true`, then at
+the end of their scope, `b` will be completely initialized, but only
+`c.y` in `c` will be initialized.  If both calls to `t` return `true`,
+then at the end of their scope, `c` will be completely initialized,
+but only `b.x` will be initialized in `b`, and only `e.x` and `e.z`
+will be initialized in `e`.
+
+Note that we need to cover the `z` field in each case in some way,
+since it may (or may not) need to be dropped, even though `z` is never
+directly mentioned in the body of the `foo` function. We call a path
+like `b.z` a *fragment sibling* of `b.x`, since the field `z` comes
+from the same structure `S` that declared the field `x` in `b.x`.
+
+In general we need to maintain boolean flags that match the
+`S`-structure of both `b` and `c`.  In addition, we need to consult
+such a flag when doing an assignment (such as `c.y = D::new(4);`
+above), in order to know whether or not there is a previous value that
+needs to be dropped before we do the assignment.
+
+So for any given function, we need to determine what flags are needed
+to track its drop obligations. Our strategy for determining the set of
+flags is to represent the fragmentation of the structure explicitly:
+by starting initially from the paths that are explicitly mentioned in
+moves and assignments (such as `b.x` and `c.y` above), and then
+traversing the structure of the path's type to identify leftover
+*unmoved fragments*: assigning into `c.y` means that `c.x` and `c.z`
+are leftover unmoved fragments. Each fragment represents a drop
+obligation that may need to be tracked. Paths that are only moved or
+assigned in their entirety (like `a` and `d`) are treated as a single
+drop obligation.
+
+The fragment construction process works by piggy-backing on the
+existing `move_data` module. We already have callbacks that visit each
+direct move and assignment; these form the basis for the sets of
+moved_leaf_paths and assigned_leaf_paths. From these leaves, we can
+walk up their parent chain to identify all of their parent paths.
+We need to identify the parents because of cases like the following:
+
+```rust
+struct Pair<X,Y>{ x: X, y: Y }
+fn foo(dd_d_d: Pair<Pair<Pair<D, D>, D>, D>) {
+    other_function(dd_d_d.x.y);
+}
+```
+
+In this code, the move of the path `dd_d.x.y` leaves behind not only
+the fragment drop-obligation `dd_d.x.x` but also `dd_d.y` as well.
+
+Once we have identified the directly-referenced leaves and their
+parents, we compute the left-over fragments, in the function
+`fragments::add_fragment_siblings`. As of this writing this works by
+looking at each directly-moved or assigned path P, and blindly
+gathering all sibling fields of P (as well as siblings for the parents
+of P, etc). After accumulating all such siblings, we filter out the
+entries added as siblings of P that turned out to be
+directly-referenced paths (or parents of directly referenced paths)
+themselves, thus leaving the never-referenced "left-overs" as the only
+thing left from the gathering step.
+
+## Array structural fragments
+
+A special case of the structural fragments discussed above are
+the elements of an array that has been passed by value, such as
+the following:
+
+```rust
+fn foo(a: [D, ..10], i: uint) -> D {
+    a[i]
+}
+```
+
+The above code moves a single element out of the input array `a`.
+The remainder of the array still needs to be dropped; i.e., it
+is a structural fragment. Note that after performing such a move,
+it is not legal to read from the array `a`. There are a number of
+ways to deal with this, but the important thing to note is that
+the semantics needs to distinguish in some manner between a
+fragment that is the *entire* array versus a fragment that represents
+all-but-one element of the array.  A place where that distinction
+would arise is the following:
+
+```rust
+fn foo(a: [D, ..10], b: [D, ..10], i: uint, t: bool) -> D {
+    if t {
+        a[i]
+    } else {
+        b[i]
+    }
+
+    // When control exits, we will need either to drop all of `a`
+    // and all-but-one of `b`, or to drop all of `b` and all-but-one
+    // of `a`.
+}
+```
+
+There are a number of ways that the trans backend could choose to
+compile this (e.g. a `[bool, ..10]` array for each such moved array;
+or an `Option<uint>` for each moved array).  From the viewpoint of the
+borrow-checker, the important thing is to record what kind of fragment
+is implied by the relevant moves.
+
 # Future work
 
 While writing up these docs, I encountered some rules I believe to be
diff --git a/src/librustc/middle/borrowck/fragments.rs b/src/librustc/middle/borrowck/fragments.rs
new file mode 100644
index 00000000000..7e766e9138e
--- /dev/null
+++ b/src/librustc/middle/borrowck/fragments.rs
@@ -0,0 +1,491 @@
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+/*!
+
+Helper routines used for fragmenting structural paths due to moves for
+tracking drop obligations. Please see the extensive comments in the
+section "Structural fragments" in `doc.rs`.
+
+*/
+use self::Fragment::*;
+
+use session::config;
+use middle::borrowck::{LoanPath};
+use middle::borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend};
+use middle::borrowck::LoanPathElem::{LpDeref, LpInterior};
+use middle::borrowck::move_data::{InvalidMovePathIndex};
+use middle::borrowck::move_data::{MoveData, MovePathIndex};
+use middle::ty;
+use middle::mem_categorization as mc;
+use util::ppaux::{Repr, UserString};
+
+use std::mem;
+use std::rc::Rc;
+use std::slice;
+use syntax::ast;
+use syntax::ast_map;
+use syntax::attr::AttrMetaMethods;
+use syntax::codemap::Span;
+
+#[deriving(PartialEq, Eq, PartialOrd, Ord)]
+enum Fragment {
+    // This represents the path described by the move path index
+    Just(MovePathIndex),
+
+    // This represents the collection of all but one of the elements
+    // from an array at the path described by the move path index.
+    // Note that attached MovePathIndex should have mem_categorization
+    // of InteriorElement (i.e. array dereference `[]`).
+    AllButOneFrom(MovePathIndex),
+}
+
+impl Fragment {
+    fn loan_path_repr<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
+        let repr = |mpi| move_data.path_loan_path(mpi).repr(tcx);
+        match *self {
+            Just(mpi) => repr(mpi),
+            AllButOneFrom(mpi) => format!("$(allbutone {})", repr(mpi)),
+        }
+    }
+
+    fn loan_path_user_string<'tcx>(&self,
+                                   move_data: &MoveData<'tcx>,
+                                   tcx: &ty::ctxt<'tcx>) -> String {
+        let user_string = |mpi| move_data.path_loan_path(mpi).user_string(tcx);
+        match *self {
+            Just(mpi) => user_string(mpi),
+            AllButOneFrom(mpi) => format!("$(allbutone {})", user_string(mpi)),
+        }
+    }
+}
+
+pub struct FragmentSets {
+    /// During move_data construction, `moved_leaf_paths` tracks paths
+    /// that have been used directly by being moved out of.  When
+    /// move_data construction has been completed, `moved_leaf_paths`
+    /// tracks such paths that are *leaf fragments* (e.g. `a.j` if we
+    /// never move out any child like `a.j.x`); any parent paths
+    /// (e.g. `a` for the `a.j` example) are moved over to
+    /// `parents_of_fragments`.
+    moved_leaf_paths: Vec<MovePathIndex>,
+
+    /// `assigned_leaf_paths` tracks paths that have been used
+    /// directly by being overwritten, but is otherwise much like
+    /// `moved_leaf_paths`.
+    assigned_leaf_paths: Vec<MovePathIndex>,
+
+    /// `parents_of_fragments` tracks paths that are definitely
+    /// parents of paths that have been moved.
+    ///
+    /// FIXME(pnkfelix) probably do not want/need
+    /// `parents_of_fragments` at all, if we can avoid it.
+    ///
+    /// Update: I do not see a way to to avoid it.  Maybe just remove
+    /// above fixme, or at least document why doing this may be hard.
+    parents_of_fragments: Vec<MovePathIndex>,
+
+    /// During move_data construction (specifically the
+    /// fixup_fragment_sets call), `unmoved_fragments` tracks paths
+    /// that have been "left behind" after a sibling has been moved or
+    /// assigned.  When move_data construction has been completed,
+    /// `unmoved_fragments` tracks paths that were *only* results of
+    /// being left-behind, and never directly moved themselves.
+    unmoved_fragments: Vec<Fragment>,
+}
+
+impl FragmentSets {
+    pub fn new() -> FragmentSets {
+        FragmentSets {
+            unmoved_fragments: Vec::new(),
+            moved_leaf_paths: Vec::new(),
+            assigned_leaf_paths: Vec::new(),
+            parents_of_fragments: Vec::new(),
+        }
+    }
+
+    pub fn add_move(&mut self, path_index: MovePathIndex) {
+        self.moved_leaf_paths.push(path_index);
+    }
+
+    pub fn add_assignment(&mut self, path_index: MovePathIndex) {
+        self.assigned_leaf_paths.push(path_index);
+    }
+}
+
+pub fn instrument_move_fragments<'tcx>(this: &MoveData<'tcx>,
+                                       tcx: &ty::ctxt<'tcx>,
+                                       sp: Span,
+                                       id: ast::NodeId) {
+    let (span_err, print) = {
+        let attrs : &[ast::Attribute];
+        attrs = match tcx.map.find(id) {
+            Some(ast_map::NodeItem(ref item)) =>
+                item.attrs.as_slice(),
+            Some(ast_map::NodeImplItem(&ast::MethodImplItem(ref m))) =>
+                m.attrs.as_slice(),
+            Some(ast_map::NodeTraitItem(&ast::ProvidedMethod(ref m))) =>
+                m.attrs.as_slice(),
+            _ => [].as_slice(),
+        };
+
+        let span_err =
+            attrs.iter().any(|a| a.check_name("rustc_move_fragments"));
+        let print = tcx.sess.debugging_opt(config::PRINT_MOVE_FRAGMENTS);
+
+        (span_err, print)
+    };
+
+    if !span_err && !print { return; }
+
+    let instrument_all_paths = |kind, vec_rc: &Vec<MovePathIndex>| {
+        for (i, mpi) in vec_rc.iter().enumerate() {
+            let render = || this.path_loan_path(*mpi).user_string(tcx);
+            if span_err {
+                tcx.sess.span_err(sp, format!("{}: `{}`", kind, render()).as_slice());
+            }
+            if print {
+                println!("id:{} {}[{}] `{}`", id, kind, i, render());
+            }
+        }
+    };
+
+    let instrument_all_fragments = |kind, vec_rc: &Vec<Fragment>| {
+        for (i, f) in vec_rc.iter().enumerate() {
+            let render = || f.loan_path_user_string(this, tcx);
+            if span_err {
+                tcx.sess.span_err(sp, format!("{}: `{}`", kind, render()).as_slice());
+            }
+            if print {
+                println!("id:{} {}[{}] `{}`", id, kind, i, render());
+            }
+        }
+    };
+
+    let fragments = this.fragments.borrow();
+    instrument_all_paths("moved_leaf_path", &fragments.moved_leaf_paths);
+    instrument_all_fragments("unmoved_fragment", &fragments.unmoved_fragments);
+    instrument_all_paths("parent_of_fragments", &fragments.parents_of_fragments);
+    instrument_all_paths("assigned_leaf_path", &fragments.assigned_leaf_paths);
+}
+
+pub fn fixup_fragment_sets<'tcx>(this: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) {
+    /*!
+     * Normalizes the fragment sets in `this`; i.e., removes
+     * duplicate entries, constructs the set of parents, and
+     * constructs the left-over fragments.
+     *
+     * Note: "left-over fragments" means paths that were not
+     * directly referenced in moves nor assignments, but must
+     * nonetheless be tracked as potential drop obligations.
+     */
+
+    let mut fragments = this.fragments.borrow_mut();
+
+    // Swap out contents of fragments so that we can modify the fields
+    // without borrowing the common fragments.
+    let mut unmoved = mem::replace(&mut fragments.unmoved_fragments, vec![]);
+    let mut parents = mem::replace(&mut fragments.parents_of_fragments, vec![]);
+    let mut moved = mem::replace(&mut fragments.moved_leaf_paths, vec![]);
+    let mut assigned = mem::replace(&mut fragments.assigned_leaf_paths, vec![]);
+
+    let path_lps = |mpis: &[MovePathIndex]| -> Vec<String> {
+        mpis.iter().map(|mpi| this.path_loan_path(*mpi).repr(tcx)).collect()
+    };
+
+    let frag_lps = |fs: &[Fragment]| -> Vec<String> {
+        fs.iter().map(|f| f.loan_path_repr(this, tcx)).collect()
+    };
+
+    // First, filter out duplicates
+    moved.sort();
+    moved.dedup();
+    debug!("fragments 1 moved: {}", path_lps(moved.as_slice()));
+
+    assigned.sort();
+    assigned.dedup();
+    debug!("fragments 1 assigned: {}", path_lps(assigned.as_slice()));
+
+    // Second, build parents from the moved and assigned.
+    for m in moved.iter() {
+        let mut p = this.path_parent(*m);
+        while p != InvalidMovePathIndex {
+            parents.push(p);
+            p = this.path_parent(p);
+        }
+    }
+    for a in assigned.iter() {
+        let mut p = this.path_parent(*a);
+        while p != InvalidMovePathIndex {
+            parents.push(p);
+            p = this.path_parent(p);
+        }
+    }
+
+    parents.sort();
+    parents.dedup();
+    debug!("fragments 2 parents: {}", path_lps(parents.as_slice()));
+
+    // Third, filter the moved and assigned fragments down to just the non-parents
+    moved.retain(|f| non_member(*f, parents.as_slice()));
+    debug!("fragments 3 moved: {}", path_lps(moved.as_slice()));
+
+    assigned.retain(|f| non_member(*f, parents.as_slice()));
+    debug!("fragments 3 assigned: {}", path_lps(assigned.as_slice()));
+
+    // Fourth, build the leftover from the moved, assigned, and parents.
+    for m in moved.as_slice().iter() {
+        let lp = this.path_loan_path(*m);
+        add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
+    }
+    for a in assigned.as_slice().iter() {
+        let lp = this.path_loan_path(*a);
+        add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
+    }
+    for p in parents.as_slice().iter() {
+        let lp = this.path_loan_path(*p);
+        add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
+    }
+
+    unmoved.sort();
+    unmoved.dedup();
+    debug!("fragments 4 unmoved: {}", frag_lps(unmoved.as_slice()));
+
+    // Fifth, filter the leftover fragments down to its core.
+    unmoved.retain(|f| match *f {
+        AllButOneFrom(_) => true,
+        Just(mpi) => non_member(mpi, parents.as_slice()) &&
+            non_member(mpi, moved.as_slice()) &&
+            non_member(mpi, assigned.as_slice())
+    });
+    debug!("fragments 5 unmoved: {}", frag_lps(unmoved.as_slice()));
+
+    // Swap contents back in.
+    fragments.unmoved_fragments = unmoved;
+    fragments.parents_of_fragments = parents;
+    fragments.moved_leaf_paths = moved;
+    fragments.assigned_leaf_paths = assigned;
+
+    return;
+
+    fn non_member(elem: MovePathIndex, set: &[MovePathIndex]) -> bool {
+        match set.binary_search_elem(&elem) {
+            slice::Found(_) => false,
+            slice::NotFound(_) => true,
+        }
+    }
+}
+
+fn add_fragment_siblings<'tcx>(this: &MoveData<'tcx>,
+                               tcx: &ty::ctxt<'tcx>,
+                               gathered_fragments: &mut Vec<Fragment>,
+                               lp: Rc<LoanPath<'tcx>>,
+                               origin_id: Option<ast::NodeId>) {
+    /*!
+     * Adds all of the precisely-tracked siblings of `lp` as
+     * potential move paths of interest. For example, if `lp`
+     * represents `s.x.j`, then adds moves paths for `s.x.i` and
+     * `s.x.k`, the siblings of `s.x.j`.
+     */
+
+    match lp.kind {
+        LpVar(_) | LpUpvar(..) => {} // Local variables have no siblings.
+
+        // Consuming a downcast is like consuming the original value, so propage inward.
+        LpDowncast(ref loan_parent, _) => {
+            add_fragment_siblings(this, tcx, gathered_fragments, loan_parent.clone(), origin_id);
+        }
+
+        // *LV for OwnedPtr consumes the contents of the box (at
+        // least when it is non-copy...), so propagate inward.
+        LpExtend(ref loan_parent, _, LpDeref(mc::OwnedPtr)) => {
+            add_fragment_siblings(this, tcx, gathered_fragments, loan_parent.clone(), origin_id);
+        }
+
+        // *LV for unsafe and borrowed pointers do not consume their loan path, so stop here.
+        LpExtend(_, _, LpDeref(mc::UnsafePtr(..)))   |
+        LpExtend(_, _, LpDeref(mc::Implicit(..)))    |
+        LpExtend(_, _, LpDeref(mc::BorrowedPtr(..))) => {}
+
+        // FIXME(pnkfelix): LV[j] should be tracked, at least in the
+        // sense of we will track the remaining drop obligation of the
+        // rest of the array.
+        //
+        // LV[j] is not tracked precisely
+        LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => {
+            let mp = this.move_path(tcx, lp.clone());
+            gathered_fragments.push(AllButOneFrom(mp));
+        }
+
+        // field access LV.x and tuple access LV#k are the cases
+        // we are interested in
+        LpExtend(ref loan_parent, mc,
+                 LpInterior(mc::InteriorField(ref field_name))) => {
+            let enum_variant_info = match loan_parent.kind {
+                LpDowncast(ref loan_parent_2, variant_def_id) =>
+                    Some((variant_def_id, loan_parent_2.clone())),
+                LpExtend(..) | LpVar(..) | LpUpvar(..) =>
+                    None,
+            };
+            add_fragment_siblings_for_extension(
+                this,
+                tcx,
+                gathered_fragments,
+                loan_parent, mc, field_name, &lp, origin_id, enum_variant_info);
+        }
+    }
+}
+
+fn add_fragment_siblings_for_extension<'tcx>(this: &MoveData<'tcx>,
+                                             tcx: &ty::ctxt<'tcx>,
+                                             gathered_fragments: &mut Vec<Fragment>,
+                                             parent_lp: &Rc<LoanPath<'tcx>>,
+                                             mc: mc::MutabilityCategory,
+                                             origin_field_name: &mc::FieldName,
+                                             origin_lp: &Rc<LoanPath<'tcx>>,
+                                             origin_id: Option<ast::NodeId>,
+                                             enum_variant_info: Option<(ast::DefId,
+                                                                        Rc<LoanPath<'tcx>>)>) {
+    /*!
+     * We have determined that `origin_lp` destructures to
+     * LpExtend(parent, original_field_name). Based on this,
+     * add move paths for all of the siblings of `origin_lp`.
+     */
+
+    let parent_ty = parent_lp.to_type();
+
+    let add_fragment_sibling_local = |field_name| {
+        add_fragment_sibling_core(
+            this, tcx, gathered_fragments, parent_lp.clone(), mc, field_name, origin_lp);
+    };
+
+    match (&parent_ty.sty, enum_variant_info) {
+        (&ty::ty_tup(ref v), None) => {
+            let tuple_idx = match *origin_field_name {
+                mc::PositionalField(tuple_idx) => tuple_idx,
+                mc::NamedField(_) =>
+                    panic!("tuple type {} should not have named fields.",
+                           parent_ty.repr(tcx)),
+            };
+            let tuple_len = v.len();
+            for i in range(0, tuple_len) {
+                if i == tuple_idx { continue }
+                let field_name = mc::PositionalField(i);
+                add_fragment_sibling_local(field_name);
+            }
+        }
+
+        (&ty::ty_struct(def_id, ref _substs), None) => {
+            let fields = ty::lookup_struct_fields(tcx, def_id);
+            match *origin_field_name {
+                mc::NamedField(ast_name) => {
+                    for f in fields.iter() {
+                        if f.name == ast_name {
+                            continue;
+                        }
+                        let field_name = mc::NamedField(f.name);
+                        add_fragment_sibling_local(field_name);
+                    }
+                }
+                mc::PositionalField(tuple_idx) => {
+                    for (i, _f) in fields.iter().enumerate() {
+                        if i == tuple_idx {
+                            continue
+                        }
+                        let field_name = mc::PositionalField(i);
+                        add_fragment_sibling_local(field_name);
+                    }
+                }
+            }
+        }
+
+        (&ty::ty_enum(enum_def_id, ref substs), ref enum_variant_info) => {
+            let variant_info = {
+                let mut variants = ty::substd_enum_variants(tcx, enum_def_id, substs);
+                match *enum_variant_info {
+                    Some((variant_def_id, ref _lp2)) =>
+                        variants.iter()
+                        .find(|variant| variant.id == variant_def_id)
+                        .expect("enum_variant_with_id(): no variant exists with that ID")
+                        .clone(),
+                    None => {
+                        assert_eq!(variants.len(), 1);
+                        variants.pop().unwrap()
+                    }
+                }
+            };
+            match *origin_field_name {
+                mc::NamedField(ast_name) => {
+                    let variant_arg_names = variant_info.arg_names.as_ref().unwrap();
+                    for variant_arg_ident in variant_arg_names.iter() {
+                        if variant_arg_ident.name == ast_name {
+                            continue;
+                        }
+                        let field_name = mc::NamedField(variant_arg_ident.name);
+                        add_fragment_sibling_local(field_name);
+                    }
+                }
+                mc::PositionalField(tuple_idx) => {
+                    let variant_arg_types = &variant_info.args;
+                    for (i, _variant_arg_ty) in variant_arg_types.iter().enumerate() {
+                        if tuple_idx == i {
+                            continue;
+                        }
+                        let field_name = mc::PositionalField(i);
+                        add_fragment_sibling_local(field_name);
+                    }
+                }
+            }
+        }
+
+        ref sty_and_variant_info => {
+            let msg = format!("type {} ({}) is not fragmentable",
+                              parent_ty.repr(tcx), sty_and_variant_info);
+            let opt_span = origin_id.and_then(|id|tcx.map.opt_span(id));
+            tcx.sess.opt_span_bug(opt_span, msg.as_slice())
+        }
+    }
+}
+
+fn add_fragment_sibling_core<'tcx>(this: &MoveData<'tcx>,
+                                   tcx: &ty::ctxt<'tcx>,
+                                   gathered_fragments: &mut Vec<Fragment>,
+                                   parent: Rc<LoanPath<'tcx>>,
+                                   mc: mc::MutabilityCategory,
+                                   new_field_name: mc::FieldName,
+                                   origin_lp: &Rc<LoanPath<'tcx>>) -> MovePathIndex {
+    /*!
+     * Adds the single sibling `LpExtend(parent, new_field_name)`
+     * of `origin_lp` (the original loan-path).
+     */
+    let opt_variant_did = match parent.kind {
+        LpDowncast(_, variant_did) => Some(variant_did),
+        LpVar(..) | LpUpvar(..) | LpExtend(..) => None,
+    };
+
+    let loan_path_elem = LpInterior(mc::InteriorField(new_field_name));
+    let new_lp_type = match new_field_name {
+        mc::NamedField(ast_name) =>
+            ty::named_element_ty(tcx, parent.to_type(), ast_name, opt_variant_did),
+        mc::PositionalField(idx) =>
+            ty::positional_element_ty(tcx, parent.to_type(), idx, opt_variant_did),
+    };
+    let new_lp_variant = LpExtend(parent, mc, loan_path_elem);
+    let new_lp = LoanPath::new(new_lp_variant, new_lp_type.unwrap());
+    debug!("add_fragment_sibling_core(new_lp={}, origin_lp={})",
+           new_lp.repr(tcx), origin_lp.repr(tcx));
+    let mp = this.move_path(tcx, Rc::new(new_lp));
+
+    // Do not worry about checking for duplicates here; we will sort
+    // and dedup after all are added.
+    gathered_fragments.push(Just(mp));
+
+    mp
+}
diff --git a/src/librustc/middle/borrowck/gather_loans/gather_moves.rs b/src/librustc/middle/borrowck/gather_loans/gather_moves.rs
index 4caea5ae423..1d0b0558bb1 100644
--- a/src/librustc/middle/borrowck/gather_loans/gather_moves.rs
+++ b/src/librustc/middle/borrowck/gather_loans/gather_moves.rs
@@ -13,6 +13,7 @@
  */
 
 use middle::borrowck::*;
+use middle::borrowck::LoanPathKind::*;
 use middle::borrowck::gather_loans::move_error::MoveSpanAndPath;
 use middle::borrowck::gather_loans::move_error::{MoveError, MoveErrorCollector};
 use middle::borrowck::move_data::*;
@@ -32,17 +33,18 @@ struct GatherMoveInfo<'tcx> {
     span_path_opt: Option<MoveSpanAndPath>
 }
 
-pub fn gather_decl(bccx: &BorrowckCtxt,
-                   move_data: &MoveData,
-                   decl_id: ast::NodeId,
-                   _decl_span: Span,
-                   var_id: ast::NodeId) {
-    let loan_path = Rc::new(LpVar(var_id));
+pub fn gather_decl<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+                             move_data: &MoveData<'tcx>,
+                             decl_id: ast::NodeId,
+                             _decl_span: Span,
+                             var_id: ast::NodeId) {
+    let ty = ty::node_id_to_type(bccx.tcx, var_id);
+    let loan_path = Rc::new(LoanPath::new(LpVar(var_id), ty));
     move_data.add_move(bccx.tcx, loan_path, decl_id, Declared);
 }
 
 pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
-                                       move_data: &MoveData,
+                                       move_data: &MoveData<'tcx>,
                                        move_error_collector: &MoveErrorCollector<'tcx>,
                                        move_expr_id: ast::NodeId,
                                        cmt: mc::cmt<'tcx>,
@@ -60,8 +62,39 @@ pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
     gather_move(bccx, move_data, move_error_collector, move_info);
 }
 
+pub fn gather_match_variant<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+                                      move_data: &MoveData<'tcx>,
+                                      _move_error_collector: &MoveErrorCollector<'tcx>,
+                                      move_pat: &ast::Pat,
+                                      cmt: mc::cmt<'tcx>,
+                                      mode: euv::MatchMode) {
+    let tcx = bccx.tcx;
+    debug!("gather_match_variant(move_pat={}, cmt={}, mode={})",
+           move_pat.id, cmt.repr(tcx), mode);
+
+    let opt_lp = opt_loan_path(&cmt);
+    match opt_lp {
+        Some(lp) => {
+            match lp.kind {
+                LpDowncast(ref base_lp, _) =>
+                    move_data.add_variant_match(
+                        tcx, lp.clone(), move_pat.id, base_lp.clone(), mode),
+                _ => panic!("should only call gather_match_variant \
+                             for cat_downcast cmt"),
+            }
+        }
+        None => {
+            // We get None when input to match is non-path (e.g.
+            // temporary result like a function call). Since no
+            // loan-path is being matched, no need to record a
+            // downcast.
+            return;
+        }
+    }
+}
+
 pub fn gather_move_from_pat<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
-                                      move_data: &MoveData,
+                                      move_data: &MoveData<'tcx>,
                                       move_error_collector: &MoveErrorCollector<'tcx>,
                                       move_pat: &ast::Pat,
                                       cmt: mc::cmt<'tcx>) {
@@ -82,7 +115,7 @@ pub fn gather_move_from_pat<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
 }
 
 fn gather_move<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
-                         move_data: &MoveData,
+                         move_data: &MoveData<'tcx>,
                          move_error_collector: &MoveErrorCollector<'tcx>,
                          move_info: GatherMoveInfo<'tcx>) {
     debug!("gather_move(move_id={}, cmt={})",
@@ -112,13 +145,13 @@ fn gather_move<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
     }
 }
 
-pub fn gather_assignment(bccx: &BorrowckCtxt,
-                         move_data: &MoveData,
-                         assignment_id: ast::NodeId,
-                         assignment_span: Span,
-                         assignee_loan_path: Rc<LoanPath>,
-                         assignee_id: ast::NodeId,
-                         mode: euv::MutateMode) {
+pub fn gather_assignment<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+                                   move_data: &MoveData<'tcx>,
+                                   assignment_id: ast::NodeId,
+                                   assignment_span: Span,
+                                   assignee_loan_path: Rc<LoanPath<'tcx>>,
+                                   assignee_id: ast::NodeId,
+                                   mode: euv::MutateMode) {
     move_data.add_assignment(bccx.tcx,
                              assignee_loan_path,
                              assignment_id,
@@ -144,7 +177,7 @@ fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
             None
         }
 
-        mc::cat_downcast(ref b) |
+        mc::cat_downcast(ref b, _) |
         mc::cat_interior(ref b, _) => {
             match b.ty.sty {
                 ty::ty_struct(did, _) | ty::ty_enum(did, _) => {
diff --git a/src/librustc/middle/borrowck/gather_loans/lifetime.rs b/src/librustc/middle/borrowck/gather_loans/lifetime.rs
index 5b8cb0608b3..7a7ed3e75d2 100644
--- a/src/librustc/middle/borrowck/gather_loans/lifetime.rs
+++ b/src/librustc/middle/borrowck/gather_loans/lifetime.rs
@@ -85,7 +85,7 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
                 Ok(())
             }
 
-            mc::cat_downcast(ref base) |
+            mc::cat_downcast(ref base, _) |
             mc::cat_deref(ref base, _, mc::OwnedPtr) |     // L-Deref-Send
             mc::cat_interior(ref base, _) => {             // L-Field
                 self.check(base, discr_scope)
@@ -130,7 +130,7 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
             mc::cat_deref(_, _, mc::Implicit(_, r)) => {
                 r
             }
-            mc::cat_downcast(ref cmt) |
+            mc::cat_downcast(ref cmt, _) |
             mc::cat_deref(ref cmt, _, mc::OwnedPtr) |
             mc::cat_interior(ref cmt, _) => {
                 self.scope(cmt)
diff --git a/src/librustc/middle/borrowck/gather_loans/mod.rs b/src/librustc/middle/borrowck/gather_loans/mod.rs
index c36a466919b..088b62a12cf 100644
--- a/src/librustc/middle/borrowck/gather_loans/mod.rs
+++ b/src/librustc/middle/borrowck/gather_loans/mod.rs
@@ -17,6 +17,7 @@
 // sure that all of these loans are honored.
 
 use middle::borrowck::*;
+use middle::borrowck::LoanPathKind::*;
 use middle::borrowck::move_data::MoveData;
 use middle::expr_use_visitor as euv;
 use middle::mem_categorization as mc;
@@ -35,10 +36,10 @@ mod restrictions;
 mod gather_moves;
 mod move_error;
 
-pub fn gather_loans_in_fn(bccx: &BorrowckCtxt,
-                          decl: &ast::FnDecl,
-                          body: &ast::Block)
-                          -> (Vec<Loan>, move_data::MoveData)
+pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+                                    decl: &ast::FnDecl,
+                                    body: &ast::Block)
+                                    -> (Vec<Loan<'tcx>>, move_data::MoveData<'tcx>)
 {
     let mut glcx = GatherLoanCtxt {
         bccx: bccx,
@@ -60,9 +61,9 @@ pub fn gather_loans_in_fn(bccx: &BorrowckCtxt,
 
 struct GatherLoanCtxt<'a, 'tcx: 'a> {
     bccx: &'a BorrowckCtxt<'a, 'tcx>,
-    move_data: move_data::MoveData,
+    move_data: move_data::MoveData<'tcx>,
     move_error_collector: move_error::MoveErrorCollector<'tcx>,
-    all_loans: Vec<Loan>,
+    all_loans: Vec<Loan<'tcx>>,
     /// `item_ub` is used as an upper-bound on the lifetime whenever we
     /// ask for the scope of an expression categorized as an upvar.
     item_ub: region::CodeExtent,
@@ -87,6 +88,24 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for GatherLoanCtxt<'a, 'tcx> {
         }
     }
 
+    fn matched_pat(&mut self,
+                   matched_pat: &ast::Pat,
+                   cmt: mc::cmt<'tcx>,
+                   mode: euv::MatchMode) {
+        debug!("matched_pat(matched_pat={}, cmt={}, mode={})",
+               matched_pat.repr(self.tcx()),
+               cmt.repr(self.tcx()),
+               mode);
+
+        match cmt.cat {
+            mc::cat_downcast(..) =>
+                gather_moves::gather_match_variant(
+                    self.bccx, &self.move_data, &self.move_error_collector,
+                    matched_pat, cmt, mode),
+            _ => {}
+        }
+    }
+
     fn consume_pat(&mut self,
                    consume_pat: &ast::Pat,
                    cmt: mc::cmt<'tcx>,
@@ -395,11 +414,12 @@ impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> {
         //! For mutable loans of content whose mutability derives
         //! from a local variable, mark the mutability decl as necessary.
 
-        match *loan_path {
+        match loan_path.kind {
             LpVar(local_id) |
             LpUpvar(ty::UpvarId{ var_id: local_id, closure_expr_id: _ }) => {
                 self.tcx().used_mut_nodes.borrow_mut().insert(local_id);
             }
+            LpDowncast(ref base, _) |
             LpExtend(ref base, mc::McInherited, _) |
             LpExtend(ref base, mc::McDeclared, _) => {
                 self.mark_loan_path_as_mutated(&**base);
@@ -426,7 +446,7 @@ impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> {
         }
     }
 
-    pub fn compute_kill_scope(&self, loan_scope: region::CodeExtent, lp: &LoanPath)
+    pub fn compute_kill_scope(&self, loan_scope: region::CodeExtent, lp: &LoanPath<'tcx>)
                               -> region::CodeExtent {
         //! Determine when the loan restrictions go out of scope.
         //! This is either when the lifetime expires or when the
diff --git a/src/librustc/middle/borrowck/gather_loans/move_error.rs b/src/librustc/middle/borrowck/gather_loans/move_error.rs
index c7ffcc3ac1d..aaa0fa88242 100644
--- a/src/librustc/middle/borrowck/gather_loans/move_error.rs
+++ b/src/librustc/middle/borrowck/gather_loans/move_error.rs
@@ -124,7 +124,7 @@ fn report_cannot_move_out_of<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
                         bccx.cmt_to_string(&*move_from)).as_slice());
         }
 
-        mc::cat_downcast(ref b) |
+        mc::cat_downcast(ref b, _) |
         mc::cat_interior(ref b, _) => {
             match b.ty.sty {
                 ty::ty_struct(did, _)
diff --git a/src/librustc/middle/borrowck/gather_loans/restrictions.rs b/src/librustc/middle/borrowck/gather_loans/restrictions.rs
index 9b9a5e61393..adae34b49dc 100644
--- a/src/librustc/middle/borrowck/gather_loans/restrictions.rs
+++ b/src/librustc/middle/borrowck/gather_loans/restrictions.rs
@@ -15,6 +15,8 @@
 pub use self::RestrictionResult::*;
 
 use middle::borrowck::*;
+use middle::borrowck::LoanPathElem::*;
+use middle::borrowck::LoanPathKind::*;
 use middle::expr_use_visitor as euv;
 use middle::mem_categorization as mc;
 use middle::ty;
@@ -24,9 +26,9 @@ use util::ppaux::Repr;
 use std::rc::Rc;
 
 #[deriving(Show)]
-pub enum RestrictionResult {
+pub enum RestrictionResult<'tcx> {
     Safe,
-    SafeIf(Rc<LoanPath>, Vec<Rc<LoanPath>>)
+    SafeIf(Rc<LoanPath<'tcx>>, Vec<Rc<LoanPath<'tcx>>>)
 }
 
 pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
@@ -34,7 +36,7 @@ pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
                                       cause: euv::LoanCause,
                                       cmt: mc::cmt<'tcx>,
                                       loan_region: ty::Region)
-                                      -> RestrictionResult {
+                                      -> RestrictionResult<'tcx> {
     let ctxt = RestrictionsContext {
         bccx: bccx,
         span: span,
@@ -57,9 +59,11 @@ struct RestrictionsContext<'a, 'tcx: 'a> {
 
 impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
     fn restrict(&self,
-                cmt: mc::cmt<'tcx>) -> RestrictionResult {
+                cmt: mc::cmt<'tcx>) -> RestrictionResult<'tcx> {
         debug!("restrict(cmt={})", cmt.repr(self.bccx.tcx));
 
+        let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
+
         match cmt.cat.clone() {
             mc::cat_rvalue(..) => {
                 // Effectively, rvalues are stored into a
@@ -72,17 +76,17 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
 
             mc::cat_local(local_id) => {
                 // R-Variable, locally declared
-                let lp = Rc::new(LpVar(local_id));
+                let lp = new_lp(LpVar(local_id));
                 SafeIf(lp.clone(), vec![lp])
             }
 
             mc::cat_upvar(mc::Upvar { id, .. }) => {
                 // R-Variable, captured into closure
-                let lp = Rc::new(LpUpvar(id));
+                let lp = new_lp(LpUpvar(id));
                 SafeIf(lp.clone(), vec![lp])
             }
 
-            mc::cat_downcast(cmt_base) => {
+            mc::cat_downcast(cmt_base, _) => {
                 // When we borrow the interior of an enum, we have to
                 // ensure the enum itself is not mutated, because that
                 // could cause the type of the memory to change.
@@ -96,10 +100,9 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
                 // the memory, so no additional restrictions are
                 // needed.
                 let result = self.restrict(cmt_base);
-                self.extend(result, cmt.mutbl, LpInterior(i))
+                self.extend(result, &cmt, LpInterior(i))
             }
 
-
             mc::cat_static_item(..) => {
                 Safe
             }
@@ -116,7 +119,7 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
                         // Eventually we should make these non-special and
                         // just rely on Deref<T> implementation.
                         let result = self.restrict(cmt_base);
-                        self.extend(result, cmt.mutbl, LpDeref(pk))
+                        self.extend(result, &cmt, LpDeref(pk))
                     }
                     mc::Implicit(bk, lt) | mc::BorrowedPtr(bk, lt) => {
                         // R-Deref-[Mut-]Borrowed
@@ -140,7 +143,7 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
                                 // references lifetime ends (by a newly-unfrozen
                                 // borrow).
                                 let result = self.restrict(cmt_base);
-                                self.extend(result, cmt.mutbl, LpDeref(pk))
+                                self.extend(result, &cmt, LpDeref(pk))
                             }
                         }
                     }
@@ -152,13 +155,14 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
     }
 
     fn extend(&self,
-              result: RestrictionResult,
-              mc: mc::MutabilityCategory,
-              elem: LoanPathElem) -> RestrictionResult {
+              result: RestrictionResult<'tcx>,
+              cmt: &mc::cmt<'tcx>,
+              elem: LoanPathElem) -> RestrictionResult<'tcx> {
         match result {
             Safe => Safe,
             SafeIf(base_lp, mut base_vec) => {
-                let lp = Rc::new(LpExtend(base_lp, mc, elem));
+                let v = LpExtend(base_lp, cmt.mutbl, elem);
+                let lp = Rc::new(LoanPath::new(v, cmt.ty));
                 base_vec.push(lp.clone());
                 SafeIf(lp, base_vec)
             }
diff --git a/src/librustc/middle/borrowck/graphviz.rs b/src/librustc/middle/borrowck/graphviz.rs
index 4a2f57735e1..41f7b7dba1d 100644
--- a/src/librustc/middle/borrowck/graphviz.rs
+++ b/src/librustc/middle/borrowck/graphviz.rs
@@ -80,7 +80,7 @@ impl<'a, 'tcx> DataflowLabeller<'a, 'tcx> {
                                      e: EntryOrExit,
                                      cfgidx: CFGIndex,
                                      dfcx: &DataFlowContext<'a, 'tcx, O>,
-                                     to_lp: |uint| -> Rc<LoanPath>) -> String {
+                                     to_lp: |uint| -> Rc<LoanPath<'tcx>>) -> String {
         let mut saw_some = false;
         let mut set = "{".to_string();
         dfcx.each_bit_for_node(e, cfgidx, |index| {
diff --git a/src/librustc/middle/borrowck/mod.rs b/src/librustc/middle/borrowck/mod.rs
index 1133bc41edf..45040cd7b10 100644
--- a/src/librustc/middle/borrowck/mod.rs
+++ b/src/librustc/middle/borrowck/mod.rs
@@ -12,7 +12,7 @@
 
 #![allow(non_camel_case_types)]
 
-pub use self::LoanPath::*;
+pub use self::LoanPathKind::*;
 pub use self::LoanPathElem::*;
 pub use self::bckerr_code::*;
 pub use self::AliasableViolationKind::*;
@@ -125,7 +125,7 @@ fn borrowck_item(this: &mut BorrowckCtxt, item: &ast::Item) {
 
 /// Collection of conclusions determined via borrow checker analyses.
 pub struct AnalysisData<'a, 'tcx: 'a> {
-    pub all_loans: Vec<Loan>,
+    pub all_loans: Vec<Loan<'tcx>>,
     pub loans: DataFlowContext<'a, 'tcx, LoanDataFlowOperator>,
     pub move_data: move_data::FlowedMoveData<'a, 'tcx>,
 }
@@ -143,6 +143,9 @@ fn borrowck_fn(this: &mut BorrowckCtxt,
                        move_data:flowed_moves } =
         build_borrowck_dataflow_data(this, fk, decl, &cfg, body, sp, id);
 
+    move_data::fragments::instrument_move_fragments(&flowed_moves.move_data,
+                                                    this.tcx, sp, id);
+
     check_loans::check_loans(this, &loan_dfcx, flowed_moves,
                              all_loans.as_slice(), decl, body);
 
@@ -254,11 +257,11 @@ pub type BckResult<'tcx, T> = Result<T, BckError<'tcx>>;
 // Loans and loan paths
 
 /// Record of a loan that was issued.
-pub struct Loan {
+pub struct Loan<'tcx> {
     index: uint,
-    loan_path: Rc<LoanPath>,
+    loan_path: Rc<LoanPath<'tcx>>,
     kind: ty::BorrowKind,
-    restricted_paths: Vec<Rc<LoanPath>>,
+    restricted_paths: Vec<Rc<LoanPath<'tcx>>>,
 
     /// gen_scope indicates where loan is introduced. Typically the
     /// loan is introduced at the point of the borrow, but in some
@@ -276,19 +279,60 @@ pub struct Loan {
     cause: euv::LoanCause,
 }
 
-impl Loan {
-    pub fn loan_path(&self) -> Rc<LoanPath> {
+impl<'tcx> Loan<'tcx> {
+    pub fn loan_path(&self) -> Rc<LoanPath<'tcx>> {
         self.loan_path.clone()
     }
 }
 
+#[deriving(Eq, Hash, Show)]
+pub struct LoanPath<'tcx> {
+    kind: LoanPathKind<'tcx>,
+    ty: ty::Ty<'tcx>,
+}
+
+impl<'tcx> LoanPath<'tcx> {
+    pub fn eq_debug(&self, that: &LoanPath<'tcx>, tcx: &ty::ctxt<'tcx>) -> bool {
+        let r = self.kind == that.kind;
+        if r && self.ty != that.ty {
+            panic!("eq variants ineq types: {} == {}, {} != {}",
+                   self.repr(tcx), that.repr(tcx),
+                   self.ty.repr(tcx), that.ty.repr(tcx));
+        }
+        r
+    }
+}
+
+impl<'tcx> PartialEq for LoanPath<'tcx> {
+    fn eq(&self, that: &LoanPath<'tcx>) -> bool {
+        let r = self.kind == that.kind;
+        debug_assert!(self.ty == that.ty || !r,
+                      "Somehow loan paths are equal though their tys are not.");
+        r
+    }
+}
+
 #[deriving(PartialEq, Eq, Hash, Show)]
-pub enum LoanPath {
-    LpVar(ast::NodeId),               // `x` in doc.rs
-    LpUpvar(ty::UpvarId),             // `x` captured by-value into closure
-    LpExtend(Rc<LoanPath>, mc::MutabilityCategory, LoanPathElem)
+pub enum LoanPathKind<'tcx> {
+    LpVar(ast::NodeId),                         // `x` in doc.rs
+    LpUpvar(ty::UpvarId),                       // `x` captured by-value into closure
+    LpDowncast(Rc<LoanPath<'tcx>>, ast::DefId), // `x` downcast to particular enum variant
+    LpExtend(Rc<LoanPath<'tcx>>, mc::MutabilityCategory, LoanPathElem)
 }
 
+impl<'tcx> LoanPath<'tcx> {
+    fn new(kind: LoanPathKind<'tcx>, ty: ty::Ty<'tcx>) -> LoanPath<'tcx> {
+        LoanPath { kind: kind, ty: ty }
+    }
+
+    fn to_type(&self) -> ty::Ty<'tcx> { self.ty }
+}
+
+// FIXME (pnkfelix): See discussion here
+// https://github.com/pnkfelix/rust/commit/
+//     b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003
+static DOWNCAST_PRINTED_OPERATOR : &'static str = " as ";
+
 #[deriving(PartialEq, Eq, Hash, Show)]
 pub enum LoanPathElem {
     LpDeref(mc::PointerKind),    // `*LV` in doc.rs
@@ -296,7 +340,7 @@ pub enum LoanPathElem {
 }
 
 pub fn closure_to_block(closure_id: ast::NodeId,
-                    tcx: &ty::ctxt) -> ast::NodeId {
+                        tcx: &ty::ctxt) -> ast::NodeId {
     match tcx.map.get(closure_id) {
         ast_map::NodeExpr(expr) => match expr.node {
             ast::ExprProc(_, ref block) |
@@ -311,20 +355,21 @@ pub fn closure_to_block(closure_id: ast::NodeId,
     }
 }
 
-impl LoanPath {
-    pub fn kill_scope(&self, tcx: &ty::ctxt) -> region::CodeExtent {
-        match *self {
+impl<'tcx> LoanPath<'tcx> {
+    pub fn kill_scope(&self, tcx: &ty::ctxt<'tcx>) -> region::CodeExtent {
+        match self.kind {
             LpVar(local_id) => tcx.region_maps.var_scope(local_id),
             LpUpvar(upvar_id) => {
                 let block_id = closure_to_block(upvar_id.closure_expr_id, tcx);
                 region::CodeExtent::from_node_id(block_id)
             }
+            LpDowncast(ref base, _) |
             LpExtend(ref base, _, _) => base.kill_scope(tcx),
         }
     }
 
-    fn has_fork(&self, other: &LoanPath) -> bool {
-        match (self, other) {
+    fn has_fork(&self, other: &LoanPath<'tcx>) -> bool {
+        match (&self.kind, &other.kind) {
             (&LpExtend(ref base, _, LpInterior(id)), &LpExtend(ref base2, _, LpInterior(id2))) =>
                 if id == id2 {
                     base.has_fork(&**base2)
@@ -338,44 +383,67 @@ impl LoanPath {
     }
 
     fn depth(&self) -> uint {
-        match *self {
+        match self.kind {
             LpExtend(ref base, _, LpDeref(_)) => base.depth(),
             LpExtend(ref base, _, LpInterior(_)) => base.depth() + 1,
             _ => 0,
         }
     }
 
-    fn common(&self, other: &LoanPath) -> Option<LoanPath> {
-        match (self, other) {
-            (&LpExtend(ref base, a, LpInterior(id)), &LpExtend(ref base2, _, LpInterior(id2))) =>
+    fn common(&self, other: &LoanPath<'tcx>) -> Option<LoanPath<'tcx>> {
+        match (&self.kind, &other.kind) {
+            (&LpExtend(ref base, a, LpInterior(id)),
+             &LpExtend(ref base2, _, LpInterior(id2))) => {
                 if id == id2 {
                     base.common(&**base2).map(|x| {
                         let xd = x.depth();
                         if base.depth() == xd && base2.depth() == xd {
-                            LpExtend(Rc::new(x), a, LpInterior(id))
+                            assert_eq!(base.ty, base2.ty);
+                            assert_eq!(self.ty, other.ty);
+                            LoanPath {
+                                kind: LpExtend(Rc::new(x), a, LpInterior(id)),
+                                ty: self.ty,
+                            }
                         } else {
                             x
                         }
                     })
                 } else {
                     base.common(&**base2)
-                },
+                }
+            }
             (&LpExtend(ref base, _, LpDeref(_)), _) => base.common(other),
             (_, &LpExtend(ref other, _, LpDeref(_))) => self.common(&**other),
-            (&LpVar(id), &LpVar(id2)) => if id == id2 { Some(LpVar(id)) } else { None },
-            (&LpUpvar(id), &LpUpvar(id2)) => if id == id2 { Some(LpUpvar(id)) } else { None },
+            (&LpVar(id), &LpVar(id2)) => {
+                if id == id2 {
+                    assert_eq!(self.ty, other.ty);
+                    Some(LoanPath { kind: LpVar(id), ty: self.ty })
+                } else {
+                    None
+                }
+            }
+            (&LpUpvar(id), &LpUpvar(id2)) => {
+                if id == id2 {
+                    assert_eq!(self.ty, other.ty);
+                    Some(LoanPath { kind: LpUpvar(id), ty: self.ty })
+                } else {
+                    None
+                }
+            }
             _ => None,
         }
     }
 }
 
-pub fn opt_loan_path(cmt: &mc::cmt) -> Option<Rc<LoanPath>> {
+pub fn opt_loan_path<'tcx>(cmt: &mc::cmt<'tcx>) -> Option<Rc<LoanPath<'tcx>>> {
     //! Computes the `LoanPath` (if any) for a `cmt`.
     //! Note that this logic is somewhat duplicated in
     //! the method `compute()` found in `gather_loans::restrictions`,
     //! which allows it to share common loan path pieces as it
     //! traverses the CMT.
 
+    let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
+
     match cmt.cat {
         mc::cat_rvalue(..) |
         mc::cat_static_item => {
@@ -383,28 +451,31 @@ pub fn opt_loan_path(cmt: &mc::cmt) -> Option<Rc<LoanPath>> {
         }
 
         mc::cat_local(id) => {
-            Some(Rc::new(LpVar(id)))
+            Some(new_lp(LpVar(id)))
         }
 
         mc::cat_upvar(mc::Upvar { id, .. }) => {
-            Some(Rc::new(LpUpvar(id)))
+            Some(new_lp(LpUpvar(id)))
         }
 
         mc::cat_deref(ref cmt_base, _, pk) => {
             opt_loan_path(cmt_base).map(|lp| {
-                Rc::new(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
+                new_lp(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
             })
         }
 
         mc::cat_interior(ref cmt_base, ik) => {
             opt_loan_path(cmt_base).map(|lp| {
-                Rc::new(LpExtend(lp, cmt.mutbl, LpInterior(ik)))
+                new_lp(LpExtend(lp, cmt.mutbl, LpInterior(ik)))
             })
         }
 
-        mc::cat_downcast(ref cmt_base) => {
+        mc::cat_downcast(ref cmt_base, variant_def_id) =>
             opt_loan_path(cmt_base)
-        }
+            .map(|lp| {
+                new_lp(LpDowncast(lp, variant_def_id))
+            }),
+
     }
 }
 
@@ -472,9 +543,9 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
     pub fn report_use_of_moved_value(&self,
                                      use_span: Span,
                                      use_kind: MovedValueUseKind,
-                                     lp: &LoanPath,
+                                     lp: &LoanPath<'tcx>,
                                      the_move: &move_data::Move,
-                                     moved_lp: &LoanPath) {
+                                     moved_lp: &LoanPath<'tcx>) {
         let verb = match use_kind {
             MovedInUse => "use",
             MovedInCapture => "capture",
@@ -623,7 +694,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
 
     pub fn report_reassigned_immutable_variable(&self,
                                                 span: Span,
-                                                lp: &LoanPath,
+                                                lp: &LoanPath<'tcx>,
                                                 assign:
                                                 &move_data::Assignment) {
         self.tcx.sess.span_err(
@@ -854,14 +925,23 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
     }
 
     pub fn append_loan_path_to_string(&self,
-                                   loan_path: &LoanPath,
-                                   out: &mut String) {
-        match *loan_path {
+                                      loan_path: &LoanPath<'tcx>,
+                                      out: &mut String) {
+        match loan_path.kind {
             LpUpvar(ty::UpvarId{ var_id: id, closure_expr_id: _ }) |
             LpVar(id) => {
                 out.push_str(ty::local_var_name_str(self.tcx, id).get());
             }
 
+            LpDowncast(ref lp_base, variant_def_id) => {
+                out.push('(');
+                self.append_loan_path_to_string(&**lp_base, out);
+                out.push_str(DOWNCAST_PRINTED_OPERATOR);
+                out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice());
+                out.push(')');
+            }
+
+
             LpExtend(ref lp_base, _, LpInterior(mc::InteriorField(fname))) => {
                 self.append_autoderefd_loan_path_to_string(&**lp_base, out);
                 match fname {
@@ -889,9 +969,9 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
     }
 
     pub fn append_autoderefd_loan_path_to_string(&self,
-                                              loan_path: &LoanPath,
-                                              out: &mut String) {
-        match *loan_path {
+                                                 loan_path: &LoanPath<'tcx>,
+                                                 out: &mut String) {
+        match loan_path.kind {
             LpExtend(ref lp_base, _, LpDeref(_)) => {
                 // For a path like `(*x).f` or `(*x)[3]`, autoderef
                 // rules would normally allow users to omit the `*x`.
@@ -899,13 +979,21 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
                 self.append_autoderefd_loan_path_to_string(&**lp_base, out)
             }
 
+            LpDowncast(ref lp_base, variant_def_id) => {
+                out.push('(');
+                self.append_autoderefd_loan_path_to_string(&**lp_base, out);
+                out.push(':');
+                out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice());
+                out.push(')');
+            }
+
             LpVar(..) | LpUpvar(..) | LpExtend(_, _, LpInterior(..)) => {
                 self.append_loan_path_to_string(loan_path, out)
             }
         }
     }
 
-    pub fn loan_path_to_string(&self, loan_path: &LoanPath) -> String {
+    pub fn loan_path_to_string(&self, loan_path: &LoanPath<'tcx>) -> String {
         let mut result = String::new();
         self.append_loan_path_to_string(loan_path, &mut result);
         result
@@ -942,8 +1030,8 @@ impl DataFlowOperator for LoanDataFlowOperator {
     }
 }
 
-impl<'tcx> Repr<'tcx> for Loan {
-    fn repr(&self, tcx: &ty::ctxt) -> String {
+impl<'tcx> Repr<'tcx> for Loan<'tcx> {
+    fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String {
         format!("Loan_{}({}, {}, {}-{}, {})",
                  self.index,
                  self.loan_path.repr(tcx),
@@ -954,25 +1042,66 @@ impl<'tcx> Repr<'tcx> for Loan {
     }
 }
 
-impl<'tcx> Repr<'tcx> for LoanPath {
-    fn repr(&self, tcx: &ty::ctxt) -> String {
-        match self {
-            &LpVar(id) => {
+impl<'tcx> Repr<'tcx> for LoanPath<'tcx> {
+    fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String {
+        match self.kind {
+            LpVar(id) => {
                 format!("$({})", tcx.map.node_to_string(id))
             }
 
-            &LpUpvar(ty::UpvarId{ var_id, closure_expr_id }) => {
+            LpUpvar(ty::UpvarId{ var_id, closure_expr_id }) => {
                 let s = tcx.map.node_to_string(var_id);
                 format!("$({} captured by id={})", s, closure_expr_id)
             }
 
-            &LpExtend(ref lp, _, LpDeref(_)) => {
+            LpDowncast(ref lp, variant_def_id) => {
+                let variant_str = if variant_def_id.krate == ast::LOCAL_CRATE {
+                    ty::item_path_str(tcx, variant_def_id)
+                } else {
+                    variant_def_id.repr(tcx)
+                };
+                format!("({}{}{})", lp.repr(tcx), DOWNCAST_PRINTED_OPERATOR, variant_str)
+            }
+
+            LpExtend(ref lp, _, LpDeref(_)) => {
                 format!("{}.*", lp.repr(tcx))
             }
 
-            &LpExtend(ref lp, _, LpInterior(ref interior)) => {
+            LpExtend(ref lp, _, LpInterior(ref interior)) => {
                 format!("{}.{}", lp.repr(tcx), interior.repr(tcx))
             }
         }
     }
 }
+
+impl<'tcx> UserString<'tcx> for LoanPath<'tcx> {
+    fn user_string(&self, tcx: &ty::ctxt<'tcx>) -> String {
+        match self.kind {
+            LpVar(id) => {
+                format!("$({})", tcx.map.node_to_user_string(id))
+            }
+
+            LpUpvar(ty::UpvarId{ var_id, closure_expr_id: _ }) => {
+                let s = tcx.map.node_to_user_string(var_id);
+                format!("$({} captured by closure)", s)
+            }
+
+            LpDowncast(ref lp, variant_def_id) => {
+                let variant_str = if variant_def_id.krate == ast::LOCAL_CRATE {
+                    ty::item_path_str(tcx, variant_def_id)
+                } else {
+                    variant_def_id.repr(tcx)
+                };
+                format!("({}{}{})", lp.user_string(tcx), DOWNCAST_PRINTED_OPERATOR, variant_str)
+            }
+
+            LpExtend(ref lp, _, LpDeref(_)) => {
+                format!("{}.*", lp.user_string(tcx))
+            }
+
+            LpExtend(ref lp, _, LpInterior(ref interior)) => {
+                format!("{}.{}", lp.user_string(tcx), interior.repr(tcx))
+            }
+        }
+    }
+}
diff --git a/src/librustc/middle/borrowck/move_data.rs b/src/librustc/middle/borrowck/move_data.rs
index ab4c7787fe8..dc9516ccc5d 100644
--- a/src/librustc/middle/borrowck/move_data.rs
+++ b/src/librustc/middle/borrowck/move_data.rs
@@ -11,7 +11,7 @@
 /*!
 
 Data structures used for tracking moves. Please see the extensive
-comments in the section "Moves and initialization" and in `doc.rs`.
+comments in the section "Moves and initialization" in `doc.rs`.
 
 */
 
@@ -21,6 +21,8 @@ use std::cell::RefCell;
 use std::rc::Rc;
 use std::uint;
 use middle::borrowck::*;
+use middle::borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend};
+use middle::borrowck::LoanPathElem::{LpInterior};
 use middle::cfg;
 use middle::dataflow::DataFlowContext;
 use middle::dataflow::BitwiseOperator;
@@ -34,12 +36,15 @@ use syntax::codemap::Span;
 use util::nodemap::{FnvHashMap, NodeSet};
 use util::ppaux::Repr;
 
-pub struct MoveData {
+#[path="fragments.rs"]
+pub mod fragments;
+
+pub struct MoveData<'tcx> {
     /// Move paths. See section "Move paths" in `doc.rs`.
-    pub paths: RefCell<Vec<MovePath>>,
+    pub paths: RefCell<Vec<MovePath<'tcx>>>,
 
     /// Cache of loan path to move path index, for easy lookup.
-    pub path_map: RefCell<FnvHashMap<Rc<LoanPath>, MovePathIndex>>,
+    pub path_map: RefCell<FnvHashMap<Rc<LoanPath<'tcx>>, MovePathIndex>>,
 
     /// Each move or uninitialized variable gets an entry here.
     pub moves: RefCell<Vec<Move>>,
@@ -54,12 +59,19 @@ pub struct MoveData {
     /// kill move bits.
     pub path_assignments: RefCell<Vec<Assignment>>,
 
+    /// Enum variant matched within a pattern on some match arm, like
+    /// `SomeStruct{ f: Variant1(x, y) } => ...`
+    pub variant_matches: RefCell<Vec<VariantMatch>>,
+
     /// Assignments to a variable or path, like `x = foo`, but not `x += foo`.
     pub assignee_ids: RefCell<NodeSet>,
+
+    /// Path-fragments from moves in to or out of parts of structured data.
+    pub fragments: RefCell<fragments::FragmentSets>,
 }
 
 pub struct FlowedMoveData<'a, 'tcx: 'a> {
-    pub move_data: MoveData,
+    pub move_data: MoveData<'tcx>,
 
     pub dfcx_moves: MoveDataFlow<'a, 'tcx>,
 
@@ -70,7 +82,7 @@ pub struct FlowedMoveData<'a, 'tcx: 'a> {
 }
 
 /// Index into `MoveData.paths`, used like a pointer
-#[deriving(PartialEq, Show)]
+#[deriving(PartialEq, Eq, PartialOrd, Ord, Show)]
 pub struct MovePathIndex(uint);
 
 impl MovePathIndex {
@@ -103,9 +115,9 @@ impl MoveIndex {
 static InvalidMoveIndex: MoveIndex =
     MoveIndex(uint::MAX);
 
-pub struct MovePath {
+pub struct MovePath<'tcx> {
     /// Loan path corresponding to this move path
-    pub loan_path: Rc<LoanPath>,
+    pub loan_path: Rc<LoanPath<'tcx>>,
 
     /// Parent pointer, `InvalidMovePathIndex` if root
     pub parent: MovePathIndex,
@@ -155,6 +167,20 @@ pub struct Assignment {
     pub span: Span,
 }
 
+pub struct VariantMatch {
+    /// downcast to the variant.
+    pub path: MovePathIndex,
+
+    /// path being downcast to the variant.
+    pub base_path: MovePathIndex,
+
+    /// id where variant's pattern occurs
+    pub id: ast::NodeId,
+
+    /// says if variant established by move (and why), by copy, or by borrow.
+    pub mode: euv::MatchMode
+}
+
 #[deriving(Clone)]
 pub struct MoveDataFlowOperator;
 
@@ -166,7 +192,7 @@ pub struct AssignDataFlowOperator;
 pub type AssignDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, AssignDataFlowOperator>;
 
 fn loan_path_is_precise(loan_path: &LoanPath) -> bool {
-    match *loan_path {
+    match loan_path.kind {
         LpVar(_) | LpUpvar(_) => {
             true
         }
@@ -175,25 +201,59 @@ fn loan_path_is_precise(loan_path: &LoanPath) -> bool {
             // location, as there is no accurate tracking of the indices.
             false
         }
+        LpDowncast(ref lp_base, _) |
         LpExtend(ref lp_base, _, _) => {
             loan_path_is_precise(&**lp_base)
         }
     }
 }
 
-impl MoveData {
-    pub fn new() -> MoveData {
+impl Move {
+    pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
+        format!("Move{} path: {}, id: {}, kind: {} {}",
+                "{",
+                move_data.path_loan_path(self.path).repr(tcx),
+                self.id,
+                self.kind,
+                "}")
+    }
+}
+
+impl Assignment {
+    pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
+        format!("Assignment{} path: {}, id: {} {}",
+                "{",
+                move_data.path_loan_path(self.path).repr(tcx),
+                self.id,
+                "}")
+    }
+}
+
+impl VariantMatch {
+    pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
+        format!("VariantMatch{} path: {}, id: {} {}",
+                "{",
+                move_data.path_loan_path(self.path).repr(tcx),
+                self.id,
+                "}")
+    }
+}
+
+impl<'tcx> MoveData<'tcx> {
+    pub fn new() -> MoveData<'tcx> {
         MoveData {
             paths: RefCell::new(Vec::new()),
             path_map: RefCell::new(FnvHashMap::new()),
             moves: RefCell::new(Vec::new()),
             path_assignments: RefCell::new(Vec::new()),
             var_assignments: RefCell::new(Vec::new()),
+            variant_matches: RefCell::new(Vec::new()),
             assignee_ids: RefCell::new(NodeSet::new()),
+            fragments: RefCell::new(fragments::FragmentSets::new()),
         }
     }
 
-    pub fn path_loan_path(&self, index: MovePathIndex) -> Rc<LoanPath> {
+    pub fn path_loan_path(&self, index: MovePathIndex) -> Rc<LoanPath<'tcx>> {
         (*self.paths.borrow())[index.get()].loan_path.clone()
     }
 
@@ -205,6 +265,8 @@ impl MoveData {
         (*self.paths.borrow())[index.get()].first_move
     }
 
+    /// Returns the index of first child, or `InvalidMovePathIndex` if
+    /// `index` is leaf.
     fn path_first_child(&self, index: MovePathIndex) -> MovePathIndex {
         (*self.paths.borrow())[index.get()].first_child
     }
@@ -236,8 +298,8 @@ impl MoveData {
     }
 
     pub fn move_path(&self,
-                     tcx: &ty::ctxt,
-                     lp: Rc<LoanPath>) -> MovePathIndex {
+                     tcx: &ty::ctxt<'tcx>,
+                     lp: Rc<LoanPath<'tcx>>) -> MovePathIndex {
         /*!
          * Returns the existing move path index for `lp`, if any,
          * and otherwise adds a new index for `lp` and any of its
@@ -251,7 +313,7 @@ impl MoveData {
             None => {}
         }
 
-        let index = match *lp {
+        let index = match lp.kind {
             LpVar(..) | LpUpvar(..) => {
                 let index = MovePathIndex(self.paths.borrow().len());
 
@@ -266,6 +328,7 @@ impl MoveData {
                 index
             }
 
+            LpDowncast(ref base, _) |
             LpExtend(ref base, _, _) => {
                 let parent_index = self.move_path(tcx, base.clone());
 
@@ -295,19 +358,19 @@ impl MoveData {
         return index;
     }
 
-    fn existing_move_path(&self, lp: &Rc<LoanPath>)
+    fn existing_move_path(&self, lp: &Rc<LoanPath<'tcx>>)
                           -> Option<MovePathIndex> {
         self.path_map.borrow().get(lp).cloned()
     }
 
-    fn existing_base_paths(&self, lp: &Rc<LoanPath>)
+    fn existing_base_paths(&self, lp: &Rc<LoanPath<'tcx>>)
                            -> Vec<MovePathIndex> {
         let mut result = vec!();
         self.add_existing_base_paths(lp, &mut result);
         result
     }
 
-    fn add_existing_base_paths(&self, lp: &Rc<LoanPath>,
+    fn add_existing_base_paths(&self, lp: &Rc<LoanPath<'tcx>>,
                                result: &mut Vec<MovePathIndex>) {
         /*!
          * Adds any existing move path indices for `lp` and any base
@@ -322,8 +385,9 @@ impl MoveData {
                 });
             }
             None => {
-                match **lp {
+                match lp.kind {
                     LpVar(..) | LpUpvar(..) => { }
+                    LpDowncast(ref b, _) |
                     LpExtend(ref b, _, _) => {
                         self.add_existing_base_paths(b, result);
                     }
@@ -334,8 +398,8 @@ impl MoveData {
     }
 
     pub fn add_move(&self,
-                    tcx: &ty::ctxt,
-                    lp: Rc<LoanPath>,
+                    tcx: &ty::ctxt<'tcx>,
+                    lp: Rc<LoanPath<'tcx>>,
                     id: ast::NodeId,
                     kind: MoveKind) {
         /*!
@@ -348,9 +412,11 @@ impl MoveData {
                id,
                kind);
 
-        let path_index = self.move_path(tcx, lp);
+        let path_index = self.move_path(tcx, lp.clone());
         let move_index = MoveIndex(self.moves.borrow().len());
 
+        self.fragments.borrow_mut().add_move(path_index);
+
         let next_move = self.path_first_move(path_index);
         self.set_path_first_move(path_index, move_index);
 
@@ -363,8 +429,8 @@ impl MoveData {
     }
 
     pub fn add_assignment(&self,
-                          tcx: &ty::ctxt,
-                          lp: Rc<LoanPath>,
+                          tcx: &ty::ctxt<'tcx>,
+                          lp: Rc<LoanPath<'tcx>>,
                           assign_id: ast::NodeId,
                           span: Span,
                           assignee_id: ast::NodeId,
@@ -379,6 +445,8 @@ impl MoveData {
 
         let path_index = self.move_path(tcx, lp.clone());
 
+        self.fragments.borrow_mut().add_assignment(path_index);
+
         match mode {
             euv::Init | euv::JustWrite => {
                 self.assignee_ids.borrow_mut().insert(assignee_id);
@@ -405,8 +473,42 @@ impl MoveData {
         }
     }
 
+    pub fn add_variant_match(&self,
+                             tcx: &ty::ctxt<'tcx>,
+                             lp: Rc<LoanPath<'tcx>>,
+                             pattern_id: ast::NodeId,
+                             base_lp: Rc<LoanPath<'tcx>>,
+                             mode: euv::MatchMode) {
+        /*!
+         * Adds a new record for a match of `base_lp`, downcast to
+         * variant `lp`, that occurs at location `pattern_id`.  (One
+         * should be able to recover the span info from the
+         * `pattern_id` and the ast_map, I think.)
+         */
+        debug!("add_variant_match(lp={}, pattern_id={})",
+               lp.repr(tcx), pattern_id);
+
+        let path_index = self.move_path(tcx, lp.clone());
+        let base_path_index = self.move_path(tcx, base_lp.clone());
+
+        self.fragments.borrow_mut().add_assignment(path_index);
+
+        let variant_match = VariantMatch {
+            path: path_index,
+            base_path: base_path_index,
+            id: pattern_id,
+            mode: mode,
+        };
+
+        self.variant_matches.borrow_mut().push(variant_match);
+    }
+
+    fn fixup_fragment_sets(&self, tcx: &ty::ctxt<'tcx>) {
+        fragments::fixup_fragment_sets(self, tcx)
+    }
+
     fn add_gen_kills(&self,
-                     tcx: &ty::ctxt,
+                     tcx: &ty::ctxt<'tcx>,
                      dfcx_moves: &mut MoveDataFlow,
                      dfcx_assign: &mut AssignDataFlow) {
         /*!
@@ -430,20 +532,15 @@ impl MoveData {
             self.kill_moves(assignment.path, assignment.id, dfcx_moves);
         }
 
-        // Kill all moves related to a variable `x` when it goes out
-        // of scope:
+        // Kill all moves related to a variable `x` when
+        // it goes out of scope:
         for path in self.paths.borrow().iter() {
-            match *path.loan_path {
-                LpVar(id) => {
-                    let kill_scope = tcx.region_maps.var_scope(id);
-                    let path = (*self.path_map.borrow())[path.loan_path];
+            match path.loan_path.kind {
+                LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
+                    let kill_scope = path.loan_path.kill_scope(tcx);
+                    let path = self.path_map.borrow()[path.loan_path];
                     self.kill_moves(path, kill_scope.node_id(), dfcx_moves);
                 }
-                LpUpvar(ty::UpvarId { var_id: _, closure_expr_id }) => {
-                    let kill_id = closure_to_block(closure_expr_id, tcx);
-                    let path = (*self.path_map.borrow())[path.loan_path];
-                    self.kill_moves(path, kill_id, dfcx_moves);
-                }
                 LpExtend(..) => {}
             }
         }
@@ -451,15 +548,12 @@ impl MoveData {
         // Kill all assignments when the variable goes out of scope:
         for (assignment_index, assignment) in
                 self.var_assignments.borrow().iter().enumerate() {
-            match *self.path_loan_path(assignment.path) {
-                LpVar(id) => {
-                    let kill_scope = tcx.region_maps.var_scope(id);
+            let lp = self.path_loan_path(assignment.path);
+            match lp.kind {
+                LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
+                    let kill_scope = lp.kill_scope(tcx);
                     dfcx_assign.add_kill(kill_scope.node_id(), assignment_index);
                 }
-                LpUpvar(ty::UpvarId { var_id: _, closure_expr_id }) => {
-                    let kill_id = closure_to_block(closure_expr_id, tcx);
-                    dfcx_assign.add_kill(kill_id, assignment_index);
-                }
                 LpExtend(..) => {
                     tcx.sess.bug("var assignment for non var path");
                 }
@@ -536,7 +630,7 @@ impl MoveData {
 }
 
 impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> {
-    pub fn new(move_data: MoveData,
+    pub fn new(move_data: MoveData<'tcx>,
                tcx: &'a ty::ctxt<'tcx>,
                cfg: &cfg::CFG,
                id_range: ast_util::IdRange,
@@ -559,9 +653,16 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> {
                                  AssignDataFlowOperator,
                                  id_range,
                                  move_data.var_assignments.borrow().len());
-        move_data.add_gen_kills(tcx, &mut dfcx_moves, &mut dfcx_assign);
+
+        move_data.fixup_fragment_sets(tcx);
+
+        move_data.add_gen_kills(tcx,
+                                &mut dfcx_moves,
+                                &mut dfcx_assign);
+
         dfcx_moves.add_kills_from_flow_exits(cfg);
         dfcx_assign.add_kills_from_flow_exits(cfg);
+
         dfcx_moves.propagate(cfg, body);
         dfcx_assign.propagate(cfg, body);
 
@@ -574,7 +675,7 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> {
 
     pub fn kind_of_move_of_path(&self,
                                 id: ast::NodeId,
-                                loan_path: &Rc<LoanPath>)
+                                loan_path: &Rc<LoanPath<'tcx>>)
                                 -> Option<MoveKind> {
         //! Returns the kind of a move of `loan_path` by `id`, if one exists.
 
@@ -596,8 +697,8 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> {
 
     pub fn each_move_of(&self,
                         id: ast::NodeId,
-                        loan_path: &Rc<LoanPath>,
-                        f: |&Move, &LoanPath| -> bool)
+                        loan_path: &Rc<LoanPath<'tcx>>,
+                        f: |&Move, &LoanPath<'tcx>| -> bool)
                         -> bool {
         /*!
          * Iterates through each move of `loan_path` (or some base path
@@ -656,7 +757,7 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> {
 
     pub fn each_assignment_of(&self,
                               id: ast::NodeId,
-                              loan_path: &Rc<LoanPath>,
+                              loan_path: &Rc<LoanPath<'tcx>>,
                               f: |&Assignment| -> bool)
                               -> bool {
         /*!
diff --git a/src/librustc/middle/check_match.rs b/src/librustc/middle/check_match.rs
index 099ac34d2f2..806fea3b54f 100644
--- a/src/librustc/middle/check_match.rs
+++ b/src/librustc/middle/check_match.rs
@@ -18,6 +18,7 @@ use middle::def::*;
 use middle::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Init};
 use middle::expr_use_visitor::{JustWrite, LoanCause, MutateMode};
 use middle::expr_use_visitor::{WriteAndRead};
+use middle::expr_use_visitor as euv;
 use middle::mem_categorization::cmt;
 use middle::pat_util::*;
 use middle::ty::*;
@@ -1024,6 +1025,7 @@ struct MutationChecker<'a, 'tcx: 'a> {
 }
 
 impl<'a, 'tcx> Delegate<'tcx> for MutationChecker<'a, 'tcx> {
+    fn matched_pat(&mut self, _: &Pat, _: cmt, _: euv::MatchMode) {}
     fn consume(&mut self, _: NodeId, _: Span, _: cmt, _: ConsumeMode) {}
     fn consume_pat(&mut self, _: &Pat, _: cmt, _: ConsumeMode) {}
     fn borrow(&mut self,
diff --git a/src/librustc/middle/check_rvalues.rs b/src/librustc/middle/check_rvalues.rs
index dbba9288cbb..dae76ba125e 100644
--- a/src/librustc/middle/check_rvalues.rs
+++ b/src/librustc/middle/check_rvalues.rs
@@ -59,6 +59,11 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for RvalueContext<'a, 'tcx> {
         }
     }
 
+    fn matched_pat(&mut self,
+                   _matched_pat: &ast::Pat,
+                   _cmt: mc::cmt,
+                   _mode: euv::MatchMode) {}
+
     fn consume_pat(&mut self,
                    _consume_pat: &ast::Pat,
                    _cmt: mc::cmt,
diff --git a/src/librustc/middle/check_static.rs b/src/librustc/middle/check_static.rs
index afba72cea99..d3c7ccf65dd 100644
--- a/src/librustc/middle/check_static.rs
+++ b/src/librustc/middle/check_static.rs
@@ -270,7 +270,7 @@ impl<'tcx> euv::Delegate<'tcx> for GlobalChecker {
                     break
                 }
                 mc::cat_deref(ref cmt, _, _) |
-                mc::cat_downcast(ref cmt) |
+                mc::cat_downcast(ref cmt, _) |
                 mc::cat_interior(ref cmt, _) => cur = cmt,
 
                 mc::cat_rvalue(..) |
@@ -325,6 +325,12 @@ impl<'tcx> euv::Delegate<'tcx> for GlobalChecker {
               _assignment_span: Span,
               _assignee_cmt: mc::cmt,
               _mode: euv::MutateMode) {}
+
+    fn matched_pat(&mut self,
+                   _: &ast::Pat,
+                   _: mc::cmt,
+                   _: euv::MatchMode) {}
+
     fn consume_pat(&mut self,
                    _consume_pat: &ast::Pat,
                    _cmt: mc::cmt,
diff --git a/src/librustc/middle/expr_use_visitor.rs b/src/librustc/middle/expr_use_visitor.rs
index fa0f59f6860..656feb51a1d 100644
--- a/src/librustc/middle/expr_use_visitor.rs
+++ b/src/librustc/middle/expr_use_visitor.rs
@@ -18,6 +18,8 @@ pub use self::MutateMode::*;
 pub use self::LoanCause::*;
 pub use self::ConsumeMode::*;
 pub use self::MoveReason::*;
+pub use self::MatchMode::*;
+use self::TrackMatchMode::*;
 use self::OverloadedCallType::*;
 
 use middle::{def, region, pat_util};
@@ -48,6 +50,23 @@ pub trait Delegate<'tcx> {
                cmt: mc::cmt<'tcx>,
                mode: ConsumeMode);
 
+    // The value found at `cmt` has been determined to match the
+    // pattern binding `matched_pat`, and its subparts are being
+    // copied or moved depending on `mode`.  Note that `matched_pat`
+    // is called on all variant/structs in the pattern (i.e., the
+    // interior nodes of the pattern's tree structure) while
+    // consume_pat is called on the binding identifiers in the pattern
+    // (which are leaves of the pattern's tree structure).
+    //
+    // Note that variants/structs and identifiers are disjoint; thus
+    // `matched_pat` and `consume_pat` are never both called on the
+    // same input pattern structure (though of `consume_pat` can be
+    // called on a subpart of an input passed to `matched_pat).
+    fn matched_pat(&mut self,
+                   matched_pat: &ast::Pat,
+                   cmt: mc::cmt<'tcx>,
+                   mode: MatchMode);
+
     // The value found at `cmt` is either copied or moved via the
     // pattern binding `consume_pat`, depending on mode.
     fn consume_pat(&mut self,
@@ -104,6 +123,79 @@ pub enum MoveReason {
 }
 
 #[deriving(PartialEq,Show)]
+pub enum MatchMode {
+    NonBindingMatch,
+    BorrowingMatch,
+    CopyingMatch,
+    MovingMatch,
+}
+
+#[deriving(PartialEq,Show)]
+enum TrackMatchMode<T> {
+    Unknown, Definite(MatchMode), Conflicting,
+}
+
+impl<T> TrackMatchMode<T> {
+    // Builds up the whole match mode for a pattern from its constituent
+    // parts.  The lattice looks like this:
+    //
+    //          Conflicting
+    //            /     \
+    //           /       \
+    //      Borrowing   Moving
+    //           \       /
+    //            \     /
+    //            Copying
+    //               |
+    //          NonBinding
+    //               |
+    //            Unknown
+    //
+    // examples:
+    //
+    // * `(_, some_int)` pattern is Copying, since
+    //   NonBinding + Copying => Copying
+    //
+    // * `(some_int, some_box)` pattern is Moving, since
+    //   Copying + Moving => Moving
+    //
+    // * `(ref x, some_box)` pattern is Conflicting, since
+    //   Borrowing + Moving => Conflicting
+    //
+    // Note that the `Unknown` and `Conflicting` states are
+    // represented separately from the other more interesting
+    // `Definite` states, which simplifies logic here somewhat.
+    fn lub(&mut self, mode: MatchMode) {
+        *self = match (*self, mode) {
+            // Note that clause order below is very significant.
+            (Unknown, new) => Definite(new),
+            (Definite(old), new) if old == new => Definite(old),
+
+            (Definite(old), NonBindingMatch) => Definite(old),
+            (Definite(NonBindingMatch), new) => Definite(new),
+
+            (Definite(old), CopyingMatch) => Definite(old),
+            (Definite(CopyingMatch), new) => Definite(new),
+
+            (Definite(_), _) => Conflicting,
+            (Conflicting, _) => *self,
+        };
+    }
+
+    fn match_mode(&self) -> MatchMode {
+        match *self {
+            Unknown => NonBindingMatch,
+            Definite(mode) => mode,
+            Conflicting => {
+                // Conservatively return MovingMatch to let the
+                // compiler continue to make progress.
+                MovingMatch
+            }
+        }
+    }
+}
+
+#[deriving(PartialEq,Show)]
 pub enum MutateMode {
     Init,
     JustWrite,    // x = y
@@ -251,7 +343,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> {
                 ty::ReScope(fn_body_scope), // Args live only as long as the fn body.
                 arg_ty);
 
-            self.walk_pat(arg_cmt, &*arg.pat);
+            self.walk_irrefutable_pat(arg_cmt, &*arg.pat);
         }
     }
 
@@ -390,7 +482,9 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> {
 
                 // treatment of the discriminant is handled while walking the arms.
                 for arm in arms.iter() {
-                    self.walk_arm(discr_cmt.clone(), arm);
+                    let mode = self.arm_move_mode(discr_cmt.clone(), arm);
+                    let mode = mode.match_mode();
+                    self.walk_arm(discr_cmt.clone(), arm, mode);
                 }
             }
 
@@ -448,7 +542,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> {
                                                  pat.span,
                                                  ty::ReScope(blk_scope),
                                                  pattern_type);
-                self.walk_pat(pat_cmt, &**pat);
+                self.walk_irrefutable_pat(pat_cmt, &**pat);
 
                 self.walk_block(&**blk);
             }
@@ -617,7 +711,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> {
                 // `walk_pat`:
                 self.walk_expr(&**expr);
                 let init_cmt = return_if_err!(self.mc.cat_expr(&**expr));
-                self.walk_pat(init_cmt, &*local.pat);
+                self.walk_irrefutable_pat(init_cmt, &*local.pat);
             }
         }
     }
@@ -824,9 +918,17 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> {
         return true;
     }
 
-    fn walk_arm(&mut self, discr_cmt: mc::cmt<'tcx>, arm: &ast::Arm) {
+    fn arm_move_mode(&mut self, discr_cmt: mc::cmt<'tcx>, arm: &ast::Arm) -> TrackMatchMode<Span> {
+        let mut mode = Unknown;
         for pat in arm.pats.iter() {
-            self.walk_pat(discr_cmt.clone(), &**pat);
+            self.determine_pat_move_mode(discr_cmt.clone(), &**pat, &mut mode);
+        }
+        mode
+    }
+
+    fn walk_arm(&mut self, discr_cmt: mc::cmt<'tcx>, arm: &ast::Arm, mode: MatchMode) {
+        for pat in arm.pats.iter() {
+            self.walk_pat(discr_cmt.clone(), &**pat, mode);
         }
 
         for guard in arm.guard.iter() {
@@ -836,21 +938,71 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> {
         self.consume_expr(&*arm.body);
     }
 
-    fn walk_pat(&mut self, cmt_discr: mc::cmt<'tcx>, pat: &ast::Pat) {
+    /// Walks an pat that occurs in isolation (i.e. top-level of fn
+    /// arg or let binding.  *Not* a match arm or nested pat.)
+    fn walk_irrefutable_pat(&mut self, cmt_discr: mc::cmt<'tcx>, pat: &ast::Pat) {
+        let mut mode = Unknown;
+        self.determine_pat_move_mode(cmt_discr.clone(), pat, &mut mode);
+        let mode = mode.match_mode();
+        self.walk_pat(cmt_discr, pat, mode);
+    }
+
+    /// Identifies any bindings within `pat` and accumulates within
+    /// `mode` whether the overall pattern/match structure is a move,
+    /// copy, or borrow.
+    fn determine_pat_move_mode(&mut self,
+                               cmt_discr: mc::cmt<'tcx>,
+                               pat: &ast::Pat,
+                               mode: &mut TrackMatchMode<Span>) {
+        debug!("determine_pat_move_mode cmt_discr={} pat={}", cmt_discr.repr(self.tcx()),
+               pat.repr(self.tcx()));
+        return_if_err!(self.mc.cat_pattern(cmt_discr, pat, |_mc, cmt_pat, pat| {
+            let tcx = self.typer.tcx();
+            let def_map = &self.typer.tcx().def_map;
+            if pat_util::pat_is_binding(def_map, pat) {
+                match pat.node {
+                    ast::PatIdent(ast::BindByRef(_), _, _) =>
+                        mode.lub(BorrowingMatch),
+                    ast::PatIdent(ast::BindByValue(_), _, _) => {
+                        match copy_or_move(tcx, cmt_pat.ty, PatBindingMove) {
+                            Copy => mode.lub(CopyingMatch),
+                            Move(_) => mode.lub(MovingMatch),
+                        }
+                    }
+                    _ => {
+                        tcx.sess.span_bug(
+                            pat.span,
+                            "binding pattern not an identifier");
+                    }
+                }
+            }
+        }));
+    }
+
+    /// The core driver for walking a pattern; `match_mode` must be
+    /// established up front, e.g. via `determine_pat_move_mode` (see
+    /// also `walk_irrefutable_pat` for patterns that stand alone).
+    fn walk_pat(&mut self,
+                cmt_discr: mc::cmt<'tcx>,
+                pat: &ast::Pat,
+                match_mode: MatchMode) {
         debug!("walk_pat cmt_discr={} pat={}", cmt_discr.repr(self.tcx()),
                pat.repr(self.tcx()));
+
         let mc = &self.mc;
         let typer = self.typer;
         let tcx = typer.tcx();
         let def_map = &self.typer.tcx().def_map;
         let delegate = &mut self.delegate;
-        return_if_err!(mc.cat_pattern(cmt_discr, &*pat, |mc, cmt_pat, pat| {
+
+        return_if_err!(mc.cat_pattern(cmt_discr.clone(), pat, |mc, cmt_pat, pat| {
             if pat_util::pat_is_binding(def_map, pat) {
                 let tcx = typer.tcx();
 
-                debug!("binding cmt_pat={} pat={}",
+                debug!("binding cmt_pat={} pat={} match_mode={}",
                        cmt_pat.repr(tcx),
-                       pat.repr(tcx));
+                       pat.repr(tcx),
+                       match_mode);
 
                 // pat_ty: the type of the binding being produced.
                 let pat_ty = return_if_err!(typer.node_ty(pat.id));
@@ -933,6 +1085,93 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> {
                 }
             }
         }));
+
+        // Do a second pass over the pattern, calling `matched_pat` on
+        // the interior nodes (enum variants and structs), as opposed
+        // to the above loop's visit of than the bindings that form
+        // the leaves of the pattern tree structure.
+        return_if_err!(mc.cat_pattern(cmt_discr, pat, |mc, cmt_pat, pat| {
+            let def_map = def_map.borrow();
+            let tcx = typer.tcx();
+
+            match pat.node {
+                ast::PatEnum(_, _) | ast::PatIdent(_, _, None) | ast::PatStruct(..) => {
+                    match def_map.get(&pat.id) {
+                        None => {
+                            // no definition found: pat is not a
+                            // struct or enum pattern.
+                        }
+
+                        Some(&def::DefVariant(enum_did, variant_did, _is_struct)) => {
+                            let downcast_cmt =
+                                if ty::enum_is_univariant(tcx, enum_did) {
+                                    cmt_pat
+                                } else {
+                                    let cmt_pat_ty = cmt_pat.ty;
+                                    mc.cat_downcast(pat, cmt_pat, cmt_pat_ty, variant_did)
+                                };
+
+                            debug!("variant downcast_cmt={} pat={}",
+                                   downcast_cmt.repr(tcx),
+                                   pat.repr(tcx));
+
+                            delegate.matched_pat(pat, downcast_cmt, match_mode);
+                        }
+
+                        Some(&def::DefStruct(..)) | Some(&def::DefTy(_, false)) => {
+                            // A struct (in either the value or type
+                            // namespace; we encounter the former on
+                            // e.g. patterns for unit structs).
+
+                            debug!("struct cmt_pat={} pat={}",
+                                   cmt_pat.repr(tcx),
+                                   pat.repr(tcx));
+
+                            delegate.matched_pat(pat, cmt_pat, match_mode);
+                        }
+
+                        Some(&def::DefConst(..)) |
+                        Some(&def::DefLocal(..)) => {
+                            // This is a leaf (i.e. identifier binding
+                            // or constant value to match); thus no
+                            // `matched_pat` call.
+                        }
+
+                        Some(def @ &def::DefTy(_, true)) => {
+                            // An enum's type -- should never be in a
+                            // pattern.
+
+                            let msg = format!("Pattern has unexpected type: {}", def);
+                            tcx.sess.span_bug(pat.span, msg.as_slice())
+                        }
+
+                        Some(def) => {
+                            // Remaining cases are e.g. DefFn, to
+                            // which identifiers within patterns
+                            // should not resolve.
+
+                            let msg = format!("Pattern has unexpected def: {}", def);
+                            tcx.sess.span_bug(pat.span, msg.as_slice())
+                        }
+                    }
+                }
+
+                ast::PatIdent(_, _, Some(_)) => {
+                    // Do nothing; this is a binding (not a enum
+                    // variant or struct), and the cat_pattern call
+                    // will visit the substructure recursively.
+                }
+
+                ast::PatWild(_) | ast::PatTup(..) | ast::PatBox(..) |
+                ast::PatRegion(..) | ast::PatLit(..) | ast::PatRange(..) |
+                ast::PatVec(..) | ast::PatMac(..) => {
+                    // Similarly, each of these cases does not
+                    // correspond to a enum variant or struct, so we
+                    // do not do any `matched_pat` calls for these
+                    // cases either.
+                }
+            }
+        }));
     }
 
     fn walk_captures(&mut self, closure_expr: &ast::Expr) {
diff --git a/src/librustc/middle/mem_categorization.rs b/src/librustc/middle/mem_categorization.rs
index e9986e47e4a..046ab162cfc 100644
--- a/src/librustc/middle/mem_categorization.rs
+++ b/src/librustc/middle/mem_categorization.rs
@@ -98,7 +98,7 @@ pub enum categorization<'tcx> {
     cat_local(ast::NodeId),                    // local variable
     cat_deref(cmt<'tcx>, uint, PointerKind),   // deref of a ptr
     cat_interior(cmt<'tcx>, InteriorKind),     // something interior: field, tuple, etc
-    cat_downcast(cmt<'tcx>),                   // selects a particular enum variant (*1)
+    cat_downcast(cmt<'tcx>, ast::DefId),       // selects a particular enum variant (*1)
 
     // (*1) downcast is only required if the enum has more than one variant
 }
@@ -410,7 +410,28 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
     }
 
     fn pat_ty(&self, pat: &ast::Pat) -> McResult<Ty<'tcx>> {
-        self.typer.node_ty(pat.id)
+        let tcx = self.typer.tcx();
+        let base_ty = self.typer.node_ty(pat.id);
+        // FIXME (Issue #18207): This code detects whether we are
+        // looking at a `ref x`, and if so, figures out what the type
+        // *being borrowed* is.  But ideally we would put in a more
+        // fundamental fix to this conflated use of the node id.
+        let ret_ty = match pat.node {
+            ast::PatIdent(ast::BindByRef(_), _, _) => {
+                // a bind-by-ref means that the base_ty will be the type of the ident itself,
+                // but what we want here is the type of the underlying value being borrowed.
+                // So peel off one-level, turning the &T into T.
+                base_ty.map(|t| {
+                    ty::deref(t, false).unwrap_or_else(|| {
+                        panic!("encountered BindByRef with non &-type");
+                    }).ty
+                })
+            }
+            _ => base_ty,
+        };
+        debug!("pat_ty(pat={}) base_ty={} ret_ty={}",
+               pat.repr(tcx), base_ty.repr(tcx), ret_ty.repr(tcx));
+        ret_ty
     }
 
     pub fn cat_expr(&self, expr: &ast::Expr) -> McResult<cmt<'tcx>> {
@@ -1102,13 +1123,14 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
     pub fn cat_downcast<N:ast_node>(&self,
                                     node: &N,
                                     base_cmt: cmt<'tcx>,
-                                    downcast_ty: Ty<'tcx>)
+                                    downcast_ty: Ty<'tcx>,
+                                    variant_did: ast::DefId)
                                     -> cmt<'tcx> {
         Rc::new(cmt_ {
             id: node.id(),
             span: node.span(),
             mutbl: base_cmt.mutbl.inherit(),
-            cat: cat_downcast(base_cmt),
+            cat: cat_downcast(base_cmt, variant_did),
             ty: downcast_ty,
             note: NoteNone
         })
@@ -1117,7 +1139,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
     pub fn cat_pattern(&self,
                        cmt: cmt<'tcx>,
                        pat: &ast::Pat,
-                       op: |&MemCategorizationContext<TYPER>,
+                       op: |&MemCategorizationContext<'t,TYPER>,
                             cmt<'tcx>,
                             &ast::Pat|)
                        -> McResult<()> {
@@ -1172,6 +1194,21 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
 
         op(self, cmt.clone(), pat);
 
+        let def_map = self.tcx().def_map.borrow();
+        let opt_def = def_map.get(&pat.id);
+
+        // Note: This goes up here (rather than within the PatEnum arm
+        // alone) because struct patterns can refer to struct types or
+        // to struct variants within enums.
+        let cmt = match opt_def {
+            Some(&def::DefVariant(enum_did, variant_did, _))
+                // univariant enums do not need downcasts
+                if !ty::enum_is_univariant(self.tcx(), enum_did) => {
+                    self.cat_downcast(pat, cmt.clone(), cmt.ty, variant_did)
+                }
+            _ => cmt
+        };
+
         match pat.node {
           ast::PatWild(_) => {
             // _
@@ -1181,24 +1218,15 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
             // variant(..)
           }
           ast::PatEnum(_, Some(ref subpats)) => {
-            match self.tcx().def_map.borrow().get(&pat.id) {
-                Some(&def::DefVariant(enum_did, _, _)) => {
+            match opt_def {
+                Some(&def::DefVariant(..)) => {
                     // variant(x, y, z)
-
-                    let downcast_cmt = {
-                        if ty::enum_is_univariant(self.tcx(), enum_did) {
-                            cmt // univariant, no downcast needed
-                        } else {
-                            self.cat_downcast(pat, cmt.clone(), cmt.ty)
-                        }
-                    };
-
                     for (i, subpat) in subpats.iter().enumerate() {
                         let subpat_ty = if_ok!(self.pat_ty(&**subpat)); // see (*2)
 
                         let subcmt =
                             self.cat_imm_interior(
-                                pat, downcast_cmt.clone(), subpat_ty,
+                                pat, cmt.clone(), subpat_ty,
                                 InteriorField(PositionalField(i)));
 
                         if_ok!(self.cat_pattern(subcmt, &**subpat, |x,y,z| op(x,y,z)));
@@ -1356,7 +1384,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
           cat_upvar(ref var) => {
               upvar_to_string(var, true)
           }
-          cat_downcast(ref cmt) => {
+          cat_downcast(ref cmt, _) => {
             self.cmt_to_string(&**cmt)
           }
         }
@@ -1392,7 +1420,7 @@ impl<'tcx> cmt_<'tcx> {
             cat_upvar(..) => {
                 Rc::new((*self).clone())
             }
-            cat_downcast(ref b) |
+            cat_downcast(ref b, _) |
             cat_interior(ref b, _) |
             cat_deref(ref b, _, OwnedPtr) => {
                 b.guarantor()
@@ -1416,7 +1444,7 @@ impl<'tcx> cmt_<'tcx> {
             cat_deref(ref b, _, Implicit(ty::MutBorrow, _)) |
             cat_deref(ref b, _, BorrowedPtr(ty::UniqueImmBorrow, _)) |
             cat_deref(ref b, _, Implicit(ty::UniqueImmBorrow, _)) |
-            cat_downcast(ref b) |
+            cat_downcast(ref b, _) |
             cat_deref(ref b, _, OwnedPtr) |
             cat_interior(ref b, _) => {
                 // Aliasability depends on base cmt
@@ -1500,7 +1528,7 @@ impl<'tcx> Repr<'tcx> for categorization<'tcx> {
             cat_interior(ref cmt, interior) => {
                 format!("{}.{}", cmt.cat.repr(tcx), interior.repr(tcx))
             }
-            cat_downcast(ref cmt) => {
+            cat_downcast(ref cmt, _) => {
                 format!("{}->(enum)", cmt.cat.repr(tcx))
             }
         }
diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs
index 47b296e46cd..98b958749d5 100644
--- a/src/librustc/middle/ty.rs
+++ b/src/librustc/middle/ty.rs
@@ -3442,6 +3442,62 @@ pub fn array_element_ty<'tcx>(ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
     }
 }
 
+/// Returns the type of element at index `i` in tuple or tuple-like type `t`.
+/// For an enum `t`, `variant` is None only if `t` is a univariant enum.
+pub fn positional_element_ty<'tcx>(cx: &ctxt<'tcx>,
+                                   ty: Ty<'tcx>,
+                                   i: uint,
+                                   variant: Option<ast::DefId>) -> Option<Ty<'tcx>> {
+
+    match (&ty.sty, variant) {
+        (&ty_tup(ref v), None) => v.as_slice().get(i).map(|&t| t),
+
+
+        (&ty_struct(def_id, ref substs), None) => lookup_struct_fields(cx, def_id)
+            .as_slice().get(i)
+            .map(|&t|lookup_item_type(cx, t.id).ty.subst(cx, substs)),
+
+        (&ty_enum(def_id, ref substs), Some(variant_def_id)) => {
+            let variant_info = enum_variant_with_id(cx, def_id, variant_def_id);
+            variant_info.args.as_slice().get(i).map(|t|t.subst(cx, substs))
+        }
+
+        (&ty_enum(def_id, ref substs), None) => {
+            assert!(enum_is_univariant(cx, def_id));
+            let enum_variants = enum_variants(cx, def_id);
+            let variant_info = &(*enum_variants)[0];
+            variant_info.args.as_slice().get(i).map(|t|t.subst(cx, substs))
+        }
+
+        _ => None
+    }
+}
+
+/// Returns the type of element at field `n` in struct or struct-like type `t`.
+/// For an enum `t`, `variant` must be some def id.
+pub fn named_element_ty<'tcx>(cx: &ctxt<'tcx>,
+                              ty: Ty<'tcx>,
+                              n: ast::Name,
+                              variant: Option<ast::DefId>) -> Option<Ty<'tcx>> {
+
+    match (&ty.sty, variant) {
+        (&ty_struct(def_id, ref substs), None) => {
+            let r = lookup_struct_fields(cx, def_id);
+            r.iter().find(|f| f.name == n)
+                .map(|&f| lookup_field_type(cx, def_id, f.id, substs))
+        }
+        (&ty_enum(def_id, ref substs), Some(variant_def_id)) => {
+            let variant_info = enum_variant_with_id(cx, def_id, variant_def_id);
+            variant_info.arg_names.as_ref()
+                .expect("must have struct enum variant if accessing a named fields")
+                .iter().zip(variant_info.args.iter())
+                .find(|&(ident, _)| ident.name == n)
+                .map(|(_ident, arg_t)| arg_t.subst(cx, substs))
+        }
+        _ => None
+    }
+}
+
 pub fn node_id_to_trait_ref<'tcx>(cx: &ctxt<'tcx>, id: ast::NodeId)
                                   -> Rc<ty::TraitRef<'tcx>> {
     match cx.trait_refs.borrow().get(&id) {
diff --git a/src/librustc/middle/typeck/check/regionck.rs b/src/librustc/middle/typeck/check/regionck.rs
index 1e92dc573a9..f12b5cdad98 100644
--- a/src/librustc/middle/typeck/check/regionck.rs
+++ b/src/librustc/middle/typeck/check/regionck.rs
@@ -1503,7 +1503,7 @@ fn link_region<'a, 'tcx>(rcx: &Rcx<'a, 'tcx>,
                 }
             }
 
-            mc::cat_downcast(cmt_base) |
+            mc::cat_downcast(cmt_base, _) |
             mc::cat_deref(cmt_base, _, mc::OwnedPtr) |
             mc::cat_interior(cmt_base, _) => {
                 // Borrowing interior or owned data requires the base
@@ -1744,7 +1744,7 @@ fn adjust_upvar_borrow_kind_for_mut<'a, 'tcx>(rcx: &Rcx<'a, 'tcx>,
         match cmt.cat.clone() {
             mc::cat_deref(base, _, mc::OwnedPtr) |
             mc::cat_interior(base, _) |
-            mc::cat_downcast(base) => {
+            mc::cat_downcast(base, _) => {
                 // Interior or owned data is mutable if base is
                 // mutable, so iterate to the base.
                 cmt = base;
@@ -1795,7 +1795,7 @@ fn adjust_upvar_borrow_kind_for_unique<'a, 'tcx>(rcx: &Rcx<'a, 'tcx>, cmt: mc::c
         match cmt.cat.clone() {
             mc::cat_deref(base, _, mc::OwnedPtr) |
             mc::cat_interior(base, _) |
-            mc::cat_downcast(base) => {
+            mc::cat_downcast(base, _) => {
                 // Interior or owned data is unique if base is
                 // unique.
                 cmt = base;
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index e10a1a4342c..82cf8f28e3d 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -208,6 +208,7 @@ debugging_opts!(
         AST_JSON_NOEXPAND,
         LS,
         SAVE_ANALYSIS,
+        PRINT_MOVE_FRAGMENTS,
         FLOWGRAPH_PRINT_LOANS,
         FLOWGRAPH_PRINT_MOVES,
         FLOWGRAPH_PRINT_ASSIGNS,
@@ -246,6 +247,8 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> {
      ("ls", "List the symbols defined by a library crate", LS),
      ("save-analysis", "Write syntax and type analysis information \
                         in addition to normal output", SAVE_ANALYSIS),
+     ("print-move-fragments", "Print out move-fragment data for every fn",
+      PRINT_MOVE_FRAGMENTS),
      ("flowgraph-print-loans", "Include loan analysis data in \
                        --pretty flowgraph output", FLOWGRAPH_PRINT_LOANS),
      ("flowgraph-print-moves", "Include move analysis data in \
diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs
index 72a9f23aa1f..89f6cda64d9 100644
--- a/src/librustc/session/mod.rs
+++ b/src/librustc/session/mod.rs
@@ -90,6 +90,12 @@ impl Session {
     pub fn warn(&self, msg: &str) {
         self.diagnostic().handler().warn(msg)
     }
+    pub fn opt_span_warn(&self, opt_sp: Option<Span>, msg: &str) {
+        match opt_sp {
+            Some(sp) => self.span_warn(sp, msg),
+            None => self.warn(msg),
+        }
+    }
     pub fn span_note(&self, sp: Span, msg: &str) {
         self.diagnostic().span_note(sp, msg)
     }
@@ -108,6 +114,12 @@ impl Session {
     pub fn help(&self, msg: &str) {
         self.diagnostic().handler().note(msg)
     }
+    pub fn opt_span_bug(&self, opt_sp: Option<Span>, msg: &str) -> ! {
+        match opt_sp {
+            Some(sp) => self.span_bug(sp, msg),
+            None => self.bug(msg),
+        }
+    }
     pub fn span_bug(&self, sp: Span, msg: &str) -> ! {
         self.diagnostic().span_bug(sp, msg)
     }
diff --git a/src/librustc_trans/trans/_match.rs b/src/librustc_trans/trans/_match.rs
index d23e53d2e69..381220d587c 100644
--- a/src/librustc_trans/trans/_match.rs
+++ b/src/librustc_trans/trans/_match.rs
@@ -1274,6 +1274,7 @@ struct ReassignmentChecker {
 
 impl<'tcx> euv::Delegate<'tcx> for ReassignmentChecker {
     fn consume(&mut self, _: ast::NodeId, _: Span, _: mc::cmt, _: euv::ConsumeMode) {}
+    fn matched_pat(&mut self, _: &ast::Pat, _: mc::cmt, _: euv::MatchMode) {}
     fn consume_pat(&mut self, _: &ast::Pat, _: mc::cmt, _: euv::ConsumeMode) {}
     fn borrow(&mut self, _: ast::NodeId, _: Span, _: mc::cmt, _: ty::Region,
               _: ty::BorrowKind, _: euv::LoanCause) {}
diff --git a/src/libsyntax/ast_map/mod.rs b/src/libsyntax/ast_map/mod.rs
index 472331bc9e1..8a2202d28d5 100644
--- a/src/libsyntax/ast_map/mod.rs
+++ b/src/libsyntax/ast_map/mod.rs
@@ -551,7 +551,11 @@ impl<'ast> Map<'ast> {
     }
 
     pub fn node_to_string(&self, id: NodeId) -> String {
-        node_id_to_string(self, id)
+        node_id_to_string(self, id, true)
+    }
+
+    pub fn node_to_user_string(&self, id: NodeId) -> String {
+        node_id_to_string(self, id, false)
     }
 }
 
@@ -1028,7 +1032,10 @@ impl<'a> NodePrinter for pprust::State<'a> {
     }
 }
 
-fn node_id_to_string(map: &Map, id: NodeId) -> String {
+fn node_id_to_string(map: &Map, id: NodeId, include_id: bool) -> String {
+    let id_str = format!(" (id={})", id);
+    let id_str = if include_id { id_str.as_slice() } else { "" };
+
     match map.find(id) {
         Some(NodeItem(item)) => {
             let path_str = map.path_to_str_with_ident(id, item.ident);
@@ -1045,30 +1052,30 @@ fn node_id_to_string(map: &Map, id: NodeId) -> String {
                 ItemImpl(..) => "impl",
                 ItemMac(..) => "macro"
             };
-            format!("{} {} (id={})", item_str, path_str, id)
+            format!("{} {}{}", item_str, path_str, id_str)
         }
         Some(NodeForeignItem(item)) => {
             let path_str = map.path_to_str_with_ident(id, item.ident);
-            format!("foreign item {} (id={})", path_str, id)
+            format!("foreign item {}{}", path_str, id_str)
         }
         Some(NodeImplItem(ref ii)) => {
             match **ii {
                 MethodImplItem(ref m) => {
                     match m.node {
                         MethDecl(ident, _, _, _, _, _, _, _) =>
-                            format!("method {} in {} (id={})",
+                            format!("method {} in {}{}",
                                     token::get_ident(ident),
-                                    map.path_to_string(id), id),
+                                    map.path_to_string(id), id_str),
                         MethMac(ref mac) =>
-                            format!("method macro {} (id={})",
-                                    pprust::mac_to_string(mac), id)
+                            format!("method macro {}{}",
+                                    pprust::mac_to_string(mac), id_str)
                     }
                 }
                 TypeImplItem(ref t) => {
-                    format!("typedef {} in {} (id={})",
+                    format!("typedef {} in {}{}",
                             token::get_ident(t.ident),
                             map.path_to_string(id),
-                            id)
+                            id_str)
                 }
             }
         }
@@ -1076,51 +1083,51 @@ fn node_id_to_string(map: &Map, id: NodeId) -> String {
             match **tm {
                 RequiredMethod(_) | ProvidedMethod(_) => {
                     let m = ast_util::trait_item_to_ty_method(&**tm);
-                    format!("method {} in {} (id={})",
+                    format!("method {} in {}{}",
                             token::get_ident(m.ident),
                             map.path_to_string(id),
-                            id)
+                            id_str)
                 }
                 TypeTraitItem(ref t) => {
-                    format!("type item {} in {} (id={})",
+                    format!("type item {} in {}{}",
                             token::get_ident(t.ty_param.ident),
                             map.path_to_string(id),
-                            id)
+                            id_str)
                 }
             }
         }
         Some(NodeVariant(ref variant)) => {
-            format!("variant {} in {} (id={})",
+            format!("variant {} in {}{}",
                     token::get_ident(variant.node.name),
-                    map.path_to_string(id), id)
+                    map.path_to_string(id), id_str)
         }
         Some(NodeExpr(ref expr)) => {
-            format!("expr {} (id={})", pprust::expr_to_string(&**expr), id)
+            format!("expr {}{}", pprust::expr_to_string(&**expr), id_str)
         }
         Some(NodeStmt(ref stmt)) => {
-            format!("stmt {} (id={})", pprust::stmt_to_string(&**stmt), id)
+            format!("stmt {}{}", pprust::stmt_to_string(&**stmt), id_str)
         }
         Some(NodeArg(ref pat)) => {
-            format!("arg {} (id={})", pprust::pat_to_string(&**pat), id)
+            format!("arg {}{}", pprust::pat_to_string(&**pat), id_str)
         }
         Some(NodeLocal(ref pat)) => {
-            format!("local {} (id={})", pprust::pat_to_string(&**pat), id)
+            format!("local {}{}", pprust::pat_to_string(&**pat), id_str)
         }
         Some(NodePat(ref pat)) => {
-            format!("pat {} (id={})", pprust::pat_to_string(&**pat), id)
+            format!("pat {}{}", pprust::pat_to_string(&**pat), id_str)
         }
         Some(NodeBlock(ref block)) => {
-            format!("block {} (id={})", pprust::block_to_string(&**block), id)
+            format!("block {}{}", pprust::block_to_string(&**block), id_str)
         }
         Some(NodeStructCtor(_)) => {
-            format!("struct_ctor {} (id={})", map.path_to_string(id), id)
+            format!("struct_ctor {}{}", map.path_to_string(id), id_str)
         }
         Some(NodeLifetime(ref l)) => {
-            format!("lifetime {} (id={})",
-                    pprust::lifetime_to_string(&**l), id)
+            format!("lifetime {}{}",
+                    pprust::lifetime_to_string(&**l), id_str)
         }
         None => {
-            format!("unknown node (id={})", id)
+            format!("unknown node{}", id_str)
         }
     }
 }
diff --git a/src/test/compile-fail/move-fragments-1.rs b/src/test/compile-fail/move-fragments-1.rs
new file mode 100644
index 00000000000..ccf12cf79e1
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-1.rs
@@ -0,0 +1,58 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(tuple_indexing)]
+
+// Test that we correctly compute the move fragments for a fn.
+//
+// Note that the code below is not actually incorrect; the
+// `rustc_move_fragments` attribute is a hack that uses the error
+// reporting mechanisms as a channel for communicating from the
+// internals of the compiler.
+
+// These are all fairly trivial cases: unused variables or direct
+// drops of substructure.
+
+pub struct D { d: int }
+impl Drop for D { fn drop(&mut self) { } }
+
+#[rustc_move_fragments]
+pub fn test_noop() {
+}
+
+#[rustc_move_fragments]
+pub fn test_take(_x: D) {
+    //~^ ERROR                  assigned_leaf_path: `$(local _x)`
+}
+
+pub struct Pair<X,Y> { x: X, y: Y }
+
+#[rustc_move_fragments]
+pub fn test_take_struct(_p: Pair<D, D>) {
+    //~^ ERROR                  assigned_leaf_path: `$(local _p)`
+}
+
+#[rustc_move_fragments]
+pub fn test_drop_struct_part(p: Pair<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                     moved_leaf_path: `$(local p).x`
+    //~| ERROR                    unmoved_fragment: `$(local p).y`
+    drop(p.x);
+}
+
+#[rustc_move_fragments]
+pub fn test_drop_tuple_part(p: (D, D)) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                     moved_leaf_path: `$(local p).#0`
+    //~| ERROR                    unmoved_fragment: `$(local p).#1`
+    drop(p.0);
+}
+
+pub fn main() { }
diff --git a/src/test/compile-fail/move-fragments-2.rs b/src/test/compile-fail/move-fragments-2.rs
new file mode 100644
index 00000000000..ceb1d5a0f09
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-2.rs
@@ -0,0 +1,85 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that we correctly compute the move fragments for a fn.
+//
+// Note that the code below is not actually incorrect; the
+// `rustc_move_fragments` attribute is a hack that uses the error
+// reporting mechanisms as a channel for communicating from the
+// internals of the compiler.
+
+// These are checking that enums are tracked; note that their output
+// paths include "downcasts" of the path to a particular enum.
+
+use self::Lonely::{Zero, One, Two};
+
+pub struct D { d: int }
+impl Drop for D { fn drop(&mut self) { } }
+
+pub enum Lonely<X,Y> { Zero, One(X), Two(X, Y) }
+
+#[rustc_move_fragments]
+pub fn test_match_partial(p: Lonely<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as Zero)`
+    match p {
+        Zero(..) => {}
+        _ => {}
+    }
+}
+
+#[rustc_move_fragments]
+pub fn test_match_full(p: Lonely<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as Zero)`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as One)`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as Two)`
+    match p {
+        Zero(..) => {}
+        One(..) => {}
+        Two(..) => {}
+    }
+}
+
+#[rustc_move_fragments]
+pub fn test_match_bind_one(p: Lonely<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as Zero)`
+    //~| ERROR                 parent_of_fragments: `($(local p) as One)`
+    //~| ERROR                     moved_leaf_path: `($(local p) as One).#0`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as Two)`
+    //~| ERROR                  assigned_leaf_path: `$(local data)`
+    match p {
+        Zero(..) => {}
+        One(data) => {}
+        Two(..) => {}
+    }
+}
+
+#[rustc_move_fragments]
+pub fn test_match_bind_many(p: Lonely<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as Zero)`
+    //~| ERROR                 parent_of_fragments: `($(local p) as One)`
+    //~| ERROR                     moved_leaf_path: `($(local p) as One).#0`
+    //~| ERROR                  assigned_leaf_path: `$(local data)`
+    //~| ERROR                 parent_of_fragments: `($(local p) as Two)`
+    //~| ERROR                     moved_leaf_path: `($(local p) as Two).#0`
+    //~| ERROR                     moved_leaf_path: `($(local p) as Two).#1`
+    //~| ERROR                  assigned_leaf_path: `$(local left)`
+    //~| ERROR                  assigned_leaf_path: `$(local right)`
+    match p {
+        Zero(..) => {}
+        One(data) => {}
+        Two(left, right) => {}
+    }
+}
+
+pub fn main() { }
diff --git a/src/test/compile-fail/move-fragments-3.rs b/src/test/compile-fail/move-fragments-3.rs
new file mode 100644
index 00000000000..4540b0c5a91
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-3.rs
@@ -0,0 +1,47 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that we correctly compute the move fragments for a fn.
+//
+// Note that the code below is not actually incorrect; the
+// `rustc_move_fragments` attribute is a hack that uses the error
+// reporting mechanisms as a channel for communicating from the
+// internals of the compiler.
+
+// This checks the handling of `_` within variants, especially when mixed
+// with bindings.
+
+use self::Lonely::{Zero, One, Two};
+
+pub struct D { d: int }
+impl Drop for D { fn drop(&mut self) { } }
+
+pub enum Lonely<X,Y> { Zero, One(X), Two(X, Y) }
+
+#[rustc_move_fragments]
+pub fn test_match_bind_and_underscore(p: Lonely<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as Zero)`
+    //~| ERROR                  assigned_leaf_path: `($(local p) as One)`
+    //~| ERROR                 parent_of_fragments: `($(local p) as Two)`
+    //~| ERROR                     moved_leaf_path: `($(local p) as Two).#0`
+    //~| ERROR                    unmoved_fragment: `($(local p) as Two).#1`
+    //~| ERROR                  assigned_leaf_path: `$(local left)`
+
+    match p {
+        Zero(..) => {}
+
+        One(_) => {}       // <-- does not fragment `($(local p) as One)` ...
+
+        Two(left, _) => {} // <-- ... *does* fragment `($(local p) as Two)`.
+    }
+}
+
+pub fn main() { }
diff --git a/src/test/compile-fail/move-fragments-4.rs b/src/test/compile-fail/move-fragments-4.rs
new file mode 100644
index 00000000000..dc43dcb9b0e
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-4.rs
@@ -0,0 +1,39 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that we correctly compute the move fragments for a fn.
+//
+// Note that the code below is not actually incorrect; the
+// `rustc_move_fragments` attribute is a hack that uses the error
+// reporting mechanisms as a channel for communicating from the
+// internals of the compiler.
+
+// This checks that a move of deep structure is properly tracked. (An
+// early draft of the code did not properly traverse up through all of
+// the parents of the leaf fragment.)
+
+pub struct D { d: int }
+impl Drop for D { fn drop(&mut self) { } }
+
+pub struct Pair<X,Y> { x: X, y: Y }
+
+#[rustc_move_fragments]
+pub fn test_move_substructure(pppp: Pair<Pair<Pair<Pair<D,D>, D>, D>, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local pppp)`
+    //~| ERROR                 parent_of_fragments: `$(local pppp).x`
+    //~| ERROR                 parent_of_fragments: `$(local pppp).x.x`
+    //~| ERROR                    unmoved_fragment: `$(local pppp).x.x.x`
+    //~| ERROR                     moved_leaf_path: `$(local pppp).x.x.y`
+    //~| ERROR                    unmoved_fragment: `$(local pppp).x.y`
+    //~| ERROR                    unmoved_fragment: `$(local pppp).y`
+    drop(pppp.x.x.y);
+}
+
+pub fn main() { }
diff --git a/src/test/compile-fail/move-fragments-5.rs b/src/test/compile-fail/move-fragments-5.rs
new file mode 100644
index 00000000000..df069461ab7
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-5.rs
@@ -0,0 +1,92 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that we correctly compute the move fragments for a fn.
+//
+// Note that the code below is not actually incorrect; the
+// `rustc_move_fragments` attribute is a hack that uses the error
+// reporting mechanisms as a channel for communicating from the
+// internals of the compiler.
+
+// This is the first test that checks moving into local variables.
+
+pub struct D { d: int }
+impl Drop for D { fn drop(&mut self) { } }
+
+pub struct Pair<X,Y> { x: X, y: Y }
+
+#[rustc_move_fragments]
+pub fn test_move_field_to_local(p: Pair<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                     moved_leaf_path: `$(local p).x`
+    //~| ERROR                    unmoved_fragment: `$(local p).y`
+    //~| ERROR                  assigned_leaf_path: `$(local _x)`
+    let _x = p.x;
+}
+
+#[rustc_move_fragments]
+pub fn test_move_field_to_local_to_local(p: Pair<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                     moved_leaf_path: `$(local p).x`
+    //~| ERROR                    unmoved_fragment: `$(local p).y`
+    //~| ERROR                  assigned_leaf_path: `$(local _x)`
+    //~| ERROR                     moved_leaf_path: `$(local _x)`
+    //~| ERROR                  assigned_leaf_path: `$(local _y)`
+    let _x = p.x;
+    let _y = _x;
+}
+
+// In the following fn's `test_move_field_to_local_delayed` and
+// `test_uninitialized_local` , the instrumentation reports that `_x`
+// is moved. This is unlike `test_move_field_to_local`, where `_x` is
+// just reported as an assigned_leaf_path. Presumably because this is
+// how we represent that it did not have an initalizing expression at
+// the binding site.
+
+#[rustc_move_fragments]
+pub fn test_uninitialized_local(_p: Pair<D, D>) {
+    //~^ ERROR                  assigned_leaf_path: `$(local _p)`
+    //~| ERROR                     moved_leaf_path: `$(local _x)`
+    let _x: D;
+}
+
+#[rustc_move_fragments]
+pub fn test_move_field_to_local_delayed(p: Pair<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                     moved_leaf_path: `$(local p).x`
+    //~| ERROR                    unmoved_fragment: `$(local p).y`
+    //~| ERROR                  assigned_leaf_path: `$(local _x)`
+    //~| ERROR                     moved_leaf_path: `$(local _x)`
+    let _x;
+    _x = p.x;
+}
+
+#[rustc_move_fragments]
+pub fn test_move_field_mut_to_local(mut p: Pair<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local mut p)`
+    //~| ERROR                     moved_leaf_path: `$(local mut p).x`
+    //~| ERROR                    unmoved_fragment: `$(local mut p).y`
+    //~| ERROR                  assigned_leaf_path: `$(local _x)`
+    let _x = p.x;
+}
+
+#[rustc_move_fragments]
+pub fn test_move_field_to_local_to_local_mut(p: Pair<D, D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                     moved_leaf_path: `$(local p).x`
+    //~| ERROR                    unmoved_fragment: `$(local p).y`
+    //~| ERROR                  assigned_leaf_path: `$(local mut _x)`
+    //~| ERROR                     moved_leaf_path: `$(local mut _x)`
+    //~| ERROR                  assigned_leaf_path: `$(local _y)`
+    let mut _x = p.x;
+    let _y = _x;
+}
+
+pub fn main() {}
diff --git a/src/test/compile-fail/move-fragments-6.rs b/src/test/compile-fail/move-fragments-6.rs
new file mode 100644
index 00000000000..b249d0d7397
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-6.rs
@@ -0,0 +1,59 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that we correctly compute the move fragments for a fn.
+//
+// Note that the code below is not actually incorrect; the
+// `rustc_move_fragments` attribute is a hack that uses the error
+// reporting mechanisms as a channel for communicating from the
+// internals of the compiler.
+
+// Test that moving into a field (i.e. overwriting it) fragments the
+// receiver.
+
+use std::mem::drop;
+
+pub struct Pair<X,Y> { x: X, y: Y }
+
+#[rustc_move_fragments]
+pub fn test_overwrite_uninit_field<Z>(z: Z) {
+    //~^ ERROR                 parent_of_fragments: `$(local mut p)`
+    //~| ERROR                  assigned_leaf_path: `$(local z)`
+    //~| ERROR                     moved_leaf_path: `$(local z)`
+    //~| ERROR                  assigned_leaf_path: `$(local mut p).x`
+    //~| ERROR                    unmoved_fragment: `$(local mut p).y`
+
+    let mut p: Pair<Z,Z>;
+    p.x = z;
+}
+
+#[rustc_move_fragments]
+pub fn test_overwrite_moved_field<Z>(mut p: Pair<Z,Z>, z: Z) {
+    //~^ ERROR                 parent_of_fragments: `$(local mut p)`
+    //~| ERROR                  assigned_leaf_path: `$(local z)`
+    //~| ERROR                     moved_leaf_path: `$(local z)`
+    //~| ERROR                  assigned_leaf_path: `$(local mut p).y`
+    //~| ERROR                    unmoved_fragment: `$(local mut p).x`
+
+    drop(p);
+    p.y = z;
+}
+
+#[rustc_move_fragments]
+pub fn test_overwrite_same_field<Z>(mut p: Pair<Z,Z>) {
+    //~^ ERROR                 parent_of_fragments: `$(local mut p)`
+    //~| ERROR                     moved_leaf_path: `$(local mut p).x`
+    //~| ERROR                  assigned_leaf_path: `$(local mut p).x`
+    //~| ERROR                    unmoved_fragment: `$(local mut p).y`
+
+    p.x = p.x;
+}
+
+pub fn main() { }
diff --git a/src/test/compile-fail/move-fragments-7.rs b/src/test/compile-fail/move-fragments-7.rs
new file mode 100644
index 00000000000..6b2c77bcac1
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-7.rs
@@ -0,0 +1,46 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that we correctly compute the move fragments for a fn.
+//
+// Note that the code below is not actually incorrect; the
+// `rustc_move_fragments` attribute is a hack that uses the error
+// reporting mechanisms as a channel for communicating from the
+// internals of the compiler.
+
+// Test that moving a Box<T> fragments its containing structure, for
+// both moving out of the structure (i.e. reading `*p.x`) and writing
+// into the container (i.e. writing `*p.x`).
+
+pub struct D { d: int }
+impl Drop for D { fn drop(&mut self) { } }
+
+pub struct Pair<X,Y> { x: X, y: Y }
+
+#[rustc_move_fragments]
+pub fn test_deref_box_field(p: Pair<Box<D>, Box<D>>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                 parent_of_fragments: `$(local p).x`
+    //~| ERROR                     moved_leaf_path: `$(local p).x.*`
+    //~| ERROR                    unmoved_fragment: `$(local p).y`
+    //~| ERROR                  assigned_leaf_path: `$(local i)`
+    let i : D = *p.x;
+}
+
+#[rustc_move_fragments]
+pub fn test_overwrite_deref_box_field(mut p: Pair<Box<D>, Box<D>>) {
+    //~^ ERROR                 parent_of_fragments: `$(local mut p)`
+    //~| ERROR                 parent_of_fragments: `$(local mut p).x`
+    //~| ERROR                  assigned_leaf_path: `$(local mut p).x.*`
+    //~| ERROR                    unmoved_fragment: `$(local mut p).y`
+    *p.x = D { d: 3 };
+}
+
+pub fn main() { }
diff --git a/src/test/compile-fail/move-fragments-8.rs b/src/test/compile-fail/move-fragments-8.rs
new file mode 100644
index 00000000000..40ab541128c
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-8.rs
@@ -0,0 +1,39 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that we correctly compute the move fragments for a fn.
+//
+// Note that the code below is not actually incorrect; the
+// `rustc_move_fragments` attribute is a hack that uses the error
+// reporting mechanisms as a channel for communicating from the
+// internals of the compiler.
+
+// Test that assigning into a `&T` within structured container does
+// *not* fragment its containing structure.
+//
+// Compare against the `Box<T>` handling in move-fragments-7.rs. Note
+// also that in this case we cannot do a move out of `&T`, so we only
+// test writing `*p.x` here.
+
+pub struct D { d: int }
+impl Drop for D { fn drop(&mut self) { } }
+
+pub struct Pair<X,Y> { x: X, y: Y }
+
+#[rustc_move_fragments]
+pub fn test_overwrite_deref_ampersand_field<'a>(p: Pair<&'a mut D, &'a D>) {
+    //~^ ERROR                 parent_of_fragments: `$(local p)`
+    //~| ERROR                 parent_of_fragments: `$(local p).x`
+    //~| ERROR                  assigned_leaf_path: `$(local p).x.*`
+    //~| ERROR                    unmoved_fragment: `$(local p).y`
+    *p.x = D { d: 3 };
+}
+
+pub fn main() { }
diff --git a/src/test/compile-fail/move-fragments-9.rs b/src/test/compile-fail/move-fragments-9.rs
new file mode 100644
index 00000000000..ce05087f659
--- /dev/null
+++ b/src/test/compile-fail/move-fragments-9.rs
@@ -0,0 +1,100 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test moving array structures, e.g. `[T, ..3]` as well as moving
+// elements in and out of such arrays.
+//
+// Note also that the `test_move_array_then_overwrite` tests represent
+// cases that we probably should make illegal.
+
+pub struct D { d: int }
+impl Drop for D { fn drop(&mut self) { } }
+
+#[rustc_move_fragments]
+pub fn test_move_array_via_return(a: [D, ..3]) -> [D, ..3] {
+    //~^ ERROR                  assigned_leaf_path: `$(local a)`
+    //~| ERROR                     moved_leaf_path: `$(local a)`
+    return a;
+}
+
+#[rustc_move_fragments]
+pub fn test_move_array_into_recv(a: [D, ..3], recv: &mut [D, ..3]) {
+    //~^ ERROR                 parent_of_fragments: `$(local recv)`
+    //~| ERROR                  assigned_leaf_path: `$(local a)`
+    //~| ERROR                     moved_leaf_path: `$(local a)`
+    //~| ERROR                  assigned_leaf_path: `$(local recv).*`
+    *recv = a;
+}
+
+#[rustc_move_fragments]
+pub fn test_extract_array_elem(a: [D, ..3], i: uint) -> D {
+    //~^ ERROR                 parent_of_fragments: `$(local a)`
+    //~| ERROR                  assigned_leaf_path: `$(local i)`
+    //~| ERROR                     moved_leaf_path: `$(local a).[]`
+    //~| ERROR                    unmoved_fragment: `$(allbutone $(local a).[])`
+    a[i]
+}
+
+#[rustc_move_fragments]
+pub fn test_overwrite_array_elem(mut a: [D, ..3], i: uint, d: D) {
+    //~^ ERROR                 parent_of_fragments: `$(local mut a)`
+    //~| ERROR                  assigned_leaf_path: `$(local i)`
+    //~| ERROR                  assigned_leaf_path: `$(local d)`
+    //~| ERROR                     moved_leaf_path: `$(local d)`
+    //~| ERROR                  assigned_leaf_path: `$(local mut a).[]`
+    //~| ERROR                    unmoved_fragment: `$(allbutone $(local mut a).[])`
+    a[i] = d;
+}
+
+// FIXME (pnkfelix): Both test_move_array_then_overwrite_elem1 and
+// test_move_array_then_overwrite_elem2 illustrate a behavior that
+// we need to make illegal if we want to get rid of drop-flags.
+// See RFC PR 320 for more discussion.
+
+#[rustc_move_fragments]
+pub fn test_move_array_then_overwrite_elem1(mut a: [D, ..3], i: uint, recv: &mut [D, ..3], d: D) {
+    //~^ ERROR                 parent_of_fragments: `$(local mut a)`
+    //~| ERROR                 parent_of_fragments: `$(local recv)`
+    //~| ERROR                  assigned_leaf_path: `$(local recv).*`
+    //~| ERROR                  assigned_leaf_path: `$(local i)`
+    //~| ERROR                  assigned_leaf_path: `$(local d)`
+    //~| ERROR                     moved_leaf_path: `$(local d)`
+    //~| ERROR                  assigned_leaf_path: `$(local mut a).[]`
+    //~| ERROR                    unmoved_fragment: `$(allbutone $(local mut a).[])`
+
+    // This test covers the case where the array contents have been all moved away, but
+    // we still need to deal with new initializing writes into the array.
+    *recv = a;
+    a[i] = d;
+}
+
+#[rustc_move_fragments]
+pub fn test_move_array_then_overwrite_elem2(mut a: [D, ..3], i: uint, j: uint,
+                                            recv: &mut [D, ..3], d1: D, d2: D) {
+    //~^^ ERROR                parent_of_fragments: `$(local mut a)`
+    //~| ERROR                 parent_of_fragments: `$(local recv)`
+    //~| ERROR                  assigned_leaf_path: `$(local recv).*`
+    //~| ERROR                  assigned_leaf_path: `$(local i)`
+    //~| ERROR                  assigned_leaf_path: `$(local j)`
+    //~| ERROR                  assigned_leaf_path: `$(local d1)`
+    //~| ERROR                  assigned_leaf_path: `$(local d2)`
+    //~| ERROR                     moved_leaf_path: `$(local d1)`
+    //~| ERROR                     moved_leaf_path: `$(local d2)`
+    //~| ERROR                  assigned_leaf_path: `$(local mut a).[]`
+    //~| ERROR                    unmoved_fragment: `$(allbutone $(local mut a).[])`
+
+    // This test covers the case where the array contents have been all moved away, but
+    // we still need to deal with new initializing writes into the array.
+    *recv = a;
+    a[i] = d1;
+    a[j] = d2;
+}
+
+pub fn main() { }