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

Friendlier error message for closure argument type mismatch #44735

Merged
merged 1 commit into from
Sep 26, 2017

Conversation

tirr-c
Copy link
Contributor

@tirr-c tirr-c commented Sep 21, 2017

Rebased #42270.
Fixes #42143.


test.rs:

fn main() {
    foo(|_: i32, _: usize| ());
}

fn foo<F>(_: F) where F: Fn(&str, usize) {}

Before:

error[E0281]: type mismatch: `[[email protected]:2:9: 2:30]` implements the trait `std::ops::Fn<(i32, usize)>`, but the trait `for<'r> std::ops::Fn<(&'r str, usize)>` is required
 --> test.rs:2:5
  |
2 |     foo(|_: i32, _: usize| ());
  |     ^^^ --------------------- implements `std::ops::Fn<(i32, usize)>`
  |     |
  |     expected &str, found i32
  |     requires `for<'r> std::ops::Fn<(&'r str, usize)>`
  |
  = note: required by `foo`

After (early):

error[E0631]: type mismatch in closure arguments
 --> test.rs:2:5
  |
2 |     foo(|_: i32, _: usize| ());
  |     ^^^ --------------------- takes arguments of type `i32` and `usize`
  |     |
  |     expected arguments of type `&str` and `usize`
  |
  = note: required by `foo`

After (current):

error[E0631]: type mismatch in closure arguments
 --> test.rs:2:5
  |
2 |     foo(|_: i32, _: usize| ());
  |     ^^^ --------------------- found signature of `fn(i32, usize) -> _`
  |     |
  |     expected signature of `for<'r> fn(&'r str, usize) -> _`
  |
  = note: required by `foo`

Compiler output has been changed, and a few tests are failing. Help me writing/fixing tests!

r? @nikomatsakis

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @nikomatsakis (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@@ -730,40 +730,35 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
expected_found.found,
expected_trait_ty.is_closure())
} else if let &TypeError::Sorts(ref expected_found) = e {
let expected = if let ty::TyTuple(tys, _) = expected_found.expected.sty {
Copy link
Contributor

@arielb1 arielb1 Sep 21, 2017

Choose a reason for hiding this comment

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

You're still matching against the expected/found, which could lead to this bogusly triggering when one of the arguments is a tuple - when you have something like fn((u32, u32)): Fn((u32,)).

There's no reason to do so - you have the trait-refs in expected_trait_ref and actual_trait_ref.

You can get the argument length of each argument in this way:

// skipping the binder is ok because we only care if this is a tuple
let arg_len = match X_trait_ref.skip_binder().substs.type_at(1) {
    ty::TyTuple(ref tys) => Some(tys.len()),
    _ => None
};

Copy link
Contributor Author

@tirr-c tirr-c left a comment

Choose a reason for hiding this comment

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

I have a few questions. I've contributed to Rust a little, but still not sure how to write compiler code...

ty::TyTuple(args, _) => {
let len = args.len();
if len == 0 {
return String::from("no arguments");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this code unreachable? I mean, will report_closure_arg_mismatch be called even when one of the Fn traits has zero arguments?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why aren't you calling report_type_argument_mismatch?

Copy link
Contributor

Choose a reason for hiding this comment

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

ok I see

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you won't see this error message - no arguments, no place for a type error.

if len == 1 { "" } else { "s" },
type_list_string)
},
_ => panic!(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it fine to use standard panic macro like panic! or unreachable! to signal compiler bugs? (panic! here is a mistake, I should have used unreachable! here)

Copy link
Contributor

Choose a reason for hiding this comment

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

An unreachable!() is ok-ish for local things, but it is recommended to use bug! or span_bug!(span, "error") - span_bug also displays the span of the error, which makes for easier debugging.

move |trait_ref| build_args_type_string(span, trait_ref)
);
err.span_label(span, format!("expected {}", found_arg_list.skip_binder()));
if let Some(sp) = found_span {
Copy link
Contributor

Choose a reason for hiding this comment

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

You should display the "takes" even when you don't have a span - aka fn pointers:

fn foo(f: fn((u32, u32))) {
    a.iter().map(f)
}

Copy link
Contributor

@arielb1 arielb1 Sep 21, 2017

Choose a reason for hiding this comment

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

I think you should add a test for each case with fn pointer - arg count mismatch, same arg count but type-error, higher-ranked error, F: Fn<u32> bad bound.

Copy link
Contributor

Choose a reason for hiding this comment

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

Higher-ranked test for fn pointers:

fn foo<F: Fn(*mut &u32)>(_f: F) {}
fn bar<'a>(f: fn(*mut &'a u32)) { foo(f); }

Higher-ranked test for closures: src/test/ui/mismatched_types/closure-mismatch.rs (already exists)

Just check that the error message is sane.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can I use note here?

Copy link
Contributor

Choose a reason for hiding this comment

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

With respect to the span specifically, probably just something like this would be fine:

let found_span = found_span.unwrap_or(span);

i.e., if we don't have a better span, display both messages at the same place.

@nikomatsakis
Copy link
Contributor

@tirr-c this looks awesome btw!

@tirr-c
Copy link
Contributor Author

tirr-c commented Sep 21, 2017

Tried @arielb1's test code, and I get two errors.

#![feature(unboxed_closures)]

fn main() {}
fn foo<F: Fn(*mut &u32)>(_f: F) {}
fn bar<'a>(f: fn(*mut &'a u32)) { foo(f); }

(ignore feature line there)

error[E0631]: type mismatch in closure arguments
 --> test.rs:5:35
  |
5 | fn bar<'a>(f: fn(*mut &'a u32)) { foo(f); }
  |                                   ^^^
  |                                   |
  |                                   expected argument of type `*mut &u32`
  |                                   takes argument of type `*mut &'a u32`
  |
  = note: required by `foo`

error[E0271]: type mismatch resolving `for<'r> <fn(*mut &'a u32) as std::ops::FnOnce<(*mut &'r u32,)>>::Output == ()`
 --> test.rs:5:35
  |
5 | fn bar<'a>(f: fn(*mut &'a u32)) { foo(f); }
  |                                   ^^^ expected bound lifetime parameter, found concrete lifetime
  |
  = note: required by `foo`

error: aborting due to 2 previous errors

Is this okay?

@arielb1
Copy link
Contributor

arielb1 commented Sep 21, 2017

Is this okay?

Your error message says "closure" for the function pointer. You should make it say "function" (you can use expected_trait_ty.is_closure() to check whether it's a function.

@arielb1
Copy link
Contributor

arielb1 commented Sep 21, 2017

Other than that the error messages look sane. Just be sure to add a UI test so I can see them before giving the r+.

Otherwise LGTM.

@tirr-c
Copy link
Contributor Author

tirr-c commented Sep 21, 2017

I've removed the test mismatched_types/E0281 because most of the E0281 errors are replaced by E0631 which is added in this PR, and mismatched_types/closure-arg-type-mismatch contains tests for both E0281 and E0631.

@@ -1,25 +0,0 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
Copy link
Contributor

Choose a reason for hiding this comment

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

If E0281 still exists, please write a test that reproduces it. That's the Fn<usize> test.

@@ -7,14 +7,13 @@ error[E0271]: type mismatch resolving `for<'r> <[closure@$DIR/closure-mismatch.r
= note: required because of the requirements on the impl of `Foo` for `[closure@$DIR/closure-mismatch.rs:18:9: 18:15]`
= note: required by `baz`

error[E0281]: type mismatch: `[closure@$DIR/closure-mismatch.rs:18:9: 18:15]` implements the trait `std::ops::Fn<(_,)>`, but the trait `for<'r> std::ops::Fn<(&'r (),)>` is required
error[E0631]: type mismatch in closure arguments
Copy link
Contributor

Choose a reason for hiding this comment

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

This erases the binder. I don't like this error message.

Copy link
Contributor

Choose a reason for hiding this comment

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

@arielb1 the expected and found signatures are in the labels now, I'm not sure if that was the case when you left this comment.

@arielb1
Copy link
Contributor

arielb1 commented Sep 21, 2017

Please add the test for E0281 back - all error codes are supposed to have a test for them, and you can similarly add a test for E0631 with the name E0631.rs. We also need to think of how to handle closure-mismatch.

@alexcrichton alexcrichton added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Sep 21, 2017
@arielb1
Copy link
Contributor

arielb1 commented Sep 21, 2017

[01:05:30] failures:
[01:05:30] 
[01:05:30] ---- /checkout/obj/build/x86_64-unknown-linux-gnu/test/error-index.md - Rust_Compiler_Error_Index (line 4928) stdout ----
[01:05:30] 	error[E0631]: type mismatch in closure arguments
[01:05:30]  --> /checkout/obj/build/x86_64-unknown-linux-gnu/test/error-index.md:7:5
[01:05:30]   |
[01:05:30] 7 |     foo(|y: String| { });
[01:05:30]   |     ^^^ --------------- takes argument of type `std::string::String`
[01:05:30]   |     |
[01:05:30]   |     expected argument of type `usize`
[01:05:30]   |
[01:05:30]   = note: required by `foo`
[01:05:30] 
[01:05:30] thread 'rustc' panicked at 'Some expected error codes were not found: ["E0281"]', /checkout/src/librustdoc/test.rs:286:8
[01:05:30] 
[01:05:30] 
[01:05:30] failures:
[01:05:30]     /checkout/obj/build/x86_64-unknown-linux-gnu/test/error-index.md - Rust_Compiler_Error_Index (line 4928)
[01:05:30] 
[01:05:30] test result: FAILED. 659 passed; 1 failed; 19 ignored; 0 measured; 0 filtered out
[01:05:30] 
[01:05:30] 

there's another doctest for E0281 in

E0281: r##"
, you need to fix it

@nikomatsakis
Copy link
Contributor

r? @arielb1

@tirr-c
Copy link
Contributor Author

tirr-c commented Sep 21, 2017

Should pass the test, but the documentation needs to be rewritten now I think...

@arielb1
Copy link
Contributor

arielb1 commented Sep 21, 2017

now you just need to use fn in the error messages.

@tirr-c
Copy link
Contributor Author

tirr-c commented Sep 22, 2017

error[E0631]: type mismatch in closure arguments
  --> src/test/ui/mismatched_types/closure-mismatch.rs:18:5
   |
18 |     baz(|_| ());
   |     ^^^ ------ found signature of fn(_) -> _
   |     |
   |     expected signature of for<'r> fn(&'r ()) -> _
   |
   = note: required because of the requirements on the impl of `Foo` for `[closure@src/test/ui/mismatched_types/closure-mismatch.rs:18:9: 18:15]`
   = note: required by `baz`

@tirr-c
Copy link
Contributor Author

tirr-c commented Sep 22, 2017

Error message from the test for Fn<usize> became worse.

Before:

error[E0593]: closure takes 0 arguments but 1 argument is required
  --> src/test/ui/mismatched_types/closure-arg-type-mismatch.rs:26:5
   |
26 |     bar(|| {});
   |     ^^^ ----- takes 0 arguments
   |     |
   |     expected closure that takes 1 argument
   |
   = note: required by `bar`

After:

error[E0281]: type mismatch: `[closure@src/test/ui/mismatched_types/closure-arg-type-mismatch.rs:26:9: 26:14]` implements the trait `std::ops::Fn<()>`, but the trait `std::ops::Fn<usize>` is required
  --> src/test/ui/mismatched_types/closure-arg-type-mismatch.rs:26:5
   |
26 |     bar(|| {});
   |     ^^^ ----- implements `std::ops::Fn<()>`
   |     |
   |     expected usize, found ()
   |     requires `std::ops::Fn<usize>`
   |
   = note: required by `bar`

@tirr-c
Copy link
Contributor Author

tirr-c commented Sep 22, 2017

error[E0631]: type mismatch in closure arguments
  --> E0631.rs:17:5
   |
17 |     foo(|_: isize| {});
   |     ^^^ ------------- found signature of fn(isize) -> _
   |     |
   |     expected signature of fn(usize) -> _
   |
   = note: required by `foo`

error[E0631]: type mismatch in closure arguments
  --> E0631.rs:18:5
   |
18 |     bar(|_: isize| {});
   |     ^^^ ------------- found signature of fn(isize) -> _
   |     |
   |     expected signature of fn(usize) -> _
   |
   = note: required by `bar`

E0281, the original error code, is totally replaced by E0631. I left E0281 in the error code index, though.

@bors
Copy link
Contributor

bors commented Sep 22, 2017

☔ The latest upstream changes (presumably #44691) made this pull request unmergeable. Please resolve the merge conflicts.

--> $DIR/issue-36053-2.rs:17:32
|
17 | once::<&str>("str").fuse().filter(|a: &str| true).count();
| ^^^^^^ -------------- implements `for<'r> std::ops::FnMut<(&'r str,)>`
| ^^^^^^ -------------- found signature of for<'r> fn(&'r str) -> _
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: we should enclose the signature in backticks:

 +   |                                ^^^^^^ -------------- found signature of `for<'r> fn(&'r str) -> _`

Fixes rust-lang#42143.
E0281 is totally replaced by E0631. UI tests are updated accordingly.
@arielb1
Copy link
Contributor

arielb1 commented Sep 24, 2017

That's cool.

@bors r+

@bors
Copy link
Contributor

bors commented Sep 24, 2017

📌 Commit 1bfbfb2 has been approved by arielb1

@bors
Copy link
Contributor

bors commented Sep 26, 2017

⌛ Testing commit 1bfbfb2 with merge 4b8bf39...

bors added a commit that referenced this pull request Sep 26, 2017
Friendlier error message for closure argument type mismatch

Rebased #42270.
Fixes #42143.

---

`test.rs`:

```rust
fn main() {
    foo(|_: i32, _: usize| ());
}

fn foo<F>(_: F) where F: Fn(&str, usize) {}
```

Before:

```
error[E0281]: type mismatch: `[[email protected]:2:9: 2:30]` implements the trait `std::ops::Fn<(i32, usize)>`, but the trait `for<'r> std::ops::Fn<(&'r str, usize)>` is required
 --> test.rs:2:5
  |
2 |     foo(|_: i32, _: usize| ());
  |     ^^^ --------------------- implements `std::ops::Fn<(i32, usize)>`
  |     |
  |     expected &str, found i32
  |     requires `for<'r> std::ops::Fn<(&'r str, usize)>`
  |
  = note: required by `foo`
```

After (early):

```
error[E0631]: type mismatch in closure arguments
 --> test.rs:2:5
  |
2 |     foo(|_: i32, _: usize| ());
  |     ^^^ --------------------- takes arguments of type `i32` and `usize`
  |     |
  |     expected arguments of type `&str` and `usize`
  |
  = note: required by `foo`
```

After (current):

```
error[E0631]: type mismatch in closure arguments
 --> test.rs:2:5
  |
2 |     foo(|_: i32, _: usize| ());
  |     ^^^ --------------------- found signature of `fn(i32, usize) -> _`
  |     |
  |     expected signature of `for<'r> fn(&'r str, usize) -> _`
  |
  = note: required by `foo`
```

~~Compiler output has been changed, and a few tests are failing. Help me writing/fixing tests!~~

r? @nikomatsakis
@bors
Copy link
Contributor

bors commented Sep 26, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: arielb1
Pushing 4b8bf39 to master...

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.

Wrong error message with closures on nightly
7 participants