Skip to content

Commit e313b33

Browse files
committed
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`.
1 parent 906a972 commit e313b33

File tree

8 files changed

+128
-13
lines changed

8 files changed

+128
-13
lines changed

src/librustc_borrowck/borrowck/mod.rs

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,16 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
522522
}
523523

524524
pub fn report(&self, err: BckError<'tcx>) {
525+
// Catch and handle some particular cases.
526+
match (&err.code, &err.cause) {
527+
(&err_out_of_scope(ty::ReScope(_), ty::ReStatic), &euv::ClosureCapture(span)) |
528+
(&err_out_of_scope(ty::ReScope(_), ty::ReFree(..)), &euv::ClosureCapture(span)) => {
529+
return self.report_out_of_scope_escaping_closure_capture(&err, span);
530+
}
531+
_ => { }
532+
}
533+
534+
// General fallback.
525535
self.span_err(
526536
err.span,
527537
&self.bckerr_to_string(&err));
@@ -796,16 +806,10 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
796806
format!("{} does not live long enough", msg)
797807
}
798808
err_borrowed_pointer_too_short(..) => {
799-
let descr = match opt_loan_path(&err.cmt) {
800-
Some(lp) => {
801-
format!("`{}`", self.loan_path_to_string(&*lp))
802-
}
803-
None => self.cmt_to_string(&*err.cmt),
804-
};
805-
809+
let descr = self.cmt_to_path_or_string(&err.cmt);
806810
format!("lifetime of {} is too short to guarantee \
807-
its contents can be safely reborrowed",
808-
descr)
811+
its contents can be safely reborrowed",
812+
descr)
809813
}
810814
}
811815
}
@@ -888,6 +892,39 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
888892
}
889893
}
890894

895+
fn report_out_of_scope_escaping_closure_capture(&self,
896+
err: &BckError<'tcx>,
897+
capture_span: Span)
898+
{
899+
let cmt_path_or_string = self.cmt_to_path_or_string(&err.cmt);
900+
901+
span_err!(
902+
self.tcx.sess, err.span, E0373,
903+
"closure may outlive the current function, \
904+
but it borrows {}, \
905+
which is owned by the current function",
906+
cmt_path_or_string);
907+
908+
self.tcx.sess.span_note(
909+
capture_span,
910+
&format!("{} is borrowed here",
911+
cmt_path_or_string));
912+
913+
let suggestion =
914+
match self.tcx.sess.codemap().span_to_snippet(err.span) {
915+
Ok(string) => format!("move {}", string),
916+
Err(_) => format!("move |<args>| <body>")
917+
};
918+
919+
self.tcx.sess.span_suggestion(
920+
err.span,
921+
&format!("to force the closure to take ownership of {} \
922+
(and any other referenced variables), \
923+
use the `move` keyword, as shown:",
924+
cmt_path_or_string),
925+
suggestion);
926+
}
927+
891928
pub fn note_and_explain_bckerr(&self, err: BckError<'tcx>) {
892929
let code = err.code;
893930
match code {
@@ -1035,6 +1072,13 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
10351072
pub fn cmt_to_string(&self, cmt: &mc::cmt_<'tcx>) -> String {
10361073
cmt.descriptive_string(self.tcx)
10371074
}
1075+
1076+
pub fn cmt_to_path_or_string(&self, cmt: &mc::cmt<'tcx>) -> String {
1077+
match opt_loan_path(cmt) {
1078+
Some(lp) => format!("`{}`", self.loan_path_to_string(&lp)),
1079+
None => self.cmt_to_string(cmt),
1080+
}
1081+
}
10381082
}
10391083

10401084
fn is_statement_scope(tcx: &ty::ctxt, region: ty::Region) -> bool {

src/librustc_borrowck/diagnostics.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![allow(non_snake_case)]
12+
13+
register_diagnostics! {
14+
E0373 // closure may outlive current fn, but it borrows {}, which is owned by current fn
15+
}
16+
17+
__build_diagnostic_array! { DIAGNOSTICS }

src/librustc_borrowck/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ pub use borrowck::check_crate;
4040
pub use borrowck::build_borrowck_dataflow_data_for_fn;
4141
pub use borrowck::FnPartsWithCFG;
4242

43+
// NB: This module needs to be declared first so diagnostics are
44+
// registered before they are used.
45+
pub mod diagnostics;
46+
4347
mod borrowck;
4448

4549
pub mod graphviz;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use std::thread::spawn;
12+
13+
// Test that we give a custom error (E0373) for the case where a
14+
// closure is escaping current frame, and offer a suggested code edit.
15+
// I refrained from including the precise message here, but the
16+
// original text as of the time of this writing is:
17+
//
18+
// closure may outlive the current function, but it borrows `books`,
19+
// which is owned by the current function
20+
21+
fn main() {
22+
let mut books = vec![1,2,3];
23+
spawn(|| books.push(4));
24+
//~^ ERROR E0373
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Test that we give a custom error (E0373) for the case where a
12+
// closure is escaping current frame, and offer a suggested code edit.
13+
// I refrained from including the precise message here, but the
14+
// original text as of the time of this writing is:
15+
//
16+
// closure may outlive the current function, but it borrows `books`,
17+
// which is owned by the current function
18+
19+
fn foo<'a>(x: &'a i32) -> Box<FnMut()+'a> {
20+
let mut books = vec![1,2,3];
21+
Box::new(|| books.push(4))
22+
//~^ ERROR E0373
23+
}
24+
25+
fn main() { }

src/test/compile-fail/issue-16747.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ trait Collection { fn len(&self) -> usize; }
1818

1919
struct List<'a, T: ListItem<'a>> {
2020
//~^ ERROR the parameter type `T` may not live long enough
21-
//~^^ NOTE ...so that the reference type `&'a [T]` does not outlive the data it points at
21+
//~| HELP consider adding an explicit lifetime bound
22+
//~| NOTE ...so that the reference type `&'a [T]` does not outlive the data it points at
2223
slice: &'a [T]
2324
}
24-
//~^ HELP consider adding an explicit lifetime bound
2525
impl<'a, T: ListItem<'a>> Collection for List<'a, T> {
2626
fn len(&self) -> usize {
2727
0

src/test/compile-fail/issue-4335.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn id<T>(t: T) -> T { t }
1515
fn f<'r, T>(v: &'r T) -> Box<FnMut() -> T + 'r> {
1616
// FIXME (#22405): Replace `Box::new` with `box` here when/if possible.
1717
id(Box::new(|| *v))
18-
//~^ ERROR `v` does not live long enough
18+
//~^ ERROR E0373
1919
//~| ERROR cannot move out of borrowed content
2020
}
2121

src/test/compile-fail/regions-nested-fns-2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn ignore<F>(_f: F) where F: for<'z> FnOnce(&'z isize) -> &'z isize {}
1313
fn nested() {
1414
let y = 3;
1515
ignore(
16-
|z| { //~ ERROR `y` does not live long enough
16+
|z| { //~ ERROR E0373
1717
if false { &y } else { z }
1818
});
1919
}

0 commit comments

Comments
 (0)