Skip to content

Detect unmet bound error caused by lack of perfect derives #143993

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

estebank
Copy link
Contributor

@estebank estebank commented Jul 15, 2025

When encountering an unmet bound E0599 error originating in a derived trait impl, provide additional context:

error[E0599]: the method `clone` exists for struct `S<X, X, Y>`, but its trait bounds were not satisfied
  --> $DIR/lack-of-perfect-derives.rs:27:16
   |
LL | struct S<T, K, V>(Arc<T>, Rc<K>, Arc<V>, ());
   | ----------------- method `clone` not found for this struct because it doesn't satisfy `S<X, X, Y>: Clone`
...
LL | struct X;
   | -------- doesn't satisfy `X: Clone`
...
LL |     let s2 = s.clone();
   |                ^^^^^ method cannot be called on `S<X, X, Y>` due to unsatisfied trait bounds
   |
note: trait bound `X: Clone` was not satisfied
  --> $DIR/lack-of-perfect-derives.rs:4:10
   |
LL | #[derive(Clone, PartialEq)]
   |          ^^^^^ unsatisfied trait bound introduced in this `derive` macro
LL | struct S<T, K, V>(Arc<T>, Rc<K>, Arc<V>, ());
   |          -  - implicit unsatisfied bound on this type parameter
   |          |
   |          implicit unsatisfied bound on this type parameter
   = note: `#[derive(Clone)]` introduces `where`-bounds requiring every type parameter to implement `Clone`, even when not strictly necessary
   = help: you can manually implement `Clone` with more targeted bounds: `impl<T, K, V> Clone for S<T, K, V> where Arc<T>: Clone, Rc<K>: Clone, Arc<V>: Clone { /* .. */ }`
help: consider annotating `X` with `#[derive(Clone)]`
   |
LL + #[derive(Clone)]
LL | struct X;
   |

We now add a note mentioning that derive introduces where-bounds on the type parameters.

If the derived bounds would be different under "perfect derives", meaning a relaxation of the impl bounds could make the impl apply, then we mention (an approximate of) the impl that can be manually written.

Fix #143714.

When encountering an unmet bound E0599 error originating in a derived trait impl, provide additional context:

```
error[E0599]: the method `clone` exists for struct `S<X, X, Y>`, but its trait bounds were not satisfied
  --> $DIR/lack-of-perfect-derives.rs:27:16
   |
LL | struct S<T, K, V>(Arc<T>, Rc<K>, Arc<V>, ());
   | ----------------- method `clone` not found for this struct because it doesn't satisfy `S<X, X, Y>: Clone`
...
LL | struct X;
   | -------- doesn't satisfy `X: Clone`
...
LL |     let s2 = s.clone();
   |                ^^^^^ method cannot be called on `S<X, X, Y>` due to unsatisfied trait bounds
   |
note: trait bound `X: Clone` was not satisfied
  --> $DIR/lack-of-perfect-derives.rs:4:10
   |
LL | #[derive(Clone, PartialEq)]
   |          ^^^^^ unsatisfied trait bound introduced in this `derive` macro
LL | struct S<T, K, V>(Arc<T>, Rc<K>, Arc<V>, ());
   |          -  - implicit unsatisfied bound on this type parameter
   |          |
   |          implicit unsatisfied bound on this type parameter
   = note: `#[derive(Clone)]` introduces `where`-bounds requiring every type parameter to implement `Clone`, even when not strictly necessary
   = help: you can manually implement `Clone` with more targeted bounds: `impl<T, K, V> Clone for S<T, K, V> where Arc<T>: Clone, Rc<K>: Clone, Arc<V>: Clone { /* .. */ }`
help: consider annotating `X` with `#[derive(Clone)]`
   |
LL + #[derive(Clone)]
LL | struct X;
   |
```

We now add a `note` mentioning that `derive` introduces `where`-bounds on the type parameters.

If the derived bounds would be different under "perfect derives", meaning a relaxation of the impl bounds could make the impl apply, then we mention (an approximate of) the impl that can be manually written.
@rustbot
Copy link
Collaborator

rustbot commented Jul 15, 2025

r? @lcnr

rustbot has assigned @lcnr.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 15, 2025
Comment on lines +1119 to +1125
})) if let (
ExpnKind::Macro(MacroKind::Derive, mac), _)
| (_, Some(ExpnKind::Macro(MacroKind::Derive, mac))
) = (
self_ty.span.ctxt().outer_expn_data().kind,
of_trait.as_ref().map(|t| t.path.span.ctxt().outer_expn_data().kind),
) =>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is done so that we can get the macro name from the Span of either the implemented trait or the type being implemented. I don't think we strictly need it to be like this, but I didn't try alternatives.

LL | struct Bar<T: Foo> {
| - implicit unsatisfied bound on this type parameter
= note: `#[derive(Clone)]` introduces `where`-bounds requiring every type parameter to implement `Clone`, even when not strictly necessary
= help: you can manually implement `Clone` with more targeted bounds: `impl<T> Clone for Bar<T> where T::X: Clone { /* .. */ }`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The correct version of this would be impl<T: Foo> Clone for Bar<T> where T::X: Clone { /* .. */ }, but that is a where bound on the impl. The naïve approach to dealing with predicates in the suggestion code isn't conductive to properly handling this case :-/

Comment on lines +1 to +18
error[E0369]: binary operation `==` cannot be applied to type `S<X, X, X>`
--> $DIR/lack-of-perfect-derives.rs:23:15
|
LL | let _ = s == s2;
| - ^^ -- S<X, X, X>
| |
| S<X, X, X>
|
note: an implementation of `PartialEq` might be missing for `X`
--> $DIR/lack-of-perfect-derives.rs:15:1
|
LL | struct X;
| ^^^^^^^^ must implement `PartialEq`
help: consider annotating `X` with `#[derive(PartialEq)]`
|
LL + #[derive(PartialEq)]
LL | struct X;
|
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This added test is unrelated to the codepath being taken here, but E0369 should eventually use part of the same logic that E0599 does, so that it can provide more suggestions than it does today.

Comment on lines +20 to +47
error[E0599]: the method `clone` exists for struct `S<X, X, Y>`, but its trait bounds were not satisfied
--> $DIR/lack-of-perfect-derives.rs:27:16
|
LL | struct S<T, K, V>(Arc<T>, Rc<K>, Arc<V>, ());
| ----------------- method `clone` not found for this struct because it doesn't satisfy `S<X, X, Y>: Clone`
...
LL | struct X;
| -------- doesn't satisfy `X: Clone`
...
LL | let s2 = s.clone();
| ^^^^^ method cannot be called on `S<X, X, Y>` due to unsatisfied trait bounds
|
note: trait bound `X: Clone` was not satisfied
--> $DIR/lack-of-perfect-derives.rs:4:10
|
LL | #[derive(Clone, PartialEq)]
| ^^^^^ unsatisfied trait bound introduced in this `derive` macro
LL | struct S<T, K, V>(Arc<T>, Rc<K>, Arc<V>, ());
| - - implicit unsatisfied bound on this type parameter
| |
| implicit unsatisfied bound on this type parameter
= note: `#[derive(Clone)]` introduces `where`-bounds requiring every type parameter to implement `Clone`, even when not strictly necessary
= help: you can manually implement `Clone` with more targeted bounds: `impl<T, K, V> Clone for S<T, K, V> where V: Clone { /* .. */ }`
help: consider annotating `X` with `#[derive(Clone)]`
|
LL + #[derive(Clone)]
LL | struct X;
|
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the case that "perfect derives" would fix, and main impetus behind this change. Note that the help still includes where V: Clone, because that obligation was met (because V ends up being Y, which is Clone. We could change this behavior slightly to not include that bound either, but ran out of time to look into that.

I would like to remove some of the output from this error, like the suggestion to make X cloneable, and maybe the secondary span label pointing at S, as that is already pointed at by the "unsatisfied trait bound X: Clone" note.

Comment on lines +91 to +102
note: trait bound `X: PartialEq` was not satisfied
--> $DIR/lack-of-perfect-derives.rs:4:17
|
LL | #[derive(Clone, PartialEq)]
| ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
LL | struct S<T, K, V>(Arc<T>, Rc<K>, Arc<V>, ());
| - - - implicit unsatisfied bound on this type parameter
| | |
| | implicit unsatisfied bound on this type parameter
| implicit unsatisfied bound on this type parameter
= note: `#[derive(PartialEq)]` introduces `where`-bounds requiring every type parameter to implement `PartialEq`, even when not strictly necessary
= help: you can manually implement `PartialEq` with more targeted bounds: `impl<T, K, V> PartialEq for S<T, K, V> where Arc<T>: PartialEq, Rc<K>: PartialEq, Arc<V>: PartialEq { /* .. */ }`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the opposite perfect derive case: instead of the impl having no additional bounds, the bounds added are all not directly on the type parameter but rather on the wrapper types.

Comment on lines +103 to +107
= note: the following trait bounds were not satisfied:
`S<X, X, X>: Iterator`
which is required by `&mut S<X, X, X>: Iterator`
note: the trait `Iterator` must be implemented
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pre-existing issue. We shouldn't be talking about Iterator at all for this case.

Comment on lines +126 to +135
note: trait bound `X: Clone` was not satisfied
--> $DIR/lack-of-perfect-derives.rs:8:10
|
LL | #[derive(Clone, PartialEq)]
| ^^^^^ unsatisfied trait bound introduced in this `derive` macro
LL | struct N<T, K, V>(Arc<T>, T, Rc<K>, K, Arc<V>, V, ());
| - - implicit unsatisfied bound on this type parameter
| |
| implicit unsatisfied bound on this type parameter
= note: `#[derive(Clone)]` introduces `where`-bounds requiring every type parameter to implement `Clone`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note the mixed use of Arc<T> and T. We don't suggest where Arc<T>: Clone, T: Clone, so in the end we suggest no impl.

@@ -1752,6 +1762,226 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err.emit()
}

fn extract_derive_bounds(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The new suggestion logic starting here isn't... the cleanest and would love it if anyone comes up with a neater alternative, but this is what I got to.

bounds
.entry(self_ty.to_string())
.or_default()
.insert("placeholder".to_string());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"placeholder" is so that we keep the space of types that can implement the trait without obligating the original type parameter to also implement the trait (like for Arc and unlike PartialEq).

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. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve error message around unexpected derive bounds
3 participants