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

Rust tries to be too helpful with impl suggestions, sometimes being unhelpful #28894

Open
sgrif opened this issue Oct 7, 2015 · 29 comments
Open
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. D-confusing Diagnostics: Confusing error or lint that should be reworked. F-on_unimplemented Error messages that can be tackled with `#[rustc_on_unimplemented]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@sgrif
Copy link
Contributor

sgrif commented Oct 7, 2015

This code example is a simplified extraction of part of the hierarchy that I'm working on for https://github.com/sgrif/yaqb. It's still a bit contrived, but I think it really gets the point across about how unhelpful/nonsensical the resulting error message is.

The type structure goes like this: AsExpression represents a type which can be converted to an Expression which would return a given type. Expression represents... well, an expression. AsExpression gets a blanket impl for any type which implements Expression. Column represents a concrete column (which in the real code has other things associated to it). Ultimately the column has a type, and can be used as an expression of that type. Any type that implements Column gets a blanket impl for Expression.

In main, we're trying to compare a column representing an i32, to a str. This is incorrect, and should fail to compile. However, the error message we get out of this is:

error: the trait Column is not implemented for the type &str [E0277]

In the context of a SQL database, and what Column means, this is a nonsense error, and would not be helpful to the user in seeing why their code is failing to compile. Rust is just being super clever and realizing that the blanket impls would apply if we just implement that instead. I'd like to propose changing the error message to something with a bit more information, such as:

error: the trait AsExpression<i32> is not implemented for the type &str [E0277]

A blanket implementation of AsExpression, located at <file_path>:<line_number> could apply if Column were implemented for the type &str

@jdm jdm added the A-diagnostics Area: Messages for errors, warnings, and lints label Oct 7, 2015
@arielb1
Copy link
Contributor

arielb1 commented Oct 9, 2015

That was supposed to work.

sgrif added a commit to diesel-rs/diesel that referenced this issue Nov 24, 2015
I'm tired of seeing rustc recommend that I implement `Column` whenever
I'm having issues resolving `SelectableExpression`. It's making things
hard to debug, and I don't want to wait for
rust-lang/rust#28894 to be fixed.

The error behavior is awfully peculiar. I have no idea why this changed
causes Rust to not even try and compile the second version of the same
expression.
@sgrif
Copy link
Contributor Author

sgrif commented Jan 8, 2016

Anyone have any opinions on my recommendation on how to improve this, or other possibilities?

@sgrif
Copy link
Contributor Author

sgrif commented Jan 30, 2016

Is there anything I can do to get some attention on this? We're at the point where we're considering some sweeping/painful changes to our trait hierarchy to work around this, as there are cases that have been consistent sources of confusion in Diesel. (Our particular problem case, among others, is that rustc notices that we have impl<ST, T> AsExpression<ST> for T where T: Expression<SqlType=ST>, and never mentions AsExpression in the error message.)

I can't imagine that we have the only case where recommending the blanket impl without mentioning the actual trait it's trying to satisfy is problematic.

@jonas-schievink
Copy link
Contributor

I think the obligation forest provides the functionality needed to fix this properly: #30976

Minified example:

trait Expression {}

trait Column {}

impl<C: Column> Expression for C {}


fn assert_expression<T: Expression>() {}

fn main() {
    assert_expression::<()>();
}
<anon>:11:5: 11:28 error: the trait `Column` is not implemented for the type `()` [E0277]
<anon>:11     assert_expression::<()>();
              ^~~~~~~~~~~~~~~~~~~~~~~
<anon>:11:5: 11:28 help: see the detailed explanation for E0277
<anon>:11:5: 11:28 note: required by `assert_expression`
error: aborting due to previous error

@sgrif
Copy link
Contributor Author

sgrif commented Jan 31, 2016

It seems like your example demonstrates the problem, not that it's fixed? Expression isn't mentioned anywhere in that error message.

@jonas-schievink
Copy link
Contributor

Yes, it's just a minified version of your code above. Sorry for not being clear about that.

@sgrif
Copy link
Contributor Author

sgrif commented May 11, 2016

Had another user get bit by this today: diesel-rs/diesel#323

@arielb1
Copy link
Contributor

arielb1 commented May 11, 2016

This is fixed on 1.10/nightly:

error: the trait bound `(): Column` is not satisfied [--explain E0277]
  --> <anon>:11:5
11 |>     assert_expression::<()>();
   |>     ^^^^^^^^^^^^^^^^^^^^^^^
note: required because of the requirements on the impl of `Expression` for `()`
note: required by `assert_expression`

error: aborting due to previous error
playpen: application terminated with error code 101

@arielb1 arielb1 closed this as completed May 11, 2016
@sgrif
Copy link
Contributor Author

sgrif commented May 11, 2016

I don't think this issue is fixed. This at least mentions the trait name, which is an improvement, but the error message is still confusing to me. In the example that you gave, it makes it sound like Column is a supertrait of Expression to me.

The case in diesel-rs/diesel#323 is also giving incorrect information.

src/macros/insertable.rs:206:13: 207:32 error: the trait bound `i32: expression::Expression` is not satisfied [E0277]
src/macros/insertable.rs:206             AsExpression::<<$column as Expression>::SqlType>
                                         ^
src/macros/insertable.rs:206:13: 207:32 help: run `rustc --explain E0277` to see a detailed explanation
src/macros/insertable.rs:206:13: 207:32 note: required because of the requirements on the impl of `expression::Expression` for `&i32`
src/macros/insertable.rs:206:13: 207:32 note: required by `expression::AsExpression::as_expression`
src/macros/insertable.rs:206:13: 207:47 error: mismatched types [E0308]

&i32 does not implement Expression, and the type parameter to AsExpression isn't even mentioned.

@sgrif
Copy link
Contributor Author

sgrif commented May 11, 2016

It's still fairly trivial to get a case where the trait that's failing to apply isn't even mentioned. https://is.gd/DjS6Uk

@arielb1 I think there's still some work to do on this one, can we reopen this issue?

@arielb1
Copy link
Contributor

arielb1 commented May 11, 2016

That looks like a different bug. Are investigating

pub struct Text;

pub trait Expression {
    type SqlType;
}

pub trait AsExpression<ST> {
    type Expression: Expression<SqlType=ST>;

    fn as_expression(self) -> Self::Expression;
}

impl<T, ST> AsExpression<ST> for T where
    T: Expression<SqlType=ST>,
{
    type Expression = Self;

    fn as_expression(self) -> Self::Expression {
        self
    }
}

fn main() {
    AsExpression::as_expression(Text);
}

@arielb1 arielb1 reopened this May 11, 2016
@arielb1
Copy link
Contributor

arielb1 commented May 14, 2016

@sgrif

In your case, the reported-upon Text: Expression predicate is coming from your trait declaration, rather than from any impl. We should not register these bounds directly, but there is some room for improvement.

@arielb1
Copy link
Contributor

arielb1 commented May 14, 2016

I would like to refactor that code before we make any changes to it. cc @eddyb. (https://github.com/rust-lang/rust/blob/master/src/librustc_typeck/check/mod.rs#L4364)

@eddyb
Copy link
Member

eddyb commented May 15, 2016

@arielb1 I will... try not to touch any of that. Except if I do anything to Substs, I suppose.

Do you want to track the reason for those obligations more accurately?
Although the code looks like it should do the right thing in terms of ObligationCause, but maybe that's not enough. I don't have any strong opinions about any of that function, really, as long as it works.

@arielb1
Copy link
Contributor

arielb1 commented May 15, 2016

The problem is that we add obligations for the trait whose def-id refers to the method. If we would just add the FnSpace obligations + the Self: Trait predicate, we would get a cause that refers to the impl.

@eddyb
Copy link
Member

eddyb commented May 15, 2016

@arielb1 Ah, I agree then that a single predicate would be better for cause tracking (maybe even more efficient?).

@arielb1
Copy link
Contributor

arielb1 commented May 16, 2016

Do you have any plans for refactoring the above code, or can I try to hack on it myself?

@eddyb
Copy link
Member

eddyb commented May 16, 2016

@arielb1 Go ahead, don't mind me.

@steveklabnik steveklabnik removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Mar 9, 2017
@Mark-Simulacrum Mark-Simulacrum added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Jul 24, 2017
@behnam
Copy link
Contributor

behnam commented Aug 9, 2017

I have faced a similar problem when trait A is required, and there's blanket impl B for A available, resulting in the error message to say trait B is required, which is not accurate.

More details: https://users.rust-lang.org/t/unexpected-behaviors-of-trait-bounds/12286

@steveklabnik
Copy link
Member

Triage: other than the new format, the error message remains the same:

error[E0277]: the trait bound `&str: Column` is not satisfied
  --> src/main.rs:39:26
   |
39 |     let predicate = name.eq("Sean");
   |                          ^^ the trait `Column` is not implemented for `&str`
   |
   = note: required because of the requirements on the impl of `Expression` for `&str`
   = note: required because of the requirements on the impl of `AsExpression<std::string::String>` for `&str`

@estebank
Copy link
Contributor

For nightly users, libraries can leverage rustc_on_unimplemented to provide better errors in these cases.

@sgrif
Copy link
Contributor Author

sgrif commented May 21, 2019

#[rustc_on_unimplemented] doesn't really apply here, since Rust is showing a recommendation for the wrong trait, and won't show the custom message. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=963ede706e74095f9b99192b2ac0e5d7

@estebank
Copy link
Contributor

@sgrif that's because you need to give the on_unimplemented to trait Column:

#[rustc_on_unimplemented(
    on(
        _Self="&str",
        message="found &str but we want a `Column`",
        label="not on a `Column`",
        note="you should be fruxolizing your thingamathings"
    ),
    message="default message"
)]
trait Column {
    type SqlType;
}
error[E0277]: found &str but we want a `Column`
  --> src/main.rs:50:26
   |
50 |     let predicate = name.eq("Sean");
   |                          ^^ not on a `Column`
   |
   = help: the trait `Column` is not implemented for `&str`
   = note: you should be fruxolizing your thingamathings
   = note: required because of the requirements on the impl of `Expression` for `&str`
   = note: required because of the requirements on the impl of `AsExpression<std::string::String>` for `&str`

@sgrif
Copy link
Contributor Author

sgrif commented May 21, 2019 via email

@estebank
Copy link
Contributor

Could you show us the "fixed" code for the example provided?

I believe that I understand where you're coming from, as this is a pretty bad limitation rustc has, but I also think that the subset of problems that are expected to happen (like this one) can receive specific error messages that point people in the right direction, even though they need to be added to the "wrong" trait. I'm not saying that this ticket should be closed, as rustc should behave in a more reasonable manner, but rather that diesel can improve its user friendliness today by judicious use of on_unimplemented for common problems.

@sgrif
Copy link
Contributor Author

sgrif commented May 21, 2019 via email

@estebank estebank added the F-on_unimplemented Error messages that can be tackled with `#[rustc_on_unimplemented]` label Aug 4, 2019
@estebank
Copy link
Contributor

Current output, minor change to the span:

error[E0277]: the trait bound `&str: Column` is not satisfied
  --> file12.rs:39:29
   |
39 |     let predicate = name.eq("Sean");
   |                             ^^^^^^ the trait `Column` is not implemented for `&str`
   |
   = note: required because of the requirements on the impl of `Expression` for `&str`
   = note: required because of the requirements on the impl of `AsExpression<std::string::String>` for `&str`

@estebank estebank added D-confusing Diagnostics: Confusing error or lint that should be reworked. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 13, 2019
@estebank
Copy link
Contributor

estebank commented Apr 21, 2021

Current output, more context for the requirements, but no other changes:

error[E0277]: the trait bound `&str: Column` is not satisfied
  --> src/main.rs:42:29
   |
42 |     let predicate = name.eq("Sean");
   |                             ^^^^^^ the trait `Column` is not implemented for `&str`
   |
note: required because of the requirements on the impl of `Expression` for `&str`
  --> src/main.rs:31:17
   |
31 | impl<C: Column> Expression for C {
   |                 ^^^^^^^^^^     ^
note: required because of the requirements on the impl of `AsExpression<String>` for `&str`
  --> src/main.rs:19:21
   |
19 | impl<E: Expression> AsExpression<E::SqlType> for E {
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^     ^

Crazy idea: what if crates started adding comments next to places where spans are displayed, like 👀

error[E0277]: the trait bound `&str: Column` is not satisfied
  --> src/main.rs:43:29
   |
43 |     let predicate = name.eq("Sean");
   |                             ^^^^^^ the trait `Column` is not implemented for `&str`
   |
note: required because of the requirements on the impl of `Expression` for `&str`
  --> src/main.rs:29:1
   |
29 | Expression // Make sure that the type
   | ^^^^^^^^^^
30 | for        // of the value you're using
31 | C          // matches the table definition
   | ^
note: required because of the requirements on the impl of `AsExpression<String>` for `&str`
  --> src/main.rs:16:21
   |
16 | impl<E: Expression> AsExpression<E::SqlType> for E {
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^     ^

@estebank
Copy link
Contributor

Current output:

error[E0277]: the trait bound `&str: Column` is not satisfied
  --> src/main.rs:39:29
   |
39 |     let predicate = name.eq("Sean");
   |                          -- ^^^^^^ the trait `Column` is not implemented for `&str`
   |                          |
   |                          required by a bound introduced by this call
   |
   = help: the trait `Column` is implemented for `name`
note: required for `&str` to implement `Expression`
  --> src/main.rs:28:17
   |
28 | impl<C: Column> Expression for C {
   |         ------  ^^^^^^^^^^     ^
   |         |
   |         unsatisfied trait bound introduced here
note: required for `&str` to implement `AsExpression<String>`
  --> src/main.rs:16:21
   |
16 | impl<E: Expression> AsExpression<E::SqlType> for E {
   |         ----------  ^^^^^^^^^^^^^^^^^^^^^^^^     ^
   |         |
   |         unsatisfied trait bound introduced here
note: required by a bound in `Expression::eq`
  --> src/main.rs:4:14
   |
4  |     fn eq<T: AsExpression<Self::SqlType>>(self, other: T) {
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Expression::eq`

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. D-confusing Diagnostics: Confusing error or lint that should be reworked. F-on_unimplemented Error messages that can be tackled with `#[rustc_on_unimplemented]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

9 participants