From 1ac792630467f65b5296b95827e5ef7e68bab9fa Mon Sep 17 00:00:00 2001
From: Niko Matsakis <niko@alum.mit.edu>
Date: Thu, 9 Apr 2015 14:49:03 -0400
Subject: [PATCH] Improve error message where a closure escapes fn while trying
 to borrow from the current fn. Employ the new `span_suggestion` to show how
 you can use `move`.

---
 src/librustc_borrowck/borrowck/mod.rs         | 62 ++++++++++++++++---
 src/librustc_borrowck/diagnostics.rs          | 17 +++++
 src/librustc_borrowck/lib.rs                  |  4 ++
 .../borrowck-escaping-closure-error-1.rs      | 25 ++++++++
 .../borrowck-escaping-closure-error-2.rs      | 25 ++++++++
 src/test/compile-fail/issue-16747.rs          |  4 +-
 src/test/compile-fail/issue-4335.rs           |  2 +-
 src/test/compile-fail/regions-nested-fns-2.rs |  2 +-
 8 files changed, 128 insertions(+), 13 deletions(-)
 create mode 100644 src/librustc_borrowck/diagnostics.rs
 create mode 100644 src/test/compile-fail/borrowck-escaping-closure-error-1.rs
 create mode 100644 src/test/compile-fail/borrowck-escaping-closure-error-2.rs

diff --git a/src/librustc_borrowck/borrowck/mod.rs b/src/librustc_borrowck/borrowck/mod.rs
index f8da075e4bdc2..9ac288cd1a1c6 100644
--- a/src/librustc_borrowck/borrowck/mod.rs
+++ b/src/librustc_borrowck/borrowck/mod.rs
@@ -522,6 +522,16 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
     }
 
     pub fn report(&self, err: BckError<'tcx>) {
+        // Catch and handle some particular cases.
+        match (&err.code, &err.cause) {
+            (&err_out_of_scope(ty::ReScope(_), ty::ReStatic), &euv::ClosureCapture(span)) |
+            (&err_out_of_scope(ty::ReScope(_), ty::ReFree(..)), &euv::ClosureCapture(span)) => {
+                return self.report_out_of_scope_escaping_closure_capture(&err, span);
+            }
+            _ => { }
+        }
+
+        // General fallback.
         self.span_err(
             err.span,
             &self.bckerr_to_string(&err));
@@ -795,16 +805,10 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
                 format!("{} does not live long enough", msg)
             }
             err_borrowed_pointer_too_short(..) => {
-                let descr = match opt_loan_path(&err.cmt) {
-                    Some(lp) => {
-                        format!("`{}`", self.loan_path_to_string(&*lp))
-                    }
-                    None => self.cmt_to_string(&*err.cmt),
-                };
-
+                let descr = self.cmt_to_path_or_string(&err.cmt);
                 format!("lifetime of {} is too short to guarantee \
-                                its contents can be safely reborrowed",
-                               descr)
+                         its contents can be safely reborrowed",
+                        descr)
             }
         }
     }
@@ -886,6 +890,39 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
         }
     }
 
+    fn report_out_of_scope_escaping_closure_capture(&self,
+                                                    err: &BckError<'tcx>,
+                                                    capture_span: Span)
+    {
+        let cmt_path_or_string = self.cmt_to_path_or_string(&err.cmt);
+
+        span_err!(
+            self.tcx.sess, err.span, E0373,
+            "closure may outlive the current function, \
+             but it borrows {}, \
+             which is owned by the current function",
+            cmt_path_or_string);
+
+        self.tcx.sess.span_note(
+            capture_span,
+            &format!("{} is borrowed here",
+                     cmt_path_or_string));
+
+        let suggestion =
+            match self.tcx.sess.codemap().span_to_snippet(err.span) {
+                Ok(string) => format!("move {}", string),
+                Err(_) => format!("move |<args>| <body>")
+            };
+
+        self.tcx.sess.span_suggestion(
+            err.span,
+            &format!("to force the closure to take ownership of {} \
+                      (and any other referenced variables), \
+                      use the `move` keyword, as shown:",
+                     cmt_path_or_string),
+            suggestion);
+    }
+
     pub fn note_and_explain_bckerr(&self, err: BckError<'tcx>) {
         let code = err.code;
         match code {
@@ -1033,6 +1070,13 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
     pub fn cmt_to_string(&self, cmt: &mc::cmt_<'tcx>) -> String {
         cmt.descriptive_string(self.tcx)
     }
+
+    pub fn cmt_to_path_or_string(&self, cmt: &mc::cmt<'tcx>) -> String {
+        match opt_loan_path(cmt) {
+            Some(lp) => format!("`{}`", self.loan_path_to_string(&lp)),
+            None => self.cmt_to_string(cmt),
+        }
+    }
 }
 
 fn is_statement_scope(tcx: &ty::ctxt, region: ty::Region) -> bool {
diff --git a/src/librustc_borrowck/diagnostics.rs b/src/librustc_borrowck/diagnostics.rs
new file mode 100644
index 0000000000000..981b28593f9a4
--- /dev/null
+++ b/src/librustc_borrowck/diagnostics.rs
@@ -0,0 +1,17 @@
+// 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.
+
+#![allow(non_snake_case)]
+
+register_diagnostics! {
+    E0373 // closure may outlive current fn, but it borrows {}, which is owned by current fn
+}
+
+__build_diagnostic_array! { DIAGNOSTICS }
diff --git a/src/librustc_borrowck/lib.rs b/src/librustc_borrowck/lib.rs
index 54feed930a80d..647ea3555ba91 100644
--- a/src/librustc_borrowck/lib.rs
+++ b/src/librustc_borrowck/lib.rs
@@ -40,6 +40,10 @@ pub use borrowck::check_crate;
 pub use borrowck::build_borrowck_dataflow_data_for_fn;
 pub use borrowck::FnPartsWithCFG;
 
+// NB: This module needs to be declared first so diagnostics are
+// registered before they are used.
+pub mod diagnostics;
+
 mod borrowck;
 
 pub mod graphviz;
diff --git a/src/test/compile-fail/borrowck-escaping-closure-error-1.rs b/src/test/compile-fail/borrowck-escaping-closure-error-1.rs
new file mode 100644
index 0000000000000..87e40df7663ba
--- /dev/null
+++ b/src/test/compile-fail/borrowck-escaping-closure-error-1.rs
@@ -0,0 +1,25 @@
+// 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.
+
+use std::thread::spawn;
+
+// Test that we give a custom error (E0373) for the case where a
+// closure is escaping current frame, and offer a suggested code edit.
+// I refrained from including the precise message here, but the
+// original text as of the time of this writing is:
+//
+//    closure may outlive the current function, but it borrows `books`,
+//    which is owned by the current function
+
+fn main() {
+    let mut books = vec![1,2,3];
+    spawn(|| books.push(4));
+    //~^ ERROR E0373
+}
diff --git a/src/test/compile-fail/borrowck-escaping-closure-error-2.rs b/src/test/compile-fail/borrowck-escaping-closure-error-2.rs
new file mode 100644
index 0000000000000..67700be890b1f
--- /dev/null
+++ b/src/test/compile-fail/borrowck-escaping-closure-error-2.rs
@@ -0,0 +1,25 @@
+// 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.
+
+// Test that we give a custom error (E0373) for the case where a
+// closure is escaping current frame, and offer a suggested code edit.
+// I refrained from including the precise message here, but the
+// original text as of the time of this writing is:
+//
+//    closure may outlive the current function, but it borrows `books`,
+//    which is owned by the current function
+
+fn foo<'a>(x: &'a i32) -> Box<FnMut()+'a> {
+    let mut books = vec![1,2,3];
+    Box::new(|| books.push(4))
+    //~^ ERROR E0373
+}
+
+fn main() { }
diff --git a/src/test/compile-fail/issue-16747.rs b/src/test/compile-fail/issue-16747.rs
index 64334fe4392f8..b4abef0bd280b 100644
--- a/src/test/compile-fail/issue-16747.rs
+++ b/src/test/compile-fail/issue-16747.rs
@@ -18,10 +18,10 @@ trait Collection { fn len(&self) -> usize; }
 
 struct List<'a, T: ListItem<'a>> {
 //~^ ERROR the parameter type `T` may not live long enough
-//~^^ NOTE ...so that the reference type `&'a [T]` does not outlive the data it points at
+//~| HELP consider adding an explicit lifetime bound
+//~| NOTE ...so that the reference type `&'a [T]` does not outlive the data it points at
     slice: &'a [T]
 }
-//~^ HELP consider adding an explicit lifetime bound
 impl<'a, T: ListItem<'a>> Collection for List<'a, T> {
     fn len(&self) -> usize {
         0
diff --git a/src/test/compile-fail/issue-4335.rs b/src/test/compile-fail/issue-4335.rs
index 55a793f7480a4..0089bff3e8fd8 100644
--- a/src/test/compile-fail/issue-4335.rs
+++ b/src/test/compile-fail/issue-4335.rs
@@ -15,7 +15,7 @@ fn id<T>(t: T) -> T { t }
 fn f<'r, T>(v: &'r T) -> Box<FnMut() -> T + 'r> {
     // FIXME (#22405): Replace `Box::new` with `box` here when/if possible.
     id(Box::new(|| *v))
-        //~^ ERROR `v` does not live long enough
+        //~^ ERROR E0373
         //~| ERROR cannot move out of borrowed content
 }
 
diff --git a/src/test/compile-fail/regions-nested-fns-2.rs b/src/test/compile-fail/regions-nested-fns-2.rs
index bdebadb2832ca..948dc8cd21968 100644
--- a/src/test/compile-fail/regions-nested-fns-2.rs
+++ b/src/test/compile-fail/regions-nested-fns-2.rs
@@ -13,7 +13,7 @@ fn ignore<F>(_f: F) where F: for<'z> FnOnce(&'z isize) -> &'z isize {}
 fn nested() {
     let y = 3;
     ignore(
-        |z| { //~ ERROR `y` does not live long enough
+        |z| { //~ ERROR E0373
             if false { &y } else { z }
         });
 }