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

Misleading error message originating from the fact that closure types are not inferred being polymorphic over lifetimes. (And related errors in need of improvement.) #71209

Open
steffahn opened this issue Apr 16, 2020 · 3 comments
Labels
A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-inference Area: Type inference D-papercut Diagnostics: An error or lint that needs small tweaks. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@steffahn
Copy link
Member

steffahn commented Apr 16, 2020

The following code produces an error message that doesn’t at all explain the actual problem.

fn foo() -> &'static str {
    let identity = |x| x;
    let result = identity("result!");
    let local_string: String = "local".into();
    let _ = identity(&local_string);
    
    result
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0515]: cannot return value referencing local variable `local_string`
 --> src/lib.rs:7:5
  |
5 |     let _ = identity(&local_string);
  |                      ------------- `local_string` is borrowed here
6 |     
7 |     result
  |     ^^^^^^ returns a value referencing data owned by the current function

error: aborting due to previous error

For more information about this error, try `rustc --explain E0515`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

In particular looking at this statement – “returns a value referencing data owned by the current function” – that’s just not what is going on here.

What really is going on here (as far as I can tell) is that identity is inferred as some closure implementing Fn(A) -> A for A to be determined, then the first call identity("result!") tells the type inference that A is &'a str for some 'a to be determined, and finally identity(&local_string) already knows to apply unsized coercion and typechecks nailing 'a down to the lifetime of local_string. This means result is an &'a str with that same lifetime.

A bit less confusing of an error message but still suboptimal IMO is what you get when trying to use a closure truly polymorphically:

fn foo() {
    let identity = |x| x;
    identity(1u8);
    identity(1u16);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/lib.rs:4:14
  |
4 |     identity(1u16);
  |              ^^^^ expected `u8`, found `u16`
  |
help: change the type of the numeric literal from `u16` to `u8`
  |
4 |     identity(1u8);
  |              ^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

I think this could be improved by a message explaining where the expected type u8 comes from.





Back to the original example.. in my opinion it would be nice if the example would not error at all. After all the following does work:

fn str_closure<F: Fn(&str) -> &str>(f: F) -> F { f }

fn foo() -> &'static str {
    let identity = str_closure(|x| x);
    let result = identity("result!");
    let local_string: String = "local".into();
    let _ = identity(&local_string);
    
    result
}

However, this again doesn’t work:

fn str_closure<F: Fn(&str) -> &str>(f: F) -> F { f }

fn foo() -> &'static str {
    let closure = |x| x;
    let identity = str_closure(closure);
    let result = identity("result!");
    let local_string: String = "local".into();
    let _ = identity(&local_string);
    
    result
}





Finally, on the topic of trying to get more polymorphism out of closures than they support, I ran into this error message (which I could move into a separate issue if you guys think that it’s completely unrelated):

fn f<A>() -> A { unimplemented!() }
fn foo() {
    let _ = f;
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0282]: type annotations needed for `fn() -> A {f::<A>}`
 --> src/lib.rs:3:13
  |
3 |     let _ = f;
  |         -   ^ cannot infer type for type parameter `A` declared on the function `f`
  |         |
  |         consider giving this pattern the explicit type `fn() -> A {f::<A>}`, where the type parameter `A` is specified

error: aborting due to previous error

For more information about this error, try `rustc --explain E0282`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

Which is just weird, because: What is this fn() -> A {f::<A>} syntax supposed to mean?

@rustbot modify labels to T-compiler, T-lang, A-diagnostics, D-incorrect, D-terse, D-papercut, C-bug, C-enhancement, A-closures, A-inference.

@rustbot rustbot added A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-inference Area: Type inference C-bug Category: This is a bug. C-enhancement Category: An issue proposing an enhancement or a PR with one. D-incorrect Diagnostics: A diagnostic that is giving misleading or incorrect information. D-papercut Diagnostics: An error or lint that needs small tweaks. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Apr 16, 2020
@estebank
Copy link
Contributor

estebank commented Apr 17, 2020

Regarding the last point, it is trying to suggest you to give _ a type that carries enough type information for A to be resolved. The syntax is incorrect because it didn't account for fn-pointers in that diagnostic.

estebank added a commit to estebank/rust that referenced this issue Apr 19, 2020
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue Apr 28, 2020
On `FnDef` type annotation suggestion, use fn-pointer output

Address the last point in rust-lang#71209.
@estebank
Copy link
Contributor

No change to the first case.

Current output for the second case:

error[E0308]: mismatched types
 --> src/lib.rs:4:14
  |
4 |     identity(1u16);
  |     -------- ^^^^ expected `u8`, found `u16`
  |     |
  |     arguments to this function are incorrect
  |
note: closure parameter defined here
 --> src/lib.rs:2:21
  |
2 |     let identity = |x| x;
  |                     ^
help: change the type of the numeric literal from `u16` to `u8`
  |
4 |     identity(1u8);
  |               ~~

The last case is

error[E0282]: type annotations needed
 --> src/lib.rs:3:13
  |
3 |     let _ = f;
  |             ^ cannot infer type of the type parameter `A` declared on the function `f`
  |
help: consider specifying the generic argument
  |
3 |     let _ = f::<A>;
  |              +++++

estebank added a commit to estebank/rust that referenced this issue Sep 28, 2023
…ffected inference

Mitigate part of  rust-lang#71209.

```
error[E0308]: mismatched types
  --> $DIR/unboxed-closures-type-mismatch.rs:30:18
   |
LL |         identity(1u16);
   |         -------- ^^^^ expected `u8`, found `u16`
   |         |
   |         arguments to this function are incorrect
   |
note: expected because the closure was earlier called with an argument of type `u8`
  --> $DIR/unboxed-closures-type-mismatch.rs:29:18
   |
LL |         identity(1u8);
   |         -------- ^^^ expected because this argument is of type `u8`
   |         |
   |         in this closure call
note: closure parameter defined here
  --> $DIR/unboxed-closures-type-mismatch.rs:28:25
   |
LL |         let identity = |x| x;
   |                         ^
help: change the type of the numeric literal from `u16` to `u8`
   |
LL |         identity(1u8);
   |                   ~~
   ```
estebank added a commit to estebank/rust that referenced this issue Sep 28, 2023
…ffected inference

Mitigate part of  rust-lang#71209.

```
error[E0308]: mismatched types
  --> $DIR/unboxed-closures-type-mismatch.rs:30:18
   |
LL |         identity(1u16);
   |         -------- ^^^^ expected `u8`, found `u16`
   |         |
   |         arguments to this function are incorrect
   |
note: expected because the closure was earlier called with an argument of type `u8`
  --> $DIR/unboxed-closures-type-mismatch.rs:29:18
   |
LL |         identity(1u8);
   |         -------- ^^^ expected because this argument is of type `u8`
   |         |
   |         in this closure call
note: closure parameter defined here
  --> $DIR/unboxed-closures-type-mismatch.rs:28:25
   |
LL |         let identity = |x| x;
   |                         ^
help: change the type of the numeric literal from `u16` to `u8`
   |
LL |         identity(1u8);
   |                   ~~
   ```
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this issue Oct 10, 2023
…n, r=petrochenkov

On type error of closure call argument, point at earlier calls that affected inference

Mitigate part of  rust-lang#71209.

When we encounter a type error on a specific argument of a closure call argument, where the closure's definition doesn't have a type specified, look for other calls of the closure to try and find the specific call that cased that argument to be inferred of the expected type.

```
error[E0308]: mismatched types
  --> $DIR/unboxed-closures-type-mismatch.rs:30:18
   |
LL |         identity(1u16);
   |         -------- ^^^^ expected `u8`, found `u16`
   |         |
   |         arguments to this function are incorrect
   |
note: expected because the closure was earlier called with an argument of type `u8`
  --> $DIR/unboxed-closures-type-mismatch.rs:29:18
   |
LL |         identity(1u8);
   |         -------- ^^^ expected because this argument is of type `u8`
   |         |
   |         in this closure call
note: closure parameter defined here
  --> $DIR/unboxed-closures-type-mismatch.rs:28:25
   |
LL |         let identity = |x| x;
   |                         ^
help: change the type of the numeric literal from `u16` to `u8`
   |
LL |         identity(1u8);
   |                   ~~
```
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Oct 10, 2023
Rollup merge of rust-lang#116250 - estebank:closure-arg-inference-span, r=petrochenkov

On type error of closure call argument, point at earlier calls that affected inference

Mitigate part of  rust-lang#71209.

When we encounter a type error on a specific argument of a closure call argument, where the closure's definition doesn't have a type specified, look for other calls of the closure to try and find the specific call that cased that argument to be inferred of the expected type.

```
error[E0308]: mismatched types
  --> $DIR/unboxed-closures-type-mismatch.rs:30:18
   |
LL |         identity(1u16);
   |         -------- ^^^^ expected `u8`, found `u16`
   |         |
   |         arguments to this function are incorrect
   |
note: expected because the closure was earlier called with an argument of type `u8`
  --> $DIR/unboxed-closures-type-mismatch.rs:29:18
   |
LL |         identity(1u8);
   |         -------- ^^^ expected because this argument is of type `u8`
   |         |
   |         in this closure call
note: closure parameter defined here
  --> $DIR/unboxed-closures-type-mismatch.rs:28:25
   |
LL |         let identity = |x| x;
   |                         ^
help: change the type of the numeric literal from `u16` to `u8`
   |
LL |         identity(1u8);
   |                   ~~
```
@estebank
Copy link
Contributor

The second case is now handled:

error[E0308]: mismatched types
 --> src/lib.rs:4:14
  |
4 |     identity(1u16);
  |     -------- ^^^^ expected `u8`, found `u16`
  |     |
  |     arguments to this function are incorrect
  |
note: expected because the closure was earlier called with an argument of type `u8`
 --> src/lib.rs:3:14
  |
3 |     identity(1u8);
  |     -------- ^^^ expected because this argument is of type `u8`
  |     |
  |     in this closure call
note: closure parameter defined here
 --> src/lib.rs:2:21
  |
2 |     let identity = |x| x;
  |                     ^
help: change the type of the numeric literal from `u16` to `u8`
  |
4 |     identity(1u8);
  |               ~~

And as pointed above the third one is as well.

I don't know how we might be able to address the first one at this time.

@estebank estebank removed D-incorrect Diagnostics: A diagnostic that is giving misleading or incorrect information. C-enhancement Category: An issue proposing an enhancement or a PR with one. C-bug Category: This is a bug. labels Oct 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-inference Area: Type inference D-papercut Diagnostics: An error or lint that needs small tweaks. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

3 participants