about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc/metadata/tydecode.rs8
-rw-r--r--src/librustc/metadata/tyencode.rs4
-rw-r--r--src/librustc/middle/region.rs289
-rw-r--r--src/librustc/util/ppaux.rs72
-rw-r--r--src/test/compile-fail/borrowck-let-suggestion-suffixes.rs57
-rw-r--r--src/test/compile-fail/borrowck-let-suggestion.rs2
6 files changed, 351 insertions, 81 deletions
diff --git a/src/librustc/metadata/tydecode.rs b/src/librustc/metadata/tydecode.rs
index cb6b7e56b57..2ee4b6fbbd4 100644
--- a/src/librustc/metadata/tydecode.rs
+++ b/src/librustc/metadata/tydecode.rs
@@ -377,6 +377,14 @@ fn parse_scope(st: &mut PState) -> region::CodeExtent {
             let node_id = parse_uint(st) as ast::NodeId;
             region::CodeExtent::Misc(node_id)
         }
+        'B' => {
+            let node_id = parse_uint(st) as ast::NodeId;
+            let first_stmt_index = parse_uint(st);
+            let block_remainder = region::BlockRemainder {
+                block: node_id, first_statement_index: first_stmt_index,
+            };
+            region::CodeExtent::Remainder(block_remainder)
+        }
         _ => panic!("parse_scope: bad input")
     }
 }
diff --git a/src/librustc/metadata/tyencode.rs b/src/librustc/metadata/tyencode.rs
index 658bacdb576..2dc334bfe95 100644
--- a/src/librustc/metadata/tyencode.rs
+++ b/src/librustc/metadata/tyencode.rs
@@ -276,7 +276,9 @@ pub fn enc_region(w: &mut SeekableMemWriter, cx: &ctxt, r: ty::Region) {
 
 fn enc_scope(w: &mut SeekableMemWriter, _cx: &ctxt, scope: region::CodeExtent) {
     match scope {
-        region::CodeExtent::Misc(node_id) => mywrite!(w, "M{}", node_id)
+        region::CodeExtent::Misc(node_id) => mywrite!(w, "M{}", node_id),
+        region::CodeExtent::Remainder(region::BlockRemainder {
+            block: b, first_statement_index: i }) => mywrite!(w, "B{}{}", b, i),
     }
 }
 
diff --git a/src/librustc/middle/region.rs b/src/librustc/middle/region.rs
index 67c0e52d664..9bba01f8af7 100644
--- a/src/librustc/middle/region.rs
+++ b/src/librustc/middle/region.rs
@@ -26,6 +26,7 @@ use syntax::codemap::{self, Span};
 use syntax::{ast, visit};
 use syntax::ast::{Block, Item, FnDecl, NodeId, Arm, Pat, Stmt, Expr, Local};
 use syntax::ast_util::{stmt_id};
+use syntax::ast_map;
 use syntax::visit::{Visitor, FnKind};
 
 /// CodeExtent represents a statically-describable extent that can be
@@ -38,7 +39,32 @@ use syntax::visit::{Visitor, FnKind};
 #[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash, RustcEncodable,
            RustcDecodable, Show, Copy)]
 pub enum CodeExtent {
-    Misc(ast::NodeId)
+    Misc(ast::NodeId),
+    Remainder(BlockRemainder),
+}
+
+/// Represents a subscope of `block` for a binding that is introduced
+/// by `block.stmts[first_statement_index]`. Such subscopes represent
+/// a suffix of the block. Note that each subscope does not include
+/// the initializer expression, if any, for the statement indexed by
+/// `first_statement_index`.
+///
+/// For example, given `{ let (a, b) = EXPR_1; let c = EXPR_2; ... }`:
+///
+/// * the subscope with `first_statement_index == 0` is scope of both
+///   `a` and `b`; it does not include EXPR_1, but does include
+///   everything after that first `let`. (If you want a scope that
+///   includes EXPR_1 as well, then do not use `CodeExtent::Remainder`,
+///   but instead another `CodeExtent` that encompasses the whole block,
+///   e.g. `CodeExtent::Misc`.
+///
+/// * the subscope with `first_statement_index == 1` is scope of `c`,
+///   and thus does not include EXPR_2, but covers the `...`.
+#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash, RustcEncodable,
+         RustcDecodable, Show, Copy)]
+pub struct BlockRemainder {
+    pub block: ast::NodeId,
+    pub first_statement_index: uint,
 }
 
 impl CodeExtent {
@@ -55,6 +81,7 @@ impl CodeExtent {
     pub fn node_id(&self) -> ast::NodeId {
         match *self {
             CodeExtent::Misc(node_id) => node_id,
+            CodeExtent::Remainder(br) => br.block,
         }
     }
 
@@ -65,8 +92,41 @@ impl CodeExtent {
     {
         match *self {
             CodeExtent::Misc(node_id) => CodeExtent::Misc(f_id(node_id)),
+            CodeExtent::Remainder(br) =>
+                CodeExtent::Remainder(BlockRemainder {
+                    block: f_id(br.block), first_statement_index: br.first_statement_index }),
         }
     }
+
+    /// Returns the span of this CodeExtent.  Note that in general the
+    /// returned span may not correspond to the span of any node id in
+    /// the AST.
+    pub fn span(&self, ast_map: &ast_map::Map) -> Option<Span> {
+        match ast_map.find(self.node_id()) {
+            Some(ast_map::NodeBlock(ref blk)) => {
+                match *self {
+                    CodeExtent::Misc(_) => Some(blk.span),
+
+                    CodeExtent::Remainder(r) => {
+                        assert_eq!(r.block, blk.id);
+                        // Want span for extent starting after the
+                        // indexed statement and ending at end of
+                        // `blk`; reuse span of `blk` and shift `lo`
+                        // forward to end of indexed statement.
+                        //
+                        // (This is the special case aluded to in the
+                        // doc-comment for this method)
+                        let stmt_span = blk.stmts[r.first_statement_index].span;
+                        Some(Span { lo: stmt_span.hi, ..blk.span })
+                    }
+                }
+            }
+            Some(ast_map::NodeExpr(ref expr)) => Some(expr.span),
+            Some(ast_map::NodeStmt(ref stmt)) => Some(stmt.span),
+            Some(ast_map::NodeItem(ref item)) => Some(item.span),
+            Some(_) | None => None,
+         }
+    }
 }
 
 /// The region maps encode information about region relationships.
@@ -74,7 +134,8 @@ impl CodeExtent {
 /// - `scope_map` maps from a scope id to the enclosing scope id; this is
 ///   usually corresponding to the lexical nesting, though in the case of
 ///   closures the parent scope is the innermost conditional expression or repeating
-///   block
+///   block. (Note that the enclosing scope id for the block
+///   associated with a closure is the closure itself.)
 ///
 /// - `var_map` maps from a variable or binding id to the block in which
 ///   that variable is declared.
@@ -115,12 +176,77 @@ pub struct RegionMaps {
     terminating_scopes: RefCell<FnvHashSet<CodeExtent>>,
 }
 
-#[derive(Copy)]
+/// Carries the node id for the innermost block or match expression,
+/// for building up the `var_map` which maps ids to the blocks in
+/// which they were declared.
+#[derive(PartialEq, Eq, Show, Copy)]
+enum InnermostDeclaringBlock {
+    None,
+    Block(ast::NodeId),
+    Statement(DeclaringStatementContext),
+    Match(ast::NodeId),
+}
+
+impl InnermostDeclaringBlock {
+    fn to_code_extent(&self) -> Option<CodeExtent> {
+        let extent = match *self {
+            InnermostDeclaringBlock::None => {
+                return Option::None;
+            }
+            InnermostDeclaringBlock::Block(id) |
+            InnermostDeclaringBlock::Match(id) => CodeExtent::from_node_id(id),
+            InnermostDeclaringBlock::Statement(s) =>  s.to_code_extent(),
+        };
+        Option::Some(extent)
+    }
+}
+
+/// Contextual information for declarations introduced by a statement
+/// (i.e. `let`). It carries node-id's for statement and enclosing
+/// block both, as well as the statement's index within the block.
+#[derive(PartialEq, Eq, Show, Copy)]
+struct DeclaringStatementContext {
+    stmt_id: ast::NodeId,
+    block_id: ast::NodeId,
+    stmt_index: uint,
+}
+
+impl DeclaringStatementContext {
+    fn to_code_extent(&self) -> CodeExtent {
+        CodeExtent::Remainder(BlockRemainder {
+            block: self.block_id,
+            first_statement_index: self.stmt_index,
+        })
+    }
+}
+
+#[derive(PartialEq, Eq, Show, Copy)]
+enum InnermostEnclosingExpr {
+    None,
+    Some(ast::NodeId),
+    Statement(DeclaringStatementContext),
+}
+
+impl InnermostEnclosingExpr {
+    fn to_code_extent(&self) -> Option<CodeExtent> {
+        let extent = match *self {
+            InnermostEnclosingExpr::None => {
+                return Option::None;
+            }
+            InnermostEnclosingExpr::Statement(s) =>
+                s.to_code_extent(),
+            InnermostEnclosingExpr::Some(parent_id) =>
+                CodeExtent::from_node_id(parent_id),
+        };
+        Some(extent)
+    }
+}
+
+#[derive(Show, Copy)]
 pub struct Context {
-    var_parent: Option<ast::NodeId>,
+    var_parent: InnermostDeclaringBlock,
 
-    // Innermost enclosing expression
-    parent: Option<ast::NodeId>,
+    parent: InnermostEnclosingExpr,
 }
 
 struct RegionResolutionVisitor<'a> {
@@ -381,16 +507,13 @@ impl RegionMaps {
     }
 }
 
-/// Records the current parent (if any) as the parent of `child_id`.
+/// Records the current parent (if any) as the parent of `child_scope`.
 fn record_superlifetime(visitor: &mut RegionResolutionVisitor,
-                        child_id: ast::NodeId,
+                        child_scope: CodeExtent,
                         _sp: Span) {
-    match visitor.cx.parent {
-        Some(parent_id) => {
-            let child_scope = CodeExtent::from_node_id(child_id);
-            let parent_scope = CodeExtent::from_node_id(parent_id);
-            visitor.region_maps.record_encl_scope(child_scope, parent_scope);
-        }
+    match visitor.cx.parent.to_code_extent() {
+        Some(parent_scope) =>
+            visitor.region_maps.record_encl_scope(child_scope, parent_scope),
         None => {}
     }
 }
@@ -399,11 +522,9 @@ fn record_superlifetime(visitor: &mut RegionResolutionVisitor,
 fn record_var_lifetime(visitor: &mut RegionResolutionVisitor,
                        var_id: ast::NodeId,
                        _sp: Span) {
-    match visitor.cx.var_parent {
-        Some(parent_id) => {
-            let parent_scope = CodeExtent::from_node_id(parent_id);
-            visitor.region_maps.record_var_scope(var_id, parent_scope);
-        }
+    match visitor.cx.var_parent.to_code_extent() {
+        Some(parent_scope) =>
+            visitor.region_maps.record_var_scope(var_id, parent_scope),
         None => {
             // this can happen in extern fn declarations like
             //
@@ -415,21 +536,72 @@ fn record_var_lifetime(visitor: &mut RegionResolutionVisitor,
 fn resolve_block(visitor: &mut RegionResolutionVisitor, blk: &ast::Block) {
     debug!("resolve_block(blk.id={:?})", blk.id);
 
-    // Record the parent of this block.
-    record_superlifetime(visitor, blk.id, blk.span);
+    let prev_cx = visitor.cx;
+
+    let blk_scope = CodeExtent::Misc(blk.id);
+    record_superlifetime(visitor, blk_scope, blk.span);
 
     // We treat the tail expression in the block (if any) somewhat
     // differently from the statements. The issue has to do with
-    // temporary lifetimes. If the user writes:
+    // temporary lifetimes. Consider the following:
     //
-    //   {
-    //     ... (&foo()) ...
-    //   }
+    //    quux({
+    //        let inner = ... (&bar()) ...;
     //
+    //        (... (&foo()) ...) // (the tail expression)
+    //    }, other_argument());
+    //
+    // Each of the statements within the block is a terminating
+    // scope, and thus a temporary (e.g. the result of calling
+    // `bar()` in the initalizer expression for `let inner = ...;`)
+    // will be cleaned up immediately after its corresponding
+    // statement (i.e. `let inner = ...;`) executes.
+    //
+    // On the other hand, temporaries associated with evaluating the
+    // tail expression for the block are assigned lifetimes so that
+    // they will be cleaned up as part of the terminating scope
+    // *surrounding* the block expression. Here, the terminating
+    // scope for the block expression is the `quux(..)` call; so
+    // those temporaries will only be cleaned up *after* both
+    // `other_argument()` has run and also the call to `quux(..)`
+    // itself has returned.
+
+    visitor.cx = Context {
+        var_parent: InnermostDeclaringBlock::Block(blk.id),
+        parent: InnermostEnclosingExpr::Some(blk.id),
+    };
+
+    {
+        // This block should be kept approximately in sync with
+        // `visit::walk_block`. (We manually walk the block, rather
+        // than call `walk_block`, in order to maintain precise
+        // `InnermostDeclaringBlock` information.)
+
+        for (i, statement) in blk.stmts.iter().enumerate() {
+            if let ast::StmtDecl(_, stmt_id) = statement.node {
+                // Each StmtDecl introduces a subscope for bindings
+                // introduced by the declaration; this subscope covers
+                // a suffix of the block . Each subscope in a block
+                // has the previous subscope in the block as a parent,
+                // except for the first such subscope, which has the
+                // block itself as a parent.
+                let declaring = DeclaringStatementContext {
+                    stmt_id: stmt_id,
+                    block_id: blk.id,
+                    stmt_index: i,
+                };
+                record_superlifetime(
+                    visitor, declaring.to_code_extent(), statement.span);
+                visitor.cx = Context {
+                    var_parent: InnermostDeclaringBlock::Statement(declaring),
+                    parent: InnermostEnclosingExpr::Statement(declaring),
+                };
+            }
+            visitor.visit_stmt(&**statement)
+        }
+        visit::walk_expr_opt(visitor, &blk.expr)
+    }
 
-    let prev_cx = visitor.cx;
-    visitor.cx = Context {var_parent: Some(blk.id), parent: Some(blk.id)};
-    visit::walk_block(visitor, blk);
     visitor.cx = prev_cx;
 }
 
@@ -449,7 +621,7 @@ fn resolve_arm(visitor: &mut RegionResolutionVisitor, arm: &ast::Arm) {
 }
 
 fn resolve_pat(visitor: &mut RegionResolutionVisitor, pat: &ast::Pat) {
-    record_superlifetime(visitor, pat.id, pat.span);
+    record_superlifetime(visitor, CodeExtent::from_node_id(pat.id), pat.span);
 
     // If this is a binding (or maybe a binding, I'm too lazy to check
     // the def map) then record the lifetime of that binding.
@@ -468,11 +640,17 @@ fn resolve_stmt(visitor: &mut RegionResolutionVisitor, stmt: &ast::Stmt) {
     debug!("resolve_stmt(stmt.id={:?})", stmt_id);
 
     let stmt_scope = CodeExtent::from_node_id(stmt_id);
+
+    // Every statement will clean up the temporaries created during
+    // execution of that statement. Therefore each statement has an
+    // associated destruction scope that represents the extent of the
+    // statement plus its destructors, and thus the extent for which
+    // regions referenced by the destructors need to survive.
     visitor.region_maps.mark_as_terminating_scope(stmt_scope);
-    record_superlifetime(visitor, stmt_id, stmt.span);
+    record_superlifetime(visitor, stmt_scope, stmt.span);
 
     let prev_parent = visitor.cx.parent;
-    visitor.cx.parent = Some(stmt_id);
+    visitor.cx.parent = InnermostEnclosingExpr::Some(stmt_id);
     visit::walk_stmt(visitor, stmt);
     visitor.cx.parent = prev_parent;
 }
@@ -480,10 +658,11 @@ fn resolve_stmt(visitor: &mut RegionResolutionVisitor, stmt: &ast::Stmt) {
 fn resolve_expr(visitor: &mut RegionResolutionVisitor, expr: &ast::Expr) {
     debug!("resolve_expr(expr.id={:?})", expr.id);
 
-    record_superlifetime(visitor, expr.id, expr.span);
+    let expr_scope = CodeExtent::Misc(expr.id);
+    record_superlifetime(visitor, expr_scope, expr.span);
 
     let prev_cx = visitor.cx;
-    visitor.cx.parent = Some(expr.id);
+    visitor.cx.parent = InnermostEnclosingExpr::Some(expr.id);
 
     {
         let region_maps = &mut visitor.region_maps;
@@ -527,11 +706,11 @@ fn resolve_expr(visitor: &mut RegionResolutionVisitor, expr: &ast::Expr) {
 
                 // The variable parent of everything inside (most importantly, the
                 // pattern) is the body.
-                visitor.cx.var_parent = Some(body.id);
+                visitor.cx.var_parent = InnermostDeclaringBlock::Block(body.id);
             }
 
             ast::ExprMatch(..) => {
-                visitor.cx.var_parent = Some(expr.id);
+                visitor.cx.var_parent = InnermostDeclaringBlock::Match(expr.id);
             }
 
             ast::ExprAssignOp(..) | ast::ExprIndex(..) |
@@ -568,19 +747,13 @@ fn resolve_local(visitor: &mut RegionResolutionVisitor, local: &ast::Local) {
     debug!("resolve_local(local.id={:?},local.init={:?})",
            local.id,local.init.is_some());
 
-    let blk_id = match visitor.cx.var_parent {
-        Some(id) => id,
-        None => {
-            visitor.sess.span_bug(
-                local.span,
-                "local without enclosing block");
-        }
-    };
-
     // For convenience in trans, associate with the local-id the var
     // scope that will be used for any bindings declared in this
     // pattern.
-    let blk_scope = CodeExtent::from_node_id(blk_id);
+    let blk_scope = visitor.cx.var_parent.to_code_extent()
+        .unwrap_or_else(|| visitor.sess.span_bug(
+            local.span, "local without enclosing block"));
+
     visitor.region_maps.record_var_scope(local.id, blk_scope);
 
     // As an exception to the normal rules governing temporary
@@ -803,7 +976,10 @@ fn resolve_local(visitor: &mut RegionResolutionVisitor, local: &ast::Local) {
 fn resolve_item(visitor: &mut RegionResolutionVisitor, item: &ast::Item) {
     // Items create a new outer block scope as far as we're concerned.
     let prev_cx = visitor.cx;
-    visitor.cx = Context {var_parent: None, parent: None};
+    visitor.cx = Context {
+        var_parent: InnermostDeclaringBlock::None,
+        parent: InnermostEnclosingExpr::None
+    };
     visit::walk_item(visitor, item);
     visitor.cx = prev_cx;
 }
@@ -829,15 +1005,20 @@ fn resolve_fn(visitor: &mut RegionResolutionVisitor,
     let outer_cx = visitor.cx;
 
     // The arguments and `self` are parented to the body of the fn.
-    visitor.cx = Context { parent: Some(body.id),
-                           var_parent: Some(body.id) };
+    visitor.cx = Context {
+        parent: InnermostEnclosingExpr::Some(body.id),
+        var_parent: InnermostDeclaringBlock::Block(body.id)
+    };
     visit::walk_fn_decl(visitor, decl);
 
     // The body of the fn itself is either a root scope (top-level fn)
     // or it continues with the inherited scope (closures).
     match fk {
         visit::FkItemFn(..) | visit::FkMethod(..) => {
-            visitor.cx = Context { parent: None, var_parent: None };
+            visitor.cx = Context {
+                parent: InnermostEnclosingExpr::None,
+                var_parent: InnermostDeclaringBlock::None
+            };
             visitor.visit_block(body);
             visitor.cx = outer_cx;
         }
@@ -898,7 +1079,10 @@ pub fn resolve_crate(sess: &Session, krate: &ast::Crate) -> RegionMaps {
         let mut visitor = RegionResolutionVisitor {
             sess: sess,
             region_maps: &maps,
-            cx: Context { parent: None, var_parent: None }
+            cx: Context {
+                parent: InnermostEnclosingExpr::None,
+                var_parent: InnermostDeclaringBlock::None,
+            }
         };
         visit::walk_crate(&mut visitor, krate);
     }
@@ -911,7 +1095,10 @@ pub fn resolve_inlined_item(sess: &Session,
     let mut visitor = RegionResolutionVisitor {
         sess: sess,
         region_maps: region_maps,
-        cx: Context { parent: None, var_parent: None }
+        cx: Context {
+            parent: InnermostEnclosingExpr::None,
+            var_parent: InnermostDeclaringBlock::None
+        }
     };
     visit::walk_inlined_item(&mut visitor, item);
 }
diff --git a/src/librustc/util/ppaux.rs b/src/librustc/util/ppaux.rs
index f93f5903e01..5601898136c 100644
--- a/src/librustc/util/ppaux.rs
+++ b/src/librustc/util/ppaux.rs
@@ -10,6 +10,7 @@
 
 
 use middle::def;
+use middle::region;
 use middle::subst::{VecPerParamSpace,Subst};
 use middle::subst;
 use middle::ty::{BoundRegion, BrAnon, BrNamed};
@@ -84,37 +85,41 @@ pub fn explain_region_and_span(cx: &ctxt, region: ty::Region)
                             -> (String, Option<Span>) {
     return match region {
       ReScope(scope) => {
-        match cx.map.find(scope.node_id()) {
-          Some(ast_map::NodeBlock(ref blk)) => {
-            explain_span(cx, "block", blk.span)
-          }
-          Some(ast_map::NodeExpr(expr)) => {
-            match expr.node {
-              ast::ExprCall(..) => explain_span(cx, "call", expr.span),
-              ast::ExprMethodCall(..) => {
-                explain_span(cx, "method call", expr.span)
-              },
-              ast::ExprMatch(_, _, ast::MatchSource::IfLetDesugar { .. }) =>
-                  explain_span(cx, "if let", expr.span),
-              ast::ExprMatch(_, _, ast::MatchSource::WhileLetDesugar) => {
-                  explain_span(cx, "while let", expr.span)
-              },
-              ast::ExprMatch(..) => explain_span(cx, "match", expr.span),
-              _ => explain_span(cx, "expression", expr.span)
-            }
-          }
-          Some(ast_map::NodeStmt(stmt)) => {
-              explain_span(cx, "statement", stmt.span)
-          }
-          Some(ast_map::NodeItem(it)) => {
-              let tag = item_scope_tag(&*it);
-              explain_span(cx, tag, it.span)
-          }
+        let new_string;
+        let on_unknown_scope = |&:| {
+          (format!("unknown scope: {:?}.  Please report a bug.", scope), None)
+        };
+        let span = match scope.span(&cx.map) {
+          Some(s) => s,
+          None => return on_unknown_scope(),
+        };
+        let tag = match cx.map.find(scope.node_id()) {
+          Some(ast_map::NodeBlock(_)) => "block",
+          Some(ast_map::NodeExpr(expr)) => match expr.node {
+              ast::ExprCall(..) => "call",
+              ast::ExprMethodCall(..) => "method call",
+              ast::ExprMatch(_, _, ast::MatchSource::IfLetDesugar { .. }) => "if let",
+              ast::ExprMatch(_, _, ast::MatchSource::WhileLetDesugar) =>  "while let",
+              ast::ExprMatch(..) => "match",
+              _ => "expression",
+          },
+          Some(ast_map::NodeStmt(_)) => "statement",
+          Some(ast_map::NodeItem(it)) => item_scope_tag(&*it),
           Some(_) | None => {
             // this really should not happen
-            (format!("unknown scope: {:?}.  Please report a bug.", scope), None)
+            return on_unknown_scope();
           }
-        }
+        };
+        let scope_decorated_tag = match scope {
+            region::CodeExtent::Misc(_) => tag,
+            region::CodeExtent::Remainder(r) => {
+                new_string = format!("block suffix following statement {}",
+                                     r.first_statement_index);
+                new_string.as_slice()
+            }
+        };
+        explain_span(cx, scope_decorated_tag, span)
+
       }
 
       ReFree(ref fr) => {
@@ -867,6 +872,17 @@ impl<'tcx> Repr<'tcx> for ty::FreeRegion {
     }
 }
 
+impl<'tcx> Repr<'tcx> for region::CodeExtent {
+    fn repr(&self, _tcx: &ctxt) -> String {
+        match *self {
+            region::CodeExtent::Misc(node_id) =>
+                format!("Misc({})", node_id),
+            region::CodeExtent::Remainder(rem) =>
+                format!("Remainder({}, {})", rem.block, rem.first_statement_index),
+        }
+    }
+}
+
 impl<'tcx> Repr<'tcx> for ast::DefId {
     fn repr(&self, tcx: &ctxt) -> String {
         // Unfortunately, there seems to be no way to attempt to print
diff --git a/src/test/compile-fail/borrowck-let-suggestion-suffixes.rs b/src/test/compile-fail/borrowck-let-suggestion-suffixes.rs
new file mode 100644
index 00000000000..f551a2aa811
--- /dev/null
+++ b/src/test/compile-fail/borrowck-let-suggestion-suffixes.rs
@@ -0,0 +1,57 @@
+// Copyright 2015 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.
+
+fn f() {
+    let old = ['o'];         // statement 0
+    let mut v1 = Vec::new(); // statement 1
+
+    let mut v2 = Vec::new(); // statement 2
+    //~^ NOTE reference must be valid for the block suffix following statement 2
+
+    let young = ['y'];       // statement 3
+    //~^ NOTE ...but borrowed value is only valid for the block suffix following statement 3
+
+    v2.push(&young[0]);      // statement 4
+    //~^ ERROR `young[..]` does not live long enough
+
+    let mut v3 = Vec::new(); // statement 5
+    //~^ NOTE reference must be valid for the block suffix following statement 5
+
+    v3.push(&'x');           // statement 6
+    //~^ ERROR borrowed value does not live long enough
+    //~| NOTE ...but borrowed value is only valid for the statement
+    //~| HELP consider using a `let` binding to increase its lifetime
+
+    {
+
+        let mut v4 = Vec::new(); // (sub) statement 0
+        //~^ NOTE reference must be valid for the block suffix following statement 0
+
+        v4.push(&'y');
+        //~^ ERROR borrowed value does not live long enough
+        //~| NOTE ...but borrowed value is only valid for the statement
+        //~| HELP consider using a `let` binding to increase its lifetime
+
+    }                       // (statement 7)
+
+    let mut v5 = Vec::new(); // statement 8
+    //~^ NOTE reference must be valid for the block suffix following statement 8
+
+    v5.push(&'z');
+    //~^ ERROR borrowed value does not live long enough
+    //~| NOTE ...but borrowed value is only valid for the statement
+    //~| HELP consider using a `let` binding to increase its lifetime
+
+    v1.push(&old[0]);
+}
+
+fn main() {
+    f();
+}
diff --git a/src/test/compile-fail/borrowck-let-suggestion.rs b/src/test/compile-fail/borrowck-let-suggestion.rs
index 5f5ff4014e1..a08021919df 100644
--- a/src/test/compile-fail/borrowck-let-suggestion.rs
+++ b/src/test/compile-fail/borrowck-let-suggestion.rs
@@ -10,7 +10,7 @@
 
 fn f() {
     let x = [1is].iter(); //~ ERROR borrowed value does not live long enough
-                         //~^^ NOTE reference must be valid for the block
+                         //~^ NOTE reference must be valid for the block suffix following statement
                          //~^^ HELP consider using a `let` binding to increase its lifetime
 }