Skip to content
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

Better closure error message #42443

Merged
merged 6 commits into from
Jun 8, 2017
Merged

Conversation

tommyip
Copy link
Contributor

@tommyip tommyip commented Jun 5, 2017

Use tracked data introduced in #42196 to provide a better closure
error message by showing why a closure implements FnOnce.

error[E0525]: expected a closure that implements the `Fn` trait, but
this closure only implements `FnOnce`
 --> $DIR/issue_26046.rs:4:19
  |
4 |       let closure = move || {
  |  ___________________^
5 | |         vec
6 | |     };
  | |_____^
  |
note: closure is `FnOnce` because it moves the variable `vec` out of
its environment
 --> $DIR/issue_26046.rs:5:9
  |
5 |         vec
  |         ^^^

error: aborting due to previous error(s)

Fixes #26046

r? @nikomatsakis
cc @doomrobo

tommyip added 2 commits June 5, 2017 12:01
Use tracked data introduced in rust-lang#42196 to provide a better closure
error message by showing why a closure implements `FnOnce`.

```
error[E0525]: expected a closure that implements the `Fn` trait, but
this closure only implements `FnOnce`
 --> $DIR/issue_26046.rs:4:19
  |
4 |       let closure = move || {
  |  ___________________^
5 | |         vec
6 | |     };
  | |_____^
  |
note: closure is `FnOnce` because it moves the variable `vec` out of
its environment
 --> $DIR/issue_26046.rs:5:9
  |
5 |         vec
  |         ^^^

error: aborting due to previous error(s)
```

Fixes rust-lang#26046
@tommyip tommyip force-pushed the better_closure_msg branch from c04d0a3 to 9cbb5f9 Compare June 5, 2017 11:02
@carols10cents carols10cents added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jun 5, 2017
};

if let Some(tables) = infer_tables {
if let Some(&(ty::ClosureKind::FnOnce, Some((span, name)))) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message is specific to FnOnce, but similar scenarios can arise in other situations. For example, we could highlight the x += 1 here and say something like "mutating this variable":

fn foo<F: Fn()>(f: F) { }
fn main() {
    let mut x = 0;
    foo(|| x += 1); // Error
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we match more cases here or make the error more generic?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I think it'd be better to match more cases, and keep the message fairly tailored -- there are really only two cases. The closure might be FnOnce (in which case we can talk about something being moved) or it might be FnMut (in which case we can talk about something being written to).

@tommyip
Copy link
Contributor Author

tommyip commented Jun 6, 2017

Updated

16 | | };
| |_____^
|
note: closure is `FnMut` because it mutates the variable `num` here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slick

"closure is `FnMut` because it mutates the \
variable `{}` here", name));
},
_ => {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor point -- in this arm, we emit neither the new note nor the old one. Seems like the old one might still be helpful?

Actually, maybe we should always emit the old note -- but instead of a note, just make it a span_label? Seems like it's useful information either way. In other words, the old note tells you "why it has to be Fn", and the new note tells you why it cannot be...

If you agree, maybe move the body of the else below out and above this if, and convert span_note to span_label (so that it prints within one code snippet)

Copy link
Contributor Author

@tommyip tommyip Jun 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I didn't think about that!

@tommyip
Copy link
Contributor Author

tommyip commented Jun 7, 2017

For some reason span_label is producing a trailing whitespace:

  --> src/test/ui/closure_context/issue-26046-fn-once.rs:14:19
   |
14 |       let closure = move || {
   |  ___________________^
15 | |         vec
16 | |     };
   | |_____^
17 | 
18 |       Box::new(closure)
   |       ----------------- the requirement to implement `Fn` derives from here
   |
note: closure is `FnOnce` because it moves the variable `vec` out of its environment
  --> src/test/ui/closure_context/issue-26046-fn-once.rs:15:9
   |
15 |         vec
   |         ^^^

error: aborting due to previous error(s)

Next to 17 |.
I checked my changes and the ui tests and couldn't find anything that would cause this:

diff --git a/src/librustc/traits/error_reporting.rs b/src/librustc/traits/error_reporting.rs
index 7375338..c8e99c0 100644
--- a/src/librustc/traits/error_reporting.rs
+++ b/src/librustc/traits/error_reporting.rs
@@ -648,6 +648,10 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
                             kind,
                             found_kind);
 
+                        err.span_label(
+                            obligation.cause.span,
+                            format!("the requirement to implement `{}` derives from here", kind));
+
                         let infer_tables = match self.tables {
                             InferTables::Interned(tables) =>
                                 Some(InferTablesRef::Interned(tables)),
@@ -656,6 +660,8 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
                             InferTables::Missing => None,
                         };
 
+                        // Additional context information explaining why the closure only implements
+                        // a particular trait.
                         if let Some(tables) = infer_tables {
                             match tables.closure_kinds.get(&node_id) {
                                 Some(&(ty::ClosureKind::FnOnce, Some((span, name)))) => {
@@ -670,11 +676,6 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
                                 },
                                 _ => {}
                             }
-                        } else {
-                            err.span_note(
-                                obligation.cause.span,
-                                &format!("the requirement to implement `{}` \
-                                          derives from here", kind));
                         }
 
                         err.emit();

@nikomatsakis
Copy link
Contributor

@tommyip perhaps we just always print a trailing space in that scenario? Is that really a problem?

@nikomatsakis
Copy link
Contributor

Er, I guess you didn't push the commits that implement that part yet?

@tommyip
Copy link
Contributor Author

tommyip commented Jun 7, 2017

I'll push them now

@tommyip
Copy link
Contributor Author

tommyip commented Jun 7, 2017

Pushed

@@ -6,6 +6,9 @@ error[E0525]: expected a closure that implements the `Fn` trait, but this closur
15 | | num += 1;
16 | | };
| |_____^
17 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @estebank -- @tommyip mentioned that there is a trailing space after this 17 | -- this doesn't bother me per se, but is that just how the diagnostics output works in general?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikomatsakis that space is to pad to compensate for the multiline markers. We can add a check for empty lines and avoid it.

@nikomatsakis
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Jun 8, 2017

📌 Commit 345b833 has been approved by nikomatsakis

@bors
Copy link
Contributor

bors commented Jun 8, 2017

⌛ Testing commit 345b833 with merge f09576c...

bors added a commit that referenced this pull request Jun 8, 2017
Better closure error message

Use tracked data introduced in #42196 to provide a better closure
error message by showing why a closure implements `FnOnce`.

```
error[E0525]: expected a closure that implements the `Fn` trait, but
this closure only implements `FnOnce`
 --> $DIR/issue_26046.rs:4:19
  |
4 |       let closure = move || {
  |  ___________________^
5 | |         vec
6 | |     };
  | |_____^
  |
note: closure is `FnOnce` because it moves the variable `vec` out of
its environment
 --> $DIR/issue_26046.rs:5:9
  |
5 |         vec
  |         ^^^

error: aborting due to previous error(s)
```

Fixes #26046

r? @nikomatsakis
cc @doomrobo
@bors
Copy link
Contributor

bors commented Jun 8, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: nikomatsakis
Pushing f09576c to master...

@bors bors merged commit 345b833 into rust-lang:master Jun 8, 2017
@tommyip tommyip deleted the better_closure_msg branch June 8, 2017 11:06
@nikomatsakis
Copy link
Contributor

this is really cool :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants