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

Improve ? error messages per RFC 1859 #35946

Closed
steveklabnik opened this issue Aug 23, 2016 · 34 comments · Fixed by #71409
Closed

Improve ? error messages per RFC 1859 #35946

steveklabnik opened this issue Aug 23, 2016 · 34 comments · Fixed by #71409
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@steveklabnik
Copy link
Member

steveklabnik commented Aug 23, 2016

RFC 1859 laid out specific error messages for the ? operator, but currently they are not implemented (and instead the compiler's internal desugaring is revealed).

UPDATE: Many parts of this are done, but not all. Here is a post with examples that are not yet great.

UPDATE 2: to close this ticket we need to improve the output of a single case left to point at the return type to explain where usize is coming from.


Original text:

try! in main has long been an issue, to the point of trying to adjust the language to support it: rust-lang/rfcs#1176

I was trying out ? today, and hit this error:

error[E0308]: mismatched types
 --> src/main.rs:5:13
  |
5 |     let f = File::open("hello.txt")?;
  |             ^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result`
  |
  = note: expected type `()`
  = note:    found type `std::result::Result<_, _>`

That's the only line, inside of a main() function. Even though I know about this pitfall, and have been using Rust for a long time, I asked a question about it on IRC.

Can we print a better diagnostic here that points out it's the return type of the function that's looking for ()?

@steveklabnik steveklabnik added the A-diagnostics Area: Messages for errors, warnings, and lints label Aug 23, 2016
@durka
Copy link
Contributor

durka commented Aug 23, 2016

Now that it's part of the language, we can even print help: the ? operator can only be used in a function that returns a Result.

@steveklabnik
Copy link
Member Author

Yeah, that's exactly what I was hoping.

@nagisa
Copy link
Member

nagisa commented Aug 24, 2016

Now that it's part of the language, we can even print help: the ? operator can only be used in a function that returns a Result.

Carrier trait.

@durka
Copy link
Contributor

durka commented Aug 24, 2016

@nagisa can you elaborate? Do we have to wait on this kind of diagnostic until the Carrier stuff shakes out?

@nagisa
Copy link
Member

nagisa commented Aug 24, 2016

@durka I mean that such diagnostic will make no sense once we implement and accept the carrier trait. First, because then you’ll be able to use ? in function that returns anything implementing Carrier trait (not just Result), but also because it would be possible to implement carrier trait for something like () in case anybody wanted to do so in their standard library.

Implementing such special cased diagnostic now would 100% result in it being forgotten until somebody comes along and complains that diagnostic about Result makes no sense (like it is already a case with a bunch of such help messages).

@durka
Copy link
Contributor

durka commented Aug 24, 2016

Maybe we can just put a #[rustc_on_unimplemented] on Carrier? It would be neat if that worked, but that attribute has been sorely neglected of late. Regardless I hope that good diagnostics will be taken into account while designing the Carrier trait.

@carols10cents
Copy link
Member

As of rustc 1.13.0-nightly (eac41469d 2016-08-30), the error in this case has gotten less understandable, imo:

error[E0277]: the trait bound `(): std::ops::Carrier` is not satisfied
 --> src/main.rs:6:13
  |
6 |     let f = File::open("hello.txt")?;
  |             ^^^^^^^^^^^^^^^^^^^^^^^^ trait `(): std::ops::Carrier` not satisfied
  |
  = note: required by `std::ops::Carrier::from_error`

error: aborting due to previous error

This error message seems to be guiding me towards implementing Carrier on (), but what would be better is what @steveklabnik and @durka suggested: something that mentions the return type is (), and that mentions you can only use ? in a function that returns a Result.

@nagisa
Copy link
Member

nagisa commented Sep 1, 2016

This is what you can get with rustc_on_unimplemented:

error[E0277]: the trait bound `(): std::ops::Carrier` is not satisfied
 --> test.rs:4:5
  |
4 |     Ok::<()>(42)?;
  |     ^^^^^^^^^^^^^ trait `(): std::ops::Carrier` not satisfied
  |
  = note: the return type `()` cannot be used with the `?` operator
  = note: required by `std::ops::Carrier::from_error`

Bikeshed on wording.

@nikomatsakis
Copy link
Contributor

@nagisa

This is what you can get with rustc_on_unimplemented:

I am not sure if this is good enough. I've long been unsatisfied with rustc_on_implemented for a couple of reasons -- one of them is that the "better" message is not the main thing people see. The other is that the ability to decide when to give the better message is pretty limited: often people write a "better" message for one particular scenario, but the same error (i.e., an unsatisfied trait bound) can appear in other scenarios where that better message sounds wrong.

i.e., here, we talk about return types, but if I wrote some code like:

fn foo<T: Carrier>() { .. .}
foo::<()>() // error

that error message would make any sense to me.

These two things are of course related, since giving a "maybe wrong" message high prominence feels bad.

That said, in this particular case, since Carrier is unstable, we can probably hard-code more -- but it feels like we could do better still. We know that the ? desugars into some particular code. We can probably tag that code and give the resulting trait obligations a more specific "cause", so that we can produce a good error in exactly this situation.

Note that this is valuable even once Carrier gets stabilized.

carols10cents added a commit to rust-lang/book that referenced this issue Oct 12, 2016
Until rust-lang/rust#35946 is fixed, so that the error message is
clearer than not implementing the Carrier trait.
@durka
Copy link
Contributor

durka commented Oct 12, 2016

Here's what I have so far:

fn does_not_return_result() -> i32 {
    try!(Ok(42));
}
error[E0308]: mismatched types
  --> src/test/compile-fail/expected-result.rs:14:5
14 |     try!(Ok(42));
   |     ^^^^^^^^^^^^^ expected i32, found enum `std::result::Result`
   |
   = note: `try!` and `?` can only be used in a function that returns a `Result`
   = note: this error originates in a macro outside of the current crate

Need to inhibit that second note. Next step is to actually figure out whether the error came from try! or ?.

@nikomatsakis
Copy link
Contributor

@durka seems good so far!

@carols10cents
Copy link
Member

Hi @durka, what do you have so far where? I don't see a PR linked to this issue at all :-/

Is there anyone I could bother to get you the help you need to finish this?

I'd really love for this to get fixed before the new version of the book comes out, and for all the new rustaceans who are hitting this in the meantime <3 <3 <3

@durka
Copy link
Contributor

durka commented Dec 22, 2016

Hey @carols10cents, I don't really have anything working unfortunately. I'd like to pick it up again, I'll need some help though. I think that #38301 will change things anyway (@bluss why was that closed? is the design work that came out of ongoing somewhere?).

@bluss
Copy link
Member

bluss commented Dec 22, 2016

It may look like I did something significant but I didn't think so. This rust-lang/rfcs/issues/1718 is the main thread, and niko proposed another variant of the formulation (which I confirmed compiles and works in rustc).

It does change slightly for the better, it doesn't have a carrier trait anymore.

fn does_not_return_result() -> i32 {
    Ok(42)?
}

error[E0308]: mismatched types
 --> test3.rs:2:5
  |
2 |     Ok(42)?;
  |     ^^^^^^^ expected i32, found enum `std::result::Result`
  |
  = note: expected type `i32`
  = note:    found type `std::result::Result<_, _>`

error[E0308]: mismatched types
 --> test3.rs:1:36
  |
1 |   fn does_not_return_result() -> i32 {
  |  ____________________________________^ starting here...
2 | |     Ok(42)?;
3 | | }
  | |_^ ...ending here: expected i32, found ()
  |
  = note: expected type `i32`
  = note:    found type `()`
help: consider removing this semicolon:
 --> test3.rs:2:12
  |
2 |     Ok(42)?;
  |            ^

error: aborting due to 2 previous errors

Edited, without the semicolon is maybe better:

error[E0308]: mismatched types
 --> test3.rs:2:5
  |
2 |     Ok(42)?
  |     ^^^^^^^ expected i32, found enum `std::result::Result`
  |
  = note: expected type `i32`
  = note:    found type `std::result::Result<_, _>`

error: aborting due to previous error

@carols10cents
Copy link
Member

@bluss thanks! That definitely looks better :) I assume if you use ? in main (the most common thing new rustaceans do) that the error message looks like:

error[E0308]: mismatched types
 --> test3.rs:2:5
  |
2 |     Ok(42)?
  |     ^^^^^^^ expected (), found enum `std::result::Result`
  |
  = note: expected type `()`
  = note:    found type `std::result::Result<_, _>`

error: aborting due to previous error

With this implementation, would it still be possible to add this note?

   = note: `try!` and `?` can only be used in a function that returns a `Result`

@ChrisJefferson
Copy link
Contributor

I just wanted to mentio one more option, as a beginner rust user.

I would find it helpful if the error message mentioned something like function should return (), rather than just expected () -- to make clear why we are expecting a ().

@steveklabnik steveklabnik removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Mar 9, 2017
bors added a commit that referenced this issue Aug 18, 2017
Implement CompilerDesugaringKind enum

This is the first step outlined in #35946. I think that the variants of `CompilerDesugaringKind` should be changed, I didn't know what the official names for `...` and `<-` are.

I'm not to sure how tests for the compiler work, but I would imagine that tests should be added such that
`Symbol::intern(s) == CompilerDesugaringKind::from(s).as_symbol()` for valid `s`.
@nikomatsakis
Copy link
Contributor

@huntiep regarding your question from #43984:

How would I differentiate between the first two situations of the RFC?

I think the thing you need to do is to create a second desugaring code, maybe something like CompilerDesugaringKind::QuestionMarkReturn, and then use that span for this code here, basically the parts that correspond to the return. Then we can detect errors that arose due to the return (or break) part of the ? desugaring and give them the custom error message of "try cannot be used in a function that returns". We might even make a distinct code for QuestionMarkBreak so that we can tailor the error message to the case of catch { ... foo? ... } versus just foo?.

@arielb1
Copy link
Contributor

arielb1 commented Aug 29, 2017

@huntiep feel free to ping me on IRC.

@nikomatsakis
Copy link
Contributor

So, between @huntiep and @arielb1, we now have a lot of progress on this topic. See for example the try-operator-on-main test case (output).

Notably, we now say one of the following:

  • the ? operator can only be used in a function that returns Result (or another type that implements std::ops::Try)
  • the ? operator can only be applied to values that implement std::ops::Try

I think we can close this issue, right?

@nikomatsakis
Copy link
Contributor

Well, looking through the RFC, there are a few things not covered. These are the heading:

  • Suggesting a new return type:
    • The RFC writes, 'At this point, we could likely make a suggestion such as "consider changing the return type to Result<(), Box>".'
    • It's not clear to me that this is a good idea, but it might be, subject to the caveats in the RFC.
  • Errors cannot be interconverted -- probably a good idea.
  • Consider suggesting the use of catch -- I'm not sure how I feel about this, perhaps the extended error description is the right place to talk about it though. In any case, as catch is not stable, we likely won't want to do this (yet).

@nikomatsakis nikomatsakis added E-needs-mentor and removed E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. labels Sep 25, 2017
@nikomatsakis
Copy link
Contributor

@arielb1 mentioned in the impl Try for Option PR that he was planning on trying to cover the "Errors cannot be interconverted" case. @arielb1 -- as an alternative, if you feel like writing mentoring instructions, this is the place.

@arielb1
Copy link
Contributor

arielb1 commented Sep 26, 2017

I already opened #44755 for the mentoring instructions

steveklabnik added a commit to steveklabnik/rust that referenced this issue Dec 5, 2017
Fixes rust-lang#42725

or at least, this is the best we can really do. rust-lang#35946 is tracking
better errors already, so that should cover the other part of it.
frewsxcv added a commit to frewsxcv/rust that referenced this issue Dec 6, 2017
…avus

Mention the name of ? in Result's docs

Fixes rust-lang#42725

or at least, this is the best we can really do. rust-lang#35946 is tracking
better errors already, so that should cover the other part of it.
@estebank
Copy link
Contributor

estebank commented Feb 8, 2018

Current output:

error[E0277]: the `?` operator can only be used in a function that returns `Result` (or another type that implements `std::ops::Try`)
 --> src/main.rs:3:5
  |
3 |     std::fs::File::open("foo")?;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
  |
  = help: the trait `std::ops::Try` is not implemented for `()`
  = note: required by `std::ops::Try::from_error`

@nikomatsakis
Copy link
Contributor

@estebank what are the "errors cannot be interconverted case"? (See my earlier summary)

@estebank
Copy link
Contributor

estebank commented Feb 8, 2018

It's not great yet:

error[E0277]: the trait bound `usize: std::convert::From<std::io::Error>` is not satisfied
 --> src/main.rs:2:5
  |
2 |     std::fs::File::open("foo")?;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<std::io::Error>` is not implemented for `usize`
  |
  = help: the following implementations were found:
            <usize as std::convert::From<u8>>
  = note: required by `std::convert::From::from`

The case where ? is used in a method with default return type is slightly verbose but suggests setting a return type only when returning, not when the ? used on its own (and the suggestion doesn't take the ? return path into consideration for the suggested return type):

error[E0277]: the `?` operator can only be used in a function that returns `Result` (or another type that implements `std::ops::Try`)
 --> src/main.rs:2:5
  |
2 |     std::fs::File::open("foo")?;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
  |
  = help: the trait `std::ops::Try` is not implemented for `()`
  = note: required by `std::ops::Try::from_error`

error[E0308]: mismatched types
 --> src/main.rs:3:5
  |
3 |     Ok(())
  |     ^^^^^^ expected (), found enum `std::result::Result`
  |
  = note: expected type `()`
             found type `std::result::Result<(), _>`
help: try adding a semicolon
  |
3 |     Ok(());
  |           ^
help: try adding a return type
  |
1 | fn foo() -> std::result::Result<(), _> {
  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When a return type is specified, we don't get suggestions:

error[E0277]: the trait bound `std::option::NoneError: std::convert::From<std::io::Error>` is not satisfied
 --> src/main.rs:2:8
  |
2 |     Ok(std::fs::File::open("foo")?)
  |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<std::io::Error>` is not implemented for `std::option::NoneError`
  |
  = note: required by `std::convert::From::from`

error[E0308]: mismatched types
 --> src/main.rs:2:5
  |
1 | fn foo() -> Option<()> {
  |             ---------- expected `std::option::Option<()>` because of return type
2 |     Ok(std::fs::File::open("foo")?)
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::option::Option`, found enum `std::result::Result`
  |
  = note: expected type `std::option::Option<()>`
             found type `std::result::Result<std::fs::File, _>`

@nikomatsakis
Copy link
Contributor

OK, I updated the head comment to link to your post.

@estebank
Copy link
Contributor

We now point only at the ? when we can't cover the error type, but otherwise no changes from my last comment.

@estebank
Copy link
Contributor

estebank commented May 22, 2019

Of the three cases, the first one is now:

error[E0277]: `?` couldn't convert the error to `usize`
 --> src/main.rs:2:31
  |
2 |     std::fs::File::open("foo")?;
  |                               ^ the trait `std::convert::From<std::io::Error>` is not implemented for `usize`
  |
  = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
  = help: the following implementations were found:
            <usize as std::convert::From<bool>>
            <usize as std::convert::From<std::num::NonZeroUsize>>
            <usize as std::convert::From<u16>>
            <usize as std::convert::From<u8>>
  = note: required by `std::convert::From::from`

and the third one is

error[E0277]: `?` couldn't convert the error to `std::option::NoneError`
 --> src/main.rs:2:34
  |
2 |     Ok(std::fs::File::open("foo")?)
  |                                  ^ the trait `std::convert::From<std::io::Error>` is not implemented for `std::option::NoneError`
  |
  = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
  = note: required by `std::convert::From::from`

error[E0308]: mismatched types
 --> src/main.rs:2:5
  |
1 | fn foo() -> Option<()> {
  |             ---------- expected `std::option::Option<()>` because of return type
2 |     Ok(std::fs::File::open("foo")?)
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::option::Option`, found enum `std::result::Result`
  |
  = note: expected type `std::option::Option<()>`
             found type `std::result::Result<std::fs::File, _>`

@estebank
Copy link
Contributor

The first case is unchanged. Only improvement I can think of would be to point at usize in the return type Result<(), usize>.

The current output for the second case is:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
 --> src/main.rs:2:5
  |
1 | / fn foo() {
2 | |     std::fs::File::open("foo")?;
  | |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
3 | |     Ok(())
4 | | }
  | |_- this function should return `Result` or `Option` to accept `?`
  |
  = help: the trait `std::ops::Try` is not implemented for `()`
  = note: required by `std::ops::Try::from_error`

error[E0308]: mismatched types
 --> src/main.rs:3:5
  |
3 |     Ok(())
  |     ^^^^^^ expected `()`, found enum `std::result::Result`
  |
  = note: expected unit type `()`
                  found enum `std::result::Result<(), _>`
help: try adding a semicolon
  |
3 |     Ok(());
  |           ^
help: try adding a return type
  |
1 | fn foo() -> std::result::Result<(), _> {
  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

And for the third:

error[E0277]: `?` couldn't convert the error to `std::option::NoneError`
 --> src/main.rs:2:34
  |
2 |     Ok(std::fs::File::open("foo")?)
  |                                  ^ the trait `std::convert::From<std::io::Error>` is not implemented for `std::option::NoneError`
  |
  = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
  = note: required by `std::convert::From::from`
help: consider converting the `Result<T, _>` into an `Option<T>` using `Result::ok`
  |
2 |     Ok(std::fs::File::open("foo").ok()?)
  |                                  ^^^^^

error[E0308]: mismatched types
 --> src/main.rs:2:5
  |
1 | fn foo() -> Option<()> {
  |             ---------- expected `std::option::Option<()>` because of return type
2 |     Ok(std::fs::File::open("foo")?)
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::option::Option`, found enum `std::result::Result`
  |
  = note: expected enum `std::option::Option<()>`
             found enum `std::result::Result<std::fs::File, _>`

Note the suggestion to use ok().

Overall I feel quite happy with the current state and feel we can close this after pointing at the return type when having the implicit conversion failure.

Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue Apr 27, 2020
…-op, r=petrochenkov

Point at the return type on `.into()` failure caused by `?`

Fix rust-lang#35946.
@bors bors closed this as completed in be90f90 Apr 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.