about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFelix S. Klock II <pnkfelix@pnkfx.org>2015-02-03 01:25:45 +0100
committerFelix S. Klock II <pnkfelix@pnkfx.org>2015-02-07 13:44:00 +0100
commitcc8f35f874c81be1a5950d2527902b7dd5a94513 (patch)
tree21d2582ef0f4dd357968b0b156fd18c318fdc9b9
parentc0a874472a863058ac39654626bf62c2d953b27b (diff)
downloadrust-cc8f35f874c81be1a5950d2527902b7dd5a94513.tar.gz
rust-cc8f35f874c81be1a5950d2527902b7dd5a94513.zip
Restrictions on moves out-from and into fixed-length arrays.
No longer legal: `fn foo(a: [D; 5]) { drop(a); a[2] = D::new(); }`;
one must first initialize the entirety of `a` before assigning to its
individual elements.

No longer legal: `fn foo(arr: [D; 5]) -> D { arr[2] }`, unless `D`
implements `Copy`. This "move out-from" restriction only affects
`expr[i]`, and not destructuring (e.g. `f([a, b, c]: Array) { ... }`).

uses mem_categorization to distinguish destructuring-bind from array
indexing.

See discussion on RFC PR 533.

[breaking-change]
-rw-r--r--src/librustc/middle/mem_categorization.rs77
-rw-r--r--src/librustc_borrowck/borrowck/check_loans.rs10
-rw-r--r--src/librustc_borrowck/borrowck/fragments.rs15
-rw-r--r--src/librustc_borrowck/borrowck/gather_loans/gather_moves.rs11
-rw-r--r--src/librustc_borrowck/borrowck/gather_loans/move_error.rs15
-rw-r--r--src/librustc_borrowck/borrowck/gather_loans/restrictions.rs4
-rw-r--r--src/librustc_borrowck/borrowck/mod.rs41
-rw-r--r--src/librustc_borrowck/borrowck/move_data.rs9
8 files changed, 142 insertions, 40 deletions
diff --git a/src/librustc/middle/mem_categorization.rs b/src/librustc/middle/mem_categorization.rs
index 3e7aa0ff1d0..58492c817fd 100644
--- a/src/librustc/middle/mem_categorization.rs
+++ b/src/librustc/middle/mem_categorization.rs
@@ -128,7 +128,7 @@ pub enum PointerKind {
 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
 pub enum InteriorKind {
     InteriorField(FieldName),
-    InteriorElement(ElementKind),
+    InteriorElement(InteriorOffsetKind, ElementKind),
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
@@ -138,6 +138,12 @@ pub enum FieldName {
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub enum InteriorOffsetKind {
+    Index,            // e.g. `array_expr[index_expr]`
+    Pattern,          // e.g. `fn foo([_, a, _, _]: [A; 4]) { ... }`
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
 pub enum ElementKind {
     VecElement,
     OtherElement,
@@ -196,10 +202,12 @@ pub enum deref_kind {
     deref_interior(InteriorKind),
 }
 
+type DerefKindContext = Option<InteriorOffsetKind>;
+
 // Categorizes a derefable type.  Note that we include vectors and strings as
 // derefable (we model an index as the combination of a deref and then a
 // pointer adjustment).
-pub fn deref_kind(t: Ty) -> McResult<deref_kind> {
+fn deref_kind(t: Ty, context: DerefKindContext) -> McResult<deref_kind> {
     match t.sty {
         ty::ty_uniq(_) => {
             Ok(deref_ptr(Unique))
@@ -220,7 +228,12 @@ pub fn deref_kind(t: Ty) -> McResult<deref_kind> {
         }
 
         ty::ty_vec(_, _) | ty::ty_str => {
-            Ok(deref_interior(InteriorElement(element_kind(t))))
+            // no deref of indexed content without supplying InteriorOffsetKind
+            if let Some(context) = context {
+                Ok(deref_interior(InteriorElement(context, element_kind(t))))
+            } else {
+                Err(())
+            }
         }
 
         _ => Err(()),
@@ -455,7 +468,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
                autoderefs,
                cmt.repr(self.tcx()));
         for deref in 1..autoderefs + 1 {
-            cmt = try!(self.cat_deref(expr, cmt, deref));
+            cmt = try!(self.cat_deref(expr, cmt, deref, None));
         }
         return Ok(cmt);
     }
@@ -467,7 +480,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
         match expr.node {
           ast::ExprUnary(ast::UnDeref, ref e_base) => {
             let base_cmt = try!(self.cat_expr(&**e_base));
-            self.cat_deref(expr, base_cmt, 0)
+            self.cat_deref(expr, base_cmt, 0, None)
           }
 
           ast::ExprField(ref base, f_name) => {
@@ -486,6 +499,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
 
           ast::ExprIndex(ref base, _) => {
             let method_call = ty::MethodCall::expr(expr.id());
+            let context = InteriorOffsetKind::Index;
             match self.typer.node_method_ty(method_call) {
                 Some(method_ty) => {
                     // If this is an index implemented by a method call, then it
@@ -507,10 +521,10 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
                     // is an rvalue. That is what we will be
                     // dereferencing.
                     let base_cmt = self.cat_rvalue_node(expr.id(), expr.span(), ret_ty);
-                    self.cat_deref_common(expr, base_cmt, 1, elem_ty, true)
+                    self.cat_deref_common(expr, base_cmt, 1, elem_ty, Some(context), true)
                 }
                 None => {
-                    self.cat_index(expr, try!(self.cat_expr(&**base)))
+                    self.cat_index(expr, try!(self.cat_expr(&**base)), context)
                 }
             }
           }
@@ -854,7 +868,8 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
     fn cat_deref<N:ast_node>(&self,
                              node: &N,
                              base_cmt: cmt<'tcx>,
-                             deref_cnt: uint)
+                             deref_cnt: uint,
+                             deref_context: DerefKindContext)
                              -> McResult<cmt<'tcx>> {
         let adjustment = match self.typer.adjustments().borrow().get(&node.id()) {
             Some(adj) if ty::adjust_is_object(adj) => ty::AutoObject,
@@ -882,7 +897,9 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
         };
         let base_cmt_ty = base_cmt.ty;
         match ty::deref(base_cmt_ty, true) {
-            Some(mt) => self.cat_deref_common(node, base_cmt, deref_cnt, mt.ty,
+            Some(mt) => self.cat_deref_common(node, base_cmt, deref_cnt,
+                                              mt.ty,
+                                              deref_context,
                                               /* implicit: */ false),
             None => {
                 debug!("Explicit deref of non-derefable type: {}",
@@ -897,10 +914,11 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
                                     base_cmt: cmt<'tcx>,
                                     deref_cnt: uint,
                                     deref_ty: Ty<'tcx>,
+                                    deref_context: DerefKindContext,
                                     implicit: bool)
                                     -> McResult<cmt<'tcx>>
     {
-        let (m, cat) = match try!(deref_kind(base_cmt.ty)) {
+        let (m, cat) = match try!(deref_kind(base_cmt.ty, deref_context)) {
             deref_ptr(ptr) => {
                 let ptr = if implicit {
                     match ptr {
@@ -932,7 +950,8 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
 
     pub fn cat_index<N:ast_node>(&self,
                                  elt: &N,
-                                 mut base_cmt: cmt<'tcx>)
+                                 mut base_cmt: cmt<'tcx>,
+                                 context: InteriorOffsetKind)
                                  -> McResult<cmt<'tcx>> {
         //! Creates a cmt for an indexing operation (`[]`).
         //!
@@ -974,18 +993,21 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
         };
 
         let m = base_cmt.mutbl.inherit();
-        return Ok(interior(elt, base_cmt.clone(), base_cmt.ty, m, element_ty));
+        return Ok(interior(elt, base_cmt.clone(), base_cmt.ty,
+                           m, context, element_ty));
 
         fn interior<'tcx, N: ast_node>(elt: &N,
                                        of_cmt: cmt<'tcx>,
                                        vec_ty: Ty<'tcx>,
                                        mutbl: MutabilityCategory,
+                                       context: InteriorOffsetKind,
                                        element_ty: Ty<'tcx>) -> cmt<'tcx>
         {
+            let interior_elem = InteriorElement(context, element_kind(vec_ty));
             Rc::new(cmt_ {
                 id:elt.id(),
                 span:elt.span(),
-                cat:cat_interior(of_cmt, InteriorElement(element_kind(vec_ty))),
+                cat:cat_interior(of_cmt, interior_elem),
                 mutbl:mutbl,
                 ty:element_ty,
                 note: NoteNone
@@ -997,10 +1019,11 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
     // underlying vec.
     fn deref_vec<N:ast_node>(&self,
                              elt: &N,
-                             base_cmt: cmt<'tcx>)
+                             base_cmt: cmt<'tcx>,
+                             context: InteriorOffsetKind)
                              -> McResult<cmt<'tcx>>
     {
-        match try!(deref_kind(base_cmt.ty)) {
+        match try!(deref_kind(base_cmt.ty, Some(context))) {
             deref_ptr(ptr) => {
                 // for unique ptrs, we inherit mutability from the
                 // owning reference.
@@ -1041,7 +1064,9 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
         let (slice_mutbl, slice_r) = vec_slice_info(self.tcx(),
                                                     slice_pat,
                                                     slice_ty);
-        let cmt_slice = try!(self.cat_index(slice_pat, try!(self.deref_vec(slice_pat, vec_cmt))));
+        let context = InteriorOffsetKind::Pattern;
+        let cmt_vec = try!(self.deref_vec(slice_pat, vec_cmt, context));
+        let cmt_slice = try!(self.cat_index(slice_pat, cmt_vec, context));
         return Ok((cmt_slice, slice_mutbl, slice_r));
 
         /// In a pattern like [a, b, ..c], normally `c` has slice type, but if you have [a, b,
@@ -1253,12 +1278,14 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
             // box p1, &p1, &mut p1.  we can ignore the mutability of
             // PatRegion since that information is already contained
             // in the type.
-            let subcmt = try!(self.cat_deref(pat, cmt, 0));
+            let subcmt = try!(self.cat_deref(pat, cmt, 0, None));
               try!(self.cat_pattern_(subcmt, &**subpat, op));
           }
 
           ast::PatVec(ref before, ref slice, ref after) => {
-              let elt_cmt = try!(self.cat_index(pat, try!(self.deref_vec(pat, cmt))));
+              let context = InteriorOffsetKind::Pattern;
+              let vec_cmt = try!(self.deref_vec(pat, cmt, context));
+              let elt_cmt = try!(self.cat_index(pat, vec_cmt, context));
               for before_pat in before {
                   try!(self.cat_pattern_(elt_cmt.clone(), &**before_pat, op));
               }
@@ -1455,10 +1482,18 @@ impl<'tcx> cmt_<'tcx> {
             cat_interior(_, InteriorField(PositionalField(_))) => {
                 "anonymous field".to_string()
             }
-            cat_interior(_, InteriorElement(VecElement)) |
-            cat_interior(_, InteriorElement(OtherElement)) => {
+            cat_interior(_, InteriorElement(InteriorOffsetKind::Index,
+                                            VecElement)) |
+            cat_interior(_, InteriorElement(InteriorOffsetKind::Index,
+                                            OtherElement)) => {
                 "indexed content".to_string()
             }
+            cat_interior(_, InteriorElement(InteriorOffsetKind::Pattern,
+                                            VecElement)) |
+            cat_interior(_, InteriorElement(InteriorOffsetKind::Pattern,
+                                            OtherElement)) => {
+                "pattern-bound indexed content".to_string()
+            }
             cat_upvar(ref var) => {
                 var.user_string(tcx)
             }
@@ -1546,7 +1581,7 @@ impl<'tcx> Repr<'tcx> for InteriorKind {
                 token::get_name(fld).to_string()
             }
             InteriorField(PositionalField(i)) => format!("#{}", i),
-            InteriorElement(_) => "[]".to_string(),
+            InteriorElement(..) => "[]".to_string(),
         }
     }
 }
diff --git a/src/librustc_borrowck/borrowck/check_loans.rs b/src/librustc_borrowck/borrowck/check_loans.rs
index b8d736ab9ca..91f1121deaa 100644
--- a/src/librustc_borrowck/borrowck/check_loans.rs
+++ b/src/librustc_borrowck/borrowck/check_loans.rs
@@ -19,6 +19,7 @@
 use self::UseError::*;
 
 use borrowck::*;
+use borrowck::InteriorKind::{InteriorElement, InteriorField};
 use rustc::middle::expr_use_visitor as euv;
 use rustc::middle::mem_categorization as mc;
 use rustc::middle::region;
@@ -743,15 +744,16 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
                 self.check_if_assigned_path_is_moved(id, span,
                                                      use_kind, lp_base);
             }
-            LpExtend(ref lp_base, _, LpInterior(_)) => {
+            LpExtend(ref lp_base, _, LpInterior(InteriorField(_))) => {
                 // assigning to `P.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(InteriorElement(..))) |
             LpExtend(ref lp_base, _, LpDeref(_)) => {
-                // assigning to `(*P)` requires that `P` be initialized
-                self.check_if_path_is_moved(id, span,
-                                            use_kind, lp_base);
+                // assigning to `P[i]` requires `P` is initialized
+                // assigning to `(*P)` requires `P` is initialized
+                self.check_if_path_is_moved(id, span, use_kind, lp_base);
             }
         }
     }
diff --git a/src/librustc_borrowck/borrowck/fragments.rs b/src/librustc_borrowck/borrowck/fragments.rs
index ecbf3a4366d..bee1ada28e3 100644
--- a/src/librustc_borrowck/borrowck/fragments.rs
+++ b/src/librustc_borrowck/borrowck/fragments.rs
@@ -14,6 +14,7 @@
 
 use self::Fragment::*;
 
+use borrowck::InteriorKind::{InteriorField, InteriorElement};
 use borrowck::{LoanPath};
 use borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend};
 use borrowck::LoanPathElem::{LpDeref, LpInterior};
@@ -300,12 +301,16 @@ fn add_fragment_siblings<'tcx>(this: &MoveData<'tcx>,
         LpExtend(_, _, LpDeref(mc::Implicit(..)))    |
         LpExtend(_, _, LpDeref(mc::BorrowedPtr(..))) => {}
 
-        // FIXME(pnkfelix): LV[j] should be tracked, at least in the
+        // 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(_))) => {
+        // Well, either that or LV[j] should be made illegal.
+        // But even then, we will need to deal with destructuring
+        // bind.
+        //
+        // Anyway, for now: LV[j] is not tracked precisely
+        LpExtend(_, _, LpInterior(InteriorElement(..))) => {
             let mp = this.move_path(tcx, lp.clone());
             gathered_fragments.push(AllButOneFrom(mp));
         }
@@ -313,7 +318,7 @@ fn add_fragment_siblings<'tcx>(this: &MoveData<'tcx>,
         // 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))) => {
+                 LpInterior(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())),
@@ -452,7 +457,7 @@ fn add_fragment_sibling_core<'tcx>(this: &MoveData<'tcx>,
         LpVar(..) | LpUpvar(..) | LpExtend(..) => enum_variant_did,
     };
 
-    let loan_path_elem = LpInterior(mc::InteriorField(new_field_name));
+    let loan_path_elem = LpInterior(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),
diff --git a/src/librustc_borrowck/borrowck/gather_loans/gather_moves.rs b/src/librustc_borrowck/borrowck/gather_loans/gather_moves.rs
index ca1fba53de4..da53e9fac11 100644
--- a/src/librustc_borrowck/borrowck/gather_loans/gather_moves.rs
+++ b/src/librustc_borrowck/borrowck/gather_loans/gather_moves.rs
@@ -16,6 +16,8 @@ use borrowck::gather_loans::move_error::{MoveError, MoveErrorCollector};
 use borrowck::move_data::*;
 use rustc::middle::expr_use_visitor as euv;
 use rustc::middle::mem_categorization as mc;
+use rustc::middle::mem_categorization::Typer;
+use rustc::middle::mem_categorization::InteriorOffsetKind as Kind;
 use rustc::middle::ty;
 use rustc::util::ppaux::Repr;
 use std::rc::Rc;
@@ -156,6 +158,7 @@ pub fn gather_assignment<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
                              mode);
 }
 
+// (keep in sync with move_error::report_cannot_move_out_of )
 fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
                                                cmt: &mc::cmt<'tcx>)
                                                -> Option<mc::cmt<'tcx>> {
@@ -174,7 +177,8 @@ fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
         }
 
         mc::cat_downcast(ref b, _) |
-        mc::cat_interior(ref b, _) => {
+        mc::cat_interior(ref b, mc::InteriorField(_)) |
+        mc::cat_interior(ref b, mc::InteriorElement(Kind::Pattern, _)) => {
             match b.ty.sty {
                 ty::ty_struct(did, _) | ty::ty_enum(did, _) => {
                     if ty::has_dtor(bccx.tcx, did) {
@@ -189,6 +193,11 @@ fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
             }
         }
 
+        mc::cat_interior(_, mc::InteriorElement(Kind::Index, _)) => {
+            // Forbid move of arr[i] for arr: [T; 3]; see RFC 533.
+            Some(cmt.clone())
+        }
+
         mc::cat_deref(ref b, _, mc::Unique) => {
             check_and_get_illegal_move_origin(bccx, b)
         }
diff --git a/src/librustc_borrowck/borrowck/gather_loans/move_error.rs b/src/librustc_borrowck/borrowck/gather_loans/move_error.rs
index d9e86bd4b6d..b00973c82bd 100644
--- a/src/librustc_borrowck/borrowck/gather_loans/move_error.rs
+++ b/src/librustc_borrowck/borrowck/gather_loans/move_error.rs
@@ -10,6 +10,8 @@
 
 use borrowck::BorrowckCtxt;
 use rustc::middle::mem_categorization as mc;
+use rustc::middle::mem_categorization::Typer;
+use rustc::middle::mem_categorization::InteriorOffsetKind as Kind;
 use rustc::middle::ty;
 use rustc::util::ppaux::UserString;
 use std::cell::RefCell;
@@ -110,6 +112,7 @@ fn group_errors_with_same_origin<'tcx>(errors: &Vec<MoveError<'tcx>>)
     }
 }
 
+// (keep in sync with gather_moves::check_and_get_illegal_move_origin )
 fn report_cannot_move_out_of<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
                                        move_from: mc::cmt<'tcx>) {
     match move_from.cat {
@@ -121,8 +124,18 @@ fn report_cannot_move_out_of<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
                                   move_from.descriptive_string(bccx.tcx))[]);
         }
 
+        mc::cat_interior(ref b, mc::InteriorElement(Kind::Index, _)) => {
+            let expr = bccx.tcx.map.expect_expr(move_from.id);
+            if let ast::ExprIndex(..) = expr.node {
+                bccx.span_err(move_from.span,
+                              &format!("cannot move out of type `{}`, \
+                                        a non-copy fixed-size array",
+                                       b.ty.user_string(bccx.tcx))[]);
+            }
+        }
+
         mc::cat_downcast(ref b, _) |
-        mc::cat_interior(ref b, _) => {
+        mc::cat_interior(ref b, mc::InteriorField(_)) => {
             match b.ty.sty {
                 ty::ty_struct(did, _) |
                 ty::ty_enum(did, _) if ty::has_dtor(bccx.tcx, did) => {
diff --git a/src/librustc_borrowck/borrowck/gather_loans/restrictions.rs b/src/librustc_borrowck/borrowck/gather_loans/restrictions.rs
index ac1e097be6f..7078b2b5f17 100644
--- a/src/librustc_borrowck/borrowck/gather_loans/restrictions.rs
+++ b/src/librustc_borrowck/borrowck/gather_loans/restrictions.rs
@@ -19,6 +19,8 @@ use rustc::middle::ty;
 use rustc::util::ppaux::Repr;
 use syntax::codemap::Span;
 
+use borrowck::ToInteriorKind;
+
 use std::rc::Rc;
 
 #[derive(Debug)]
@@ -96,7 +98,7 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
                 // the memory, so no additional restrictions are
                 // needed.
                 let result = self.restrict(cmt_base);
-                self.extend(result, &cmt, LpInterior(i))
+                self.extend(result, &cmt, LpInterior(i.cleaned()))
             }
 
             mc::cat_static_item(..) => {
diff --git a/src/librustc_borrowck/borrowck/mod.rs b/src/librustc_borrowck/borrowck/mod.rs
index e86388a5934..0bad5594882 100644
--- a/src/librustc_borrowck/borrowck/mod.rs
+++ b/src/librustc_borrowck/borrowck/mod.rs
@@ -18,6 +18,8 @@ pub use self::bckerr_code::*;
 pub use self::AliasableViolationKind::*;
 pub use self::MovedValueUseKind::*;
 
+use self::InteriorKind::*;
+
 use rustc::middle::cfg;
 use rustc::middle::dataflow::DataFlowContext;
 use rustc::middle::dataflow::BitwiseOperator;
@@ -314,10 +316,30 @@ impl<'tcx> LoanPath<'tcx> {
 //     b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003
 static DOWNCAST_PRINTED_OPERATOR : &'static str = " as ";
 
+// A local, "cleaned" version of `mc::InteriorKind` that drops
+// information that is not relevant to loan-path analysis. (In
+// particular, the distinction between how precisely a array-element
+// is tracked is irrelevant here.)
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub enum InteriorKind {
+    InteriorField(mc::FieldName),
+    InteriorElement(mc::ElementKind),
+}
+
+trait ToInteriorKind { fn cleaned(self) -> InteriorKind; }
+impl ToInteriorKind for mc::InteriorKind {
+    fn cleaned(self) -> InteriorKind {
+        match self {
+            mc::InteriorField(name) => InteriorField(name),
+            mc::InteriorElement(_, elem_kind) => InteriorElement(elem_kind),
+        }
+    }
+}
+
 #[derive(Copy, PartialEq, Eq, Hash, Debug)]
 pub enum LoanPathElem {
     LpDeref(mc::PointerKind),    // `*LV` in doc.rs
-    LpInterior(mc::InteriorKind) // `LV.f` in doc.rs
+    LpInterior(InteriorKind),    // `LV.f` in doc.rs
 }
 
 pub fn closure_to_block(closure_id: ast::NodeId,
@@ -446,7 +468,7 @@ pub fn opt_loan_path<'tcx>(cmt: &mc::cmt<'tcx>) -> Option<Rc<LoanPath<'tcx>>> {
 
         mc::cat_interior(ref cmt_base, ik) => {
             opt_loan_path(cmt_base).map(|lp| {
-                new_lp(LpExtend(lp, cmt.mutbl, LpInterior(ik)))
+                new_lp(LpExtend(lp, cmt.mutbl, LpInterior(ik.cleaned())))
             })
         }
 
@@ -918,7 +940,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
             }
 
 
-            LpExtend(ref lp_base, _, LpInterior(mc::InteriorField(fname))) => {
+            LpExtend(ref lp_base, _, LpInterior(InteriorField(fname))) => {
                 self.append_autoderefd_loan_path_to_string(&**lp_base, out);
                 match fname {
                     mc::NamedField(fname) => {
@@ -932,7 +954,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
                 }
             }
 
-            LpExtend(ref lp_base, _, LpInterior(mc::InteriorElement(_))) => {
+            LpExtend(ref lp_base, _, LpInterior(InteriorElement(..))) => {
                 self.append_autoderefd_loan_path_to_string(&**lp_base, out);
                 out.push_str("[..]");
             }
@@ -1006,6 +1028,17 @@ impl DataFlowOperator for LoanDataFlowOperator {
     }
 }
 
+impl<'tcx> Repr<'tcx> for InteriorKind {
+    fn repr(&self, _tcx: &ty::ctxt<'tcx>) -> String {
+        match *self {
+            InteriorField(mc::NamedField(fld)) =>
+                format!("{}", token::get_name(fld)),
+            InteriorField(mc::PositionalField(i)) => format!("#{}", i),
+            InteriorElement(..) => "[]".to_string(),
+        }
+    }
+}
+
 impl<'tcx> Repr<'tcx> for Loan<'tcx> {
     fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String {
         format!("Loan_{}({}, {:?}, {:?}-{:?}, {})",
diff --git a/src/librustc_borrowck/borrowck/move_data.rs b/src/librustc_borrowck/borrowck/move_data.rs
index 76c431fa4c5..6607b5cac9c 100644
--- a/src/librustc_borrowck/borrowck/move_data.rs
+++ b/src/librustc_borrowck/borrowck/move_data.rs
@@ -19,7 +19,6 @@ use rustc::middle::dataflow::DataFlowContext;
 use rustc::middle::dataflow::BitwiseOperator;
 use rustc::middle::dataflow::DataFlowOperator;
 use rustc::middle::expr_use_visitor as euv;
-use rustc::middle::mem_categorization as mc;
 use rustc::middle::ty;
 use rustc::util::nodemap::{FnvHashMap, NodeSet};
 use rustc::util::ppaux::Repr;
@@ -193,9 +192,13 @@ fn loan_path_is_precise(loan_path: &LoanPath) -> bool {
         LpVar(_) | LpUpvar(_) => {
             true
         }
-        LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => {
-            // Paths involving element accesses do not refer to a unique
+        LpExtend(_, _, LpInterior(InteriorKind::InteriorElement(..))) => {
+            // Paths involving element accesses a[i] do not refer to a unique
             // location, as there is no accurate tracking of the indices.
+            //
+            // (Paths involving element accesses via slice pattern bindings
+            // can in principle be tracked precisely, but that is future
+            // work. For now, continue claiming that they are imprecise.)
             false
         }
         LpDowncast(ref lp_base, _) |