about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2014-10-02 11:32:25 +0000
committerbors <bors@rust-lang.org>2014-10-02 11:32:25 +0000
commit84a4a07bbd66f9b1a05591d2c1a4964944c47d83 (patch)
treea3c7f6ddc470fc1584817587618bf7e6c31ba5b5
parentdd7f00de801e4ca24c9c1235227ace4f998d4b1c (diff)
parent02c6ebde7e9bca72baa0623d9c5f6a86a30ec486 (diff)
downloadrust-84a4a07bbd66f9b1a05591d2c1a4964944c47d83.tar.gz
rust-84a4a07bbd66f9b1a05591d2c1a4964944c47d83.zip
auto merge of #17434 : P1start/rust/borrowck-messages, r=nikomatsakis
This was originally part of #17215.

Closes #15506.
Closes #15630.
Closes #17263.

This also partially implements #15838.
-rw-r--r--src/librustc/middle/borrowck/check_loans.rs84
-rw-r--r--src/librustc/middle/borrowck/mod.rs114
-rw-r--r--src/librustc/middle/typeck/astconv.rs83
-rw-r--r--src/librustc/middle/typeck/check/mod.rs2
-rw-r--r--src/librustc/middle/typeck/rscope.rs33
-rw-r--r--src/test/compile-fail/borrowck-box-insensitivity.rs15
-rw-r--r--src/test/compile-fail/borrowck-field-sensitivity.rs4
-rw-r--r--src/test/compile-fail/issue-17263.rs23
-rw-r--r--src/test/compile-fail/lifetime-elision-return-type-requires-explicit-lifetime.rs7
-rw-r--r--src/test/compile-fail/liveness-use-after-move.rs2
-rw-r--r--src/test/compile-fail/use-after-move-self-based-on-type.rs2
-rw-r--r--src/test/compile-fail/use-after-move-self.rs2
12 files changed, 299 insertions, 72 deletions
diff --git a/src/librustc/middle/borrowck/check_loans.rs b/src/librustc/middle/borrowck/check_loans.rs
index 26eca0938b1..a5111cdf7c8 100644
--- a/src/librustc/middle/borrowck/check_loans.rs
+++ b/src/librustc/middle/borrowck/check_loans.rs
@@ -400,50 +400,82 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
         for restr_path in loan1.restricted_paths.iter() {
             if *restr_path != loan2_base_path { continue; }
 
-            let old_pronoun = if new_loan.loan_path == old_loan.loan_path {
+            // If new_loan is something like `x.a`, and old_loan is something like `x.b`, we would
+            // normally generate a rather confusing message (in this case, for multiple mutable
+            // borrows):
+            //
+            //     error: cannot borrow `x.b` as mutable more than once at a time
+            //     note: previous borrow of `x.a` occurs here; the mutable borrow prevents
+            //     subsequent moves, borrows, or modification of `x.a` until the borrow ends
+            //
+            // What we want to do instead is get the 'common ancestor' of the two borrow paths and
+            // use that for most of the message instead, giving is something like this:
+            //
+            //     error: cannot borrow `x` as mutable more than once at a time
+            //     note: previous borrow of `x` occurs here (through borrowing `x.a`); the mutable
+            //     borrow prevents subsequent moves, borrows, or modification of `x` until the
+            //     borrow ends
+
+            let common = new_loan.loan_path.common(&*old_loan.loan_path);
+            let (nl, ol, new_loan_msg, old_loan_msg) =
+                if new_loan.loan_path.has_fork(&*old_loan.loan_path) && common.is_some() {
+                    let nl = self.bccx.loan_path_to_string(&common.unwrap());
+                    let ol = nl.clone();
+                    let new_loan_msg = format!(" (here through borrowing `{}`)",
+                                               self.bccx.loan_path_to_string(
+                                                   &*new_loan.loan_path));
+                    let old_loan_msg = format!(" (through borrowing `{}`)",
+                                               self.bccx.loan_path_to_string(
+                                                   &*old_loan.loan_path));
+                    (nl, ol, new_loan_msg, old_loan_msg)
+                } else {
+                    (self.bccx.loan_path_to_string(&*new_loan.loan_path),
+                     self.bccx.loan_path_to_string(&*old_loan.loan_path),
+                     String::new(), String::new())
+                };
+
+            let ol_pronoun = if new_loan.loan_path == old_loan.loan_path {
                 "it".to_string()
             } else {
-                format!("`{}`",
-                        self.bccx.loan_path_to_string(&*old_loan.loan_path))
+                format!("`{}`", ol)
             };
 
             match (new_loan.kind, old_loan.kind) {
                 (ty::MutBorrow, ty::MutBorrow) => {
                     self.bccx.span_err(
                         new_loan.span,
-                        format!("cannot borrow `{}` as mutable \
+                        format!("cannot borrow `{}`{} as mutable \
                                 more than once at a time",
-                                self.bccx.loan_path_to_string(
-                                    &*new_loan.loan_path)).as_slice());
+                                nl, new_loan_msg).as_slice())
                 }
 
                 (ty::UniqueImmBorrow, _) => {
                     self.bccx.span_err(
                         new_loan.span,
                         format!("closure requires unique access to `{}` \
-                                but {} is already borrowed",
-                                self.bccx.loan_path_to_string(&*new_loan.loan_path),
-                                old_pronoun).as_slice());
+                                but {} is already borrowed{}",
+                                nl, ol_pronoun, old_loan_msg).as_slice());
                 }
 
                 (_, ty::UniqueImmBorrow) => {
                     self.bccx.span_err(
                         new_loan.span,
-                        format!("cannot borrow `{}` as {} because \
+                        format!("cannot borrow `{}`{} as {} because \
                                 previous closure requires unique access",
-                                self.bccx.loan_path_to_string(&*new_loan.loan_path),
-                                new_loan.kind.to_user_str()).as_slice());
+                                nl, new_loan_msg, new_loan.kind.to_user_str()).as_slice());
                 }
 
                 (_, _) => {
                     self.bccx.span_err(
                         new_loan.span,
-                        format!("cannot borrow `{}` as {} because \
-                                {} is also borrowed as {}",
-                                self.bccx.loan_path_to_string(&*new_loan.loan_path),
+                        format!("cannot borrow `{}`{} as {} because \
+                                {} is also borrowed as {}{}",
+                                nl,
+                                new_loan_msg,
                                 new_loan.kind.to_user_str(),
-                                old_pronoun,
-                                old_loan.kind.to_user_str()).as_slice());
+                                ol_pronoun,
+                                old_loan.kind.to_user_str(),
+                                old_loan_msg).as_slice());
                 }
             }
 
@@ -452,8 +484,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
                     self.bccx.span_note(
                         span,
                         format!("borrow occurs due to use of `{}` in closure",
-                                self.bccx.loan_path_to_string(
-                                    &*new_loan.loan_path)).as_slice());
+                                nl).as_slice());
                 }
                 _ => { }
             }
@@ -463,30 +494,29 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
                     format!("the mutable borrow prevents subsequent \
                             moves, borrows, or modification of `{0}` \
                             until the borrow ends",
-                            self.bccx.loan_path_to_string(
-                                &*old_loan.loan_path))
+                            ol)
                 }
 
                 ty::ImmBorrow => {
                     format!("the immutable borrow prevents subsequent \
                             moves or mutable borrows of `{0}` \
                             until the borrow ends",
-                            self.bccx.loan_path_to_string(&*old_loan.loan_path))
+                            ol)
                 }
 
                 ty::UniqueImmBorrow => {
                     format!("the unique capture prevents subsequent \
                             moves or borrows of `{0}` \
                             until the borrow ends",
-                            self.bccx.loan_path_to_string(&*old_loan.loan_path))
+                            ol)
                 }
             };
 
             let borrow_summary = match old_loan.cause {
                 euv::ClosureCapture(_) => {
-                    format!("previous borrow of `{}` occurs here due to \
+                    format!("previous borrow of `{}` occurs here{} due to \
                             use in closure",
-                            self.bccx.loan_path_to_string(&*old_loan.loan_path))
+                            ol, old_loan_msg)
                 }
 
                 euv::OverloadedOperator(..) |
@@ -496,8 +526,8 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
                 euv::ForLoop(..) |
                 euv::RefBinding(..) |
                 euv::MatchDiscriminant(..) => {
-                    format!("previous borrow of `{}` occurs here",
-                            self.bccx.loan_path_to_string(&*old_loan.loan_path))
+                    format!("previous borrow of `{}` occurs here{}",
+                            ol, old_loan_msg)
                 }
             };
 
diff --git a/src/librustc/middle/borrowck/mod.rs b/src/librustc/middle/borrowck/mod.rs
index d4d6fae53e3..b411620dac0 100644
--- a/src/librustc/middle/borrowck/mod.rs
+++ b/src/librustc/middle/borrowck/mod.rs
@@ -298,6 +298,51 @@ impl LoanPath {
             LpExtend(ref base, _, _) => base.kill_scope(tcx),
         }
     }
+
+    fn has_fork(&self, other: &LoanPath) -> bool {
+        match (self, other) {
+            (&LpExtend(ref base, _, LpInterior(id)), &LpExtend(ref base2, _, LpInterior(id2))) =>
+                if id == id2 {
+                    base.has_fork(&**base2)
+                } else {
+                    true
+                },
+            (&LpExtend(ref base, _, LpDeref(_)), _) => base.has_fork(other),
+            (_, &LpExtend(ref base, _, LpDeref(_))) => self.has_fork(&**base),
+            _ => false,
+        }
+    }
+
+    fn depth(&self) -> uint {
+        match *self {
+            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))) =>
+                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))
+                        } 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 },
+            _ => None,
+        }
+    }
 }
 
 pub fn opt_loan_path(cmt: &mc::cmt) -> Option<Rc<LoanPath>> {
@@ -416,24 +461,58 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
             MovedInCapture => "capture",
         };
 
-        match the_move.kind {
+        let (ol, moved_lp_msg) = match the_move.kind {
             move_data::Declared => {
                 self.tcx.sess.span_err(
                     use_span,
                     format!("{} of possibly uninitialized variable: `{}`",
                             verb,
                             self.loan_path_to_string(lp)).as_slice());
+                (self.loan_path_to_string(moved_lp),
+                 String::new())
             }
             _ => {
-                let partially = if lp == moved_lp {""} else {"partially "};
+                // If moved_lp is something like `x.a`, and lp is something like `x.b`, we would
+                // normally generate a rather confusing message:
+                //
+                //     error: use of moved value: `x.b`
+                //     note: `x.a` moved here...
+                //
+                // What we want to do instead is get the 'common ancestor' of the two moves and
+                // use that for most of the message instead, giving is something like this:
+                //
+                //     error: use of moved value: `x`
+                //     note: `x` moved here (through moving `x.a`)...
+
+                let common = moved_lp.common(lp);
+                let has_common = common.is_some();
+                let has_fork = moved_lp.has_fork(lp);
+                let (nl, ol, moved_lp_msg) =
+                    if has_fork && has_common {
+                        let nl = self.loan_path_to_string(&common.unwrap());
+                        let ol = nl.clone();
+                        let moved_lp_msg = format!(" (through moving `{}`)",
+                                                   self.loan_path_to_string(moved_lp));
+                        (nl, ol, moved_lp_msg)
+                    } else {
+                        (self.loan_path_to_string(lp),
+                         self.loan_path_to_string(moved_lp),
+                         String::new())
+                    };
+
+                let partial = moved_lp.depth() > lp.depth();
+                let msg = if !has_fork && partial { "partially " }
+                          else if has_fork && !has_common { "collaterally "}
+                          else { "" };
                 self.tcx.sess.span_err(
                     use_span,
                     format!("{} of {}moved value: `{}`",
                             verb,
-                            partially,
-                            self.loan_path_to_string(lp)).as_slice());
+                            msg,
+                            nl).as_slice());
+                (ol, moved_lp_msg)
             }
-        }
+        };
 
         match the_move.kind {
             move_data::Declared => {}
@@ -456,8 +535,9 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
                         "moved by default (use `copy` to override)");
                 self.tcx.sess.span_note(
                     expr_span,
-                    format!("`{}` moved here because it has type `{}`, which is {}",
-                            self.loan_path_to_string(moved_lp),
+                    format!("`{}` moved here{} because it has type `{}`, which is {}",
+                            ol,
+                            moved_lp_msg,
                             expr_ty.user_string(self.tcx),
                             suggestion).as_slice());
             }
@@ -465,10 +545,11 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
             move_data::MovePat => {
                 let pat_ty = ty::node_id_to_type(self.tcx, the_move.id);
                 self.tcx.sess.span_note(self.tcx.map.span(the_move.id),
-                    format!("`{}` moved here because it has type `{}`, \
+                    format!("`{}` moved here{} because it has type `{}`, \
                              which is moved by default (use `ref` to \
                              override)",
-                            self.loan_path_to_string(moved_lp),
+                            ol,
+                            moved_lp_msg,
                             pat_ty.user_string(self.tcx)).as_slice());
             }
 
@@ -491,9 +572,10 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
                          capture that instead to override)");
                 self.tcx.sess.span_note(
                     expr_span,
-                    format!("`{}` moved into closure environment here because it \
+                    format!("`{}` moved into closure environment here{} because it \
                             has type `{}`, which is {}",
-                            self.loan_path_to_string(moved_lp),
+                            ol,
+                            moved_lp_msg,
                             expr_ty.user_string(self.tcx),
                             suggestion).as_slice());
             }
@@ -602,6 +684,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
                                          span: Span,
                                          kind: AliasableViolationKind,
                                          cause: mc::AliasableReason) {
+        let mut is_closure = false;
         let prefix = match kind {
             MutabilityViolation => {
                 "cannot assign to data"
@@ -625,6 +708,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
             }
 
             BorrowViolation(euv::ClosureInvocation) => {
+                is_closure = true;
                 "closure invocation"
             }
 
@@ -649,7 +733,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
             mc::AliasableManaged => {
                 self.tcx.sess.span_err(
                     span,
-                    format!("{} in a `@` pointer", prefix).as_slice());
+                    format!("{} in a `Gc` pointer", prefix).as_slice());
             }
             mc::AliasableBorrowed => {
                 self.tcx.sess.span_err(
@@ -657,6 +741,12 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
                     format!("{} in a `&` reference", prefix).as_slice());
             }
         }
+
+        if is_closure {
+            self.tcx.sess.span_note(
+                span,
+                "closures behind references must be called via `&mut`");
+        }
     }
 
     pub fn note_and_explain_bckerr(&self, err: BckError) {
diff --git a/src/librustc/middle/typeck/astconv.rs b/src/librustc/middle/typeck/astconv.rs
index e618e08f8a4..b73fa172d3f 100644
--- a/src/librustc/middle/typeck/astconv.rs
+++ b/src/librustc/middle/typeck/astconv.rs
@@ -59,7 +59,7 @@ use middle::subst::{VecPerParamSpace};
 use middle::ty;
 use middle::typeck::lookup_def_tcx;
 use middle::typeck::infer;
-use middle::typeck::rscope::{ExplicitRscope, RegionScope, SpecificRscope};
+use middle::typeck::rscope::{UnelidableRscope, RegionScope, SpecificRscope};
 use middle::typeck::rscope;
 use middle::typeck::TypeAndSubsts;
 use middle::typeck;
@@ -67,10 +67,11 @@ use util::ppaux::{Repr, UserString};
 
 use std::collections::HashMap;
 use std::rc::Rc;
-use syntax::abi;
-use syntax::{ast, ast_util};
+use std::iter::AdditiveIterator;
+use syntax::{abi, ast, ast_util};
 use syntax::codemap::Span;
 use syntax::parse::token;
+use syntax::print::pprust;
 
 pub trait AstConv<'tcx> {
     fn tcx<'a>(&'a self) -> &'a ty::ctxt<'tcx>;
@@ -147,10 +148,49 @@ pub fn opt_ast_region_to_region<'tcx, AC: AstConv<'tcx>, RS: RegionScope>(
 
         None => {
             match rscope.anon_regions(default_span, 1) {
-                Err(()) => {
+                Err(v) => {
                     debug!("optional region in illegal location");
                     span_err!(this.tcx().sess, default_span, E0106,
                         "missing lifetime specifier");
+                    match v {
+                        Some(v) => {
+                            let mut m = String::new();
+                            let len = v.len();
+                            for (i, (name, n)) in v.move_iter().enumerate() {
+                                m.push_str(if n == 1 {
+                                    format!("`{}`", name)
+                                } else {
+                                    format!("one of `{}`'s {} elided lifetimes", name, n)
+                                }.as_slice());
+
+                                if len == 2 && i == 0 {
+                                    m.push_str(" or ");
+                                } else if i == len - 2 {
+                                    m.push_str(", or ");
+                                } else if i != len - 1 {
+                                    m.push_str(", ");
+                                }
+                            }
+                            if len == 1 {
+                                span_note!(this.tcx().sess, default_span,
+                                    "this function's return type contains a borrowed value, but \
+                                     the signature does not say which {} it is borrowed from",
+                                    m);
+                            } else if len == 0 {
+                                span_note!(this.tcx().sess, default_span,
+                                    "this function's return type contains a borrowed value, but \
+                                     there is no value for it to be borrowed from");
+                                span_note!(this.tcx().sess, default_span,
+                                    "consider giving it a 'static lifetime");
+                            } else {
+                                span_note!(this.tcx().sess, default_span,
+                                    "this function's return type contains a borrowed value, but \
+                                     the signature does not say whether it is borrowed from {}",
+                                    m);
+                            }
+                        }
+                        None => {},
+                    }
                     ty::ReStatic
                 }
 
@@ -217,7 +257,7 @@ fn ast_path_substs<'tcx,AC,RS>(
 
         match anon_regions {
             Ok(v) => v.into_iter().collect(),
-            Err(()) => Vec::from_fn(expected_num_region_params,
+            Err(_) => Vec::from_fn(expected_num_region_params,
                                     |_| ty::ReStatic) // hokey
         }
     };
@@ -1153,15 +1193,20 @@ fn ty_of_method_or_bare_fn<'tcx, AC: AstConv<'tcx>>(
     };
 
     // HACK(eddyb) replace the fake self type in the AST with the actual type.
-    let input_tys = if self_ty.is_some() {
+    let input_params = if self_ty.is_some() {
         decl.inputs.slice_from(1)
     } else {
         decl.inputs.as_slice()
     };
-    let input_tys = input_tys.iter().map(|a| ty_of_arg(this, &rb, a, None));
-    let self_and_input_tys: Vec<_> =
+    let input_tys = input_params.iter().map(|a| ty_of_arg(this, &rb, a, None));
+    let input_pats: Vec<String> = input_params.iter()
+                                              .map(|a| pprust::pat_to_string(&*a.pat))
+                                              .collect();
+    let self_and_input_tys: Vec<ty::t> =
         self_ty.into_iter().chain(input_tys).collect();
 
+    let mut lifetimes_for_params: Vec<(String, Vec<ty::Region>)> = Vec::new();
+
     // Second, if there was exactly one lifetime (either a substitution or a
     // reference) in the arguments, then any anonymous regions in the output
     // have that lifetime.
@@ -1172,15 +1217,25 @@ fn ty_of_method_or_bare_fn<'tcx, AC: AstConv<'tcx>>(
             drop(self_and_input_tys_iter.next())
         }
 
-        let mut accumulator = Vec::new();
-        for input_type in self_and_input_tys_iter {
-            ty::accumulate_lifetimes_in_type(&mut accumulator, *input_type)
+        for (input_type, input_pat) in self_and_input_tys_iter.zip(input_pats.into_iter()) {
+            let mut accumulator = Vec::new();
+            ty::accumulate_lifetimes_in_type(&mut accumulator, *input_type);
+            lifetimes_for_params.push((input_pat, accumulator));
         }
-        if accumulator.len() == 1 {
-            implied_output_region = Some(*accumulator.get(0));
+
+        if lifetimes_for_params.iter().map(|&(_, ref x)| x.len()).sum() == 1 {
+            implied_output_region =
+                Some(lifetimes_for_params.iter()
+                                         .filter_map(|&(_, ref x)|
+                                            if x.len() == 1 { Some(x[0]) } else { None })
+                                         .next().unwrap());
         }
     }
 
+    let param_lifetimes: Vec<(String, uint)> = lifetimes_for_params.into_iter()
+                                                                   .map(|(n, v)| (n, v.len()))
+                                                                   .collect();
+
     let output_ty = match decl.output.node {
         ast::TyInfer => this.ty_infer(decl.output.span),
         _ => {
@@ -1193,7 +1248,7 @@ fn ty_of_method_or_bare_fn<'tcx, AC: AstConv<'tcx>>(
                     // All regions must be explicitly specified in the output
                     // if the lifetime elision rules do not apply. This saves
                     // the user from potentially-confusing errors.
-                    let rb = ExplicitRscope;
+                    let rb = UnelidableRscope::new(param_lifetimes);
                     ast_ty_to_ty(this, &rb, &*decl.output)
                 }
             }
diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs
index aa53773bddf..5f7f77ea7da 100644
--- a/src/librustc/middle/typeck/check/mod.rs
+++ b/src/librustc/middle/typeck/check/mod.rs
@@ -1601,7 +1601,7 @@ impl<'a, 'tcx> RegionScope for infer::InferCtxt<'a, 'tcx> {
     }
 
     fn anon_regions(&self, span: Span, count: uint)
-                    -> Result<Vec<ty::Region> , ()> {
+                    -> Result<Vec<ty::Region>, Option<Vec<(String, uint)>>> {
         Ok(Vec::from_fn(count, |_| {
             self.next_region_var(infer::MiscVariable(span))
         }))
diff --git a/src/librustc/middle/typeck/rscope.rs b/src/librustc/middle/typeck/rscope.rs
index 530f65855d4..2845e3954b5 100644
--- a/src/librustc/middle/typeck/rscope.rs
+++ b/src/librustc/middle/typeck/rscope.rs
@@ -29,7 +29,7 @@ pub trait RegionScope {
     fn anon_regions(&self,
                     span: Span,
                     count: uint)
-                    -> Result<Vec<ty::Region> , ()>;
+                    -> Result<Vec<ty::Region>, Option<Vec<(String, uint)>>>;
 
     fn default_region_bound(&self, span: Span) -> Option<ty::Region>;
 }
@@ -46,8 +46,31 @@ impl RegionScope for ExplicitRscope {
     fn anon_regions(&self,
                     _span: Span,
                     _count: uint)
-                    -> Result<Vec<ty::Region> , ()> {
-        Err(())
+                    -> Result<Vec<ty::Region>, Option<Vec<(String, uint)>>> {
+        Err(None)
+    }
+}
+
+// Same as `ExplicitRscope`, but provides some extra information for diagnostics
+pub struct UnelidableRscope(Vec<(String, uint)>);
+
+impl UnelidableRscope {
+    pub fn new(v: Vec<(String, uint)>) -> UnelidableRscope {
+        UnelidableRscope(v)
+    }
+}
+
+impl RegionScope for UnelidableRscope {
+    fn default_region_bound(&self, _span: Span) -> Option<ty::Region> {
+        None
+    }
+
+    fn anon_regions(&self,
+                    _span: Span,
+                    _count: uint)
+                    -> Result<Vec<ty::Region>, Option<Vec<(String, uint)>>> {
+        let UnelidableRscope(ref v) = *self;
+        Err(Some(v.clone()))
     }
 }
 
@@ -72,7 +95,7 @@ impl RegionScope for SpecificRscope {
     fn anon_regions(&self,
                     _span: Span,
                     count: uint)
-                    -> Result<Vec<ty::Region> , ()>
+                    -> Result<Vec<ty::Region>, Option<Vec<(String, uint)>>>
     {
         Ok(Vec::from_elem(count, self.default))
     }
@@ -109,7 +132,7 @@ impl RegionScope for BindingRscope {
     fn anon_regions(&self,
                     _: Span,
                     count: uint)
-                    -> Result<Vec<ty::Region> , ()>
+                    -> Result<Vec<ty::Region>, Option<Vec<(String, uint)>>>
     {
         Ok(Vec::from_fn(count, |_| self.next_region()))
     }
diff --git a/src/test/compile-fail/borrowck-box-insensitivity.rs b/src/test/compile-fail/borrowck-box-insensitivity.rs
index c9b384e0b00..d05c03547ac 100644
--- a/src/test/compile-fail/borrowck-box-insensitivity.rs
+++ b/src/test/compile-fail/borrowck-box-insensitivity.rs
@@ -31,19 +31,22 @@ struct D {
 fn copy_after_move() {
     let a = box A { x: box 0, y: 1 };
     let _x = a.x;
-    let _y = a.y; //~ ERROR use of partially moved
+    let _y = a.y; //~ ERROR use of moved
+    //~^^ NOTE `a` moved here (through moving `a.x`)
 }
 
 fn move_after_move() {
     let a = box B { x: box 0, y: box 1 };
     let _x = a.x;
-    let _y = a.y; //~ ERROR use of partially moved
+    let _y = a.y; //~ ERROR use of moved
+    //~^^ NOTE `a` moved here (through moving `a.x`)
 }
 
 fn borrow_after_move() {
     let a = box A { x: box 0, y: 1 };
     let _x = a.x;
-    let _y = &a.y; //~ ERROR use of partially moved
+    let _y = &a.y; //~ ERROR use of moved
+    //~^^ NOTE `a` moved here (through moving `a.x`)
 }
 
 fn move_after_borrow() {
@@ -79,19 +82,19 @@ fn mut_borrow_after_borrow() {
 fn copy_after_move_nested() {
     let a = box C { x: box A { x: box 0, y: 1 }, y: 2 };
     let _x = a.x.x;
-    let _y = a.y; //~ ERROR use of partially moved
+    let _y = a.y; //~ ERROR use of collaterally moved
 }
 
 fn move_after_move_nested() {
     let a = box D { x: box A { x: box 0, y: 1 }, y: box 2 };
     let _x = a.x.x;
-    let _y = a.y; //~ ERROR use of partially moved
+    let _y = a.y; //~ ERROR use of collaterally moved
 }
 
 fn borrow_after_move_nested() {
     let a = box C { x: box A { x: box 0, y: 1 }, y: 2 };
     let _x = a.x.x;
-    let _y = &a.y; //~ ERROR use of partially moved
+    let _y = &a.y; //~ ERROR use of collaterally moved
 }
 
 fn move_after_borrow_nested() {
diff --git a/src/test/compile-fail/borrowck-field-sensitivity.rs b/src/test/compile-fail/borrowck-field-sensitivity.rs
index 72042b8373d..49c93e3aa9e 100644
--- a/src/test/compile-fail/borrowck-field-sensitivity.rs
+++ b/src/test/compile-fail/borrowck-field-sensitivity.rs
@@ -13,13 +13,13 @@ struct A { a: int, b: Box<int> }
 fn deref_after_move() {
     let x = A { a: 1, b: box 2 };
     drop(x.b);
-    drop(*x.b); //~ ERROR use of partially moved value: `*x.b`
+    drop(*x.b); //~ ERROR use of moved value: `*x.b`
 }
 
 fn deref_after_fu_move() {
     let x = A { a: 1, b: box 2 };
     let y = A { a: 3, .. x };
-    drop(*x.b); //~ ERROR use of partially moved value: `*x.b`
+    drop(*x.b); //~ ERROR use of moved value: `*x.b`
 }
 
 fn borrow_after_move() {
diff --git a/src/test/compile-fail/issue-17263.rs b/src/test/compile-fail/issue-17263.rs
new file mode 100644
index 00000000000..b610a2b0c91
--- /dev/null
+++ b/src/test/compile-fail/issue-17263.rs
@@ -0,0 +1,23 @@
+// 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.
+
+struct Foo { a: int, b: int }
+
+fn main() {
+    let mut x = box Foo { a: 1, b: 2 };
+    let (a, b) = (&mut x.a, &mut x.b);
+    //~^ ERROR cannot borrow `x` (here through borrowing `x.b`) as mutable more than once at a time
+    //~^^ NOTE previous borrow of `x` occurs here (through borrowing `x.a`)
+
+    let mut foo = box Foo { a: 1, b: 2 };
+    let (c, d) = (&mut foo.a, &foo.b);
+    //~^ ERROR cannot borrow `foo` (here through borrowing `foo.b`) as immutable
+    //~^^ NOTE previous borrow of `foo` occurs here (through borrowing `foo.a`)
+}
diff --git a/src/test/compile-fail/lifetime-elision-return-type-requires-explicit-lifetime.rs b/src/test/compile-fail/lifetime-elision-return-type-requires-explicit-lifetime.rs
index aef3f7a40b5..5fa8c5db5b0 100644
--- a/src/test/compile-fail/lifetime-elision-return-type-requires-explicit-lifetime.rs
+++ b/src/test/compile-fail/lifetime-elision-return-type-requires-explicit-lifetime.rs
@@ -10,11 +10,13 @@
 
 // Lifetime annotation needed because we have no arguments.
 fn f() -> &int {    //~ ERROR missing lifetime specifier
+//~^ NOTE there is no value for it to be borrowed from
     fail!()
 }
 
 // Lifetime annotation needed because we have two by-reference parameters.
-fn g(_: &int, _: &int) -> &int {    //~ ERROR missing lifetime specifier
+fn g(_x: &int, _y: &int) -> &int {    //~ ERROR missing lifetime specifier
+//~^ NOTE the signature does not say whether it is borrowed from `_x` or `_y`
     fail!()
 }
 
@@ -24,7 +26,8 @@ struct Foo<'a> {
 
 // Lifetime annotation needed because we have two lifetimes: one as a parameter
 // and one on the reference.
-fn h(_: &Foo) -> &int { //~ ERROR missing lifetime specifier
+fn h(_x: &Foo) -> &int { //~ ERROR missing lifetime specifier
+//~^ NOTE the signature does not say which one of `_x`'s 2 elided lifetimes it is borrowed from
     fail!()
 }
 
diff --git a/src/test/compile-fail/liveness-use-after-move.rs b/src/test/compile-fail/liveness-use-after-move.rs
index 0b8e65fee08..cd7401a65ae 100644
--- a/src/test/compile-fail/liveness-use-after-move.rs
+++ b/src/test/compile-fail/liveness-use-after-move.rs
@@ -13,6 +13,6 @@ extern crate debug;
 fn main() {
     let x = box 5i;
     let y = x;
-    println!("{:?}", *x); //~ ERROR use of partially moved value: `*x`
+    println!("{:?}", *x); //~ ERROR use of moved value: `*x`
     y.clone();
 }
diff --git a/src/test/compile-fail/use-after-move-self-based-on-type.rs b/src/test/compile-fail/use-after-move-self-based-on-type.rs
index b11650a6a4f..a1b7f83da2f 100644
--- a/src/test/compile-fail/use-after-move-self-based-on-type.rs
+++ b/src/test/compile-fail/use-after-move-self-based-on-type.rs
@@ -19,7 +19,7 @@ impl Drop for S {
 impl S {
     pub fn foo(self) -> int {
         self.bar();
-        return self.x;  //~ ERROR use of partially moved value: `self.x`
+        return self.x;  //~ ERROR use of moved value: `self.x`
     }
 
     pub fn bar(self) {}
diff --git a/src/test/compile-fail/use-after-move-self.rs b/src/test/compile-fail/use-after-move-self.rs
index 22c3ec7c341..607d6163208 100644
--- a/src/test/compile-fail/use-after-move-self.rs
+++ b/src/test/compile-fail/use-after-move-self.rs
@@ -16,7 +16,7 @@ struct S {
 impl S {
     pub fn foo(self) -> int {
         self.bar();
-        return *self.x;  //~ ERROR use of partially moved value: `*self.x`
+        return *self.x;  //~ ERROR use of moved value: `*self.x`
     }
 
     pub fn bar(self) {}