-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom error when moving arg outside of its closure #47144
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
14e8eed
to
1295c68
Compare
I find the phrasing confusing, especially "closure you can't escape" -- I am not in a closure! How about something like this:
I'm not even sure about the word "escape". Maybe too jargon-ish? |
After the second glass of wine, I certainly am.
It probably is, but wanted to push the PR out for exactly this feedback :) I like your suggested output, but I feel we could still further improve it. I'm just not sure how. |
We could do an ellipsis monstrosity:
|
I didn't want to do that because the closure could be
|
Yeah, it would require some kind of special case depending on ordering which isn't great. |
What about
It doesn't read as nicely, but it has the advantage of always making kind of sense, regardless of the code's formatting, and any tools that might not show the secondary spans still get a meaningful label. |
38d587a
to
0c5f4f6
Compare
Edit: DONE
There's an incorrectly handled case:
It should probably look this way:
|
62998a0
to
5831359
Compare
Hmm, I sort of suspected this would be the case. It might be worth investing some energy to see if we can do better, though I am also tempted to redirect that effort into the MIR borrowck errors, which go down a slightly different path. |
Oh, I guess you're saying you fixed it. Let me read what's there first. =) |
|
||
// #45983: when trying to assign the contents of an argument to a binding outside of a | ||
// closure, provide a specific message pointing this out. | ||
if let (&SubregionOrigin::BindingTypeIsNotValidAtDecl(ref external_span), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could should really go into the nice_region_error
module, I think, following the precedent we've established. There would be a file like nice_region_error/outlives_closure.rs
and try_report_nice_region_error
would invoke it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to its own module.
err.span_label(sup_sp, "cannot be moved outside of its closure"); | ||
if sup_sp == origin_sp { | ||
err.span_label(*external_span, | ||
"borrowed data cannot be moved into here..."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still find the word move
potentially misleading. I would prefer "stored".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
| | | ||
| ...because it cannot outlive this closure | ||
|
||
error: aborting due to previous error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a ui
test for the other case you mentioned? The one where you push into a vector that exists outside the closure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved existing test that shows the behavior to ui: https://github.com/rust-lang/rust/pull/47144/files#diff-e5107c6d2a404ecd34f782e9e9e8b20f
Ping from triage, @estebank! Do you think you'll be able to address some of the feedback here? |
When given the following code: ```rust fn give_any<F: for<'r> FnOnce(&'r ())>(f: F) { f(&()); } fn main() { let mut x = None; give_any(|y| x = Some(y)); } ``` provide a custom error: ``` error: borrowed data cannot be moved outside of its closure --> file.rs:7:27 | 6 | let mut x = None; | ----- binding declared outside of closure 7 | give_any(|y| x = Some(y)); | --- ^ cannot be assigned to binding outside of its closure | | | closure you can't escape ``` instead of the generic lifetime error: ``` error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements --> file.rs:7:27 | 7 | give_any(|y| x = Some(y)); | ^ | note: first, the lifetime cannot outlive the anonymous lifetime rust-lang#2 defined on the body at 7:14... --> file.rs:7:14 | 7 | give_any(|y| x = Some(y)); | ^^^^^^^^^^^^^^^ note: ...so that expression is assignable (expected &(), found &()) --> file.rs:7:27 | 7 | give_any(|y| x = Some(y)); | ^ note: but, the lifetime must be valid for the block suffix following statement 0 at 6:5... --> file.rs:6:5 | 6 | / let mut x = None; 7 | | give_any(|y| x = Some(y)); 8 | | } | |_^ note: ...so that variable is valid at time of its declaration --> file.rs:6:9 | 6 | let mut x = None; | ^^^^^ ```
Trigger new diagnostic in `compile-fail/regions-escape-bound-fn.rs` test, and not only in `compile-fail/regions-escape-bound-fn-2.rs`.
5831359
to
86a4c74
Compare
- Move specialized borrow checker diagnostic for bindings escaping its closure to its own module. - Move affected tests to `ui`.
86a4c74
to
1820da5
Compare
Addressed the review comments. |
| | | | | ||
| | | cannot be stored outside of its closure | ||
| | cannot infer an appropriate lifetime | ||
| borrowed data cannot outlive this closure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nikomatsakis I'm not entirely happy with the output on this case :-/
This triggers the case I originally neglected and was trying to fix...
Should we keep this PR on hold until I have time to look at how to differentiate between these two cases for a more appropriate output? In my mind, this case should completely ignore the lifetime issue for Some(_)
and point at the destination binding, like in the output of the original case for this PR:
error: borrowed data cannot be stored outside of its closure
--> $DIR/regions-escape-bound-fn.rs:18:27
|
17 | let mut x: Option<&isize> = None;
| - borrowed data cannot be stored into here...
18 | with_int(|y| x = Some(y));
| --- ^ cannot be stored outside of its closure
| |
| ...because it cannot outlive this closure
It should be possible to do so as the current diagnostic does point at x
's declaration:
error[E0495]: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements
--> src/main.rs:8:22
|
8 | with_int(|y| x = Some(y));
| ^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 8:14...
--> src/main.rs:8:14
|
8 | with_int(|y| x = Some(y));
| ^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:8:27
|
8 | with_int(|y| x = Some(y));
| ^
note: but, the lifetime must be valid for the block suffix following statement 0 at 7:5...
--> src/main.rs:7:5
|
7 | / let mut x: Option<&isize> = None;
8 | | with_int(|y| x = Some(y));
9 | | //~^ ERROR borrowed data cannot be stored outside of its closure
10| | }
| |_^
note: ...so that variable is valid at time of its declaration
--> src/main.rs:7:9
|
7 | let mut x: Option<&isize> = None;
| ^^^^^
Also, should I create a new error code for each of these cases, one shared between all of them or reuse the existing one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this output is way better than the original output. The main change I would do is not to print the "cannot infer an appropriate lifetime" label, which seems to add zero value. Saying that the data cannot escape the closure and indicating the closure is pretty awesome.
} else { | ||
err.span_label(*closure_span, | ||
"borrowed data cannot outlive this closure"); | ||
err.span_label(origin_sp, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should just drop this label. I don't really think it conveys useful information to the end-user. The origin_sp
is not especially meaningful -- it's basically a point where the region is relevant, but not necessarily a particularly good choice of point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel uneasy about removing the span completely because it's the original error span, and want to point to it in some way. Modified so that it occurs in fewer cases, but will keep it for the #7573 case. I'm planning to revisit that and the type mismatch cases at a later PR.
71e0e36
to
6f9ecaa
Compare
Ok, this feels like progress in any case. r=me once travis is happy. |
@bors r=nikomatsakis |
📌 Commit 6f9ecaa has been approved by |
⌛ Testing commit 6f9ecaa with merge eabb46a2351fab39802b4ad72bce00c5d83063f5... |
💥 Test timed out |
@bors retry |
Custom error when moving arg outside of its closure When given the following code: ```rust fn give_any<F: for<'r> FnOnce(&'r ())>(f: F) { f(&()); } fn main() { let mut x = None; give_any(|y| x = Some(y)); } ``` provide a custom error: ``` error: borrowed data cannot be moved outside of its closure --> file.rs:7:27 | 6 | let mut x = None; | ----- borrowed data cannot be moved into here... 7 | give_any(|y| x = Some(y)); | --- ^ cannot be moved outside of its closure | | | ...because it cannot outlive this closure ``` instead of the generic lifetime error: ``` error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements --> file.rs:7:27 | 7 | give_any(|y| x = Some(y)); | ^ | note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 7:14... --> file.rs:7:14 | 7 | give_any(|y| x = Some(y)); | ^^^^^^^^^^^^^^^ note: ...so that expression is assignable (expected &(), found &()) --> file.rs:7:27 | 7 | give_any(|y| x = Some(y)); | ^ note: but, the lifetime must be valid for the block suffix following statement 0 at 6:5... --> file.rs:6:5 | 6 | / let mut x = None; 7 | | give_any(|y| x = Some(y)); 8 | | } | |_^ note: ...so that variable is valid at time of its declaration --> file.rs:6:9 | 6 | let mut x = None; | ^^^^^ ``` Fix #45983.
☀️ Test successful - status-appveyor, status-travis |
When given the following code:
provide a custom error:
instead of the generic lifetime error:
Fix #45983.