On E0277 pointing at bound in derive, suggest manual impl

When encountering a bound coming from a derive macro, suggest manual impl of the trait.

Use the span for the specific param when adding bounds in builtin derive macros, so the diagnostic will point at them as well as the derive macro itself.

```
error[E0277]: can't compare `SomeNode` with `SomeNode`
  --> f29.rs:24:15
   |
24 |     accept_eq(&node);
   |     --------- ^^^^^ no implementation for `SomeNode == SomeNode`
   |     |
   |     required by a bound introduced by this call
   |
   = note: -Ztrack-diagnostics: created at compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs:279:39
   = help: the trait `PartialEq` is not implemented for `SomeNode`
note: required for `Id<SomeNode>` to implement `PartialEq`
  --> f29.rs:3:10
   |
 3 | #[derive(PartialEq, Eq)]
   |          ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
 4 | pub struct Id<T>(PhantomData<T>);
   |               -
   = help: consider manually implementing `PartialEq` to avoid undesired bounds
note: required by a bound in `accept_eq`
  --> f29.rs:15:23
   |
15 | fn accept_eq(_: &impl PartialEq) { }
   |                       ^^^^^^^^^ required by this bound in `accept_eq`
help: consider annotating `SomeNode` with `#[derive(PartialEq)]`
   |
13 + #[derive(PartialEq)]
14 | struct SomeNode();
   |
```
This commit is contained in:
Esteban Küber 2026-01-18 02:29:33 +00:00
parent 7b62d97abd
commit 679f38c125
14 changed files with 140 additions and 26 deletions

View file

@ -638,27 +638,27 @@ impl<'a> TraitDef<'a> {
GenericParamKind::Type { .. } => {
// Extra restrictions on the generics parameters to the
// type being derived upon.
let span = param.ident.span.with_ctxt(ctxt);
let bounds: Vec<_> = self
.additional_bounds
.iter()
.map(|p| {
cx.trait_bound(
p.to_path(cx, self.span, type_ident, generics),
self.is_const,
)
cx.trait_bound(p.to_path(cx, span, type_ident, generics), self.is_const)
})
.chain(
// Add a bound for the current trait.
self.skip_path_as_bound
.not()
.then(|| cx.trait_bound(trait_path.clone(), self.is_const)),
self.skip_path_as_bound.not().then(|| {
let mut trait_path = trait_path.clone();
trait_path.span = span;
cx.trait_bound(trait_path, self.is_const)
}),
)
.chain({
// Add a `Copy` bound if required.
if is_packed && self.needs_copy_as_bound_if_packed {
let p = deriving::path_std!(marker::Copy);
Some(cx.trait_bound(
p.to_path(cx, self.span, type_ident, generics),
p.to_path(cx, span, type_ident, generics),
self.is_const,
))
} else {
@ -671,7 +671,7 @@ impl<'a> TraitDef<'a> {
)
.collect();
cx.typaram(param.ident.span.with_ctxt(ctxt), param.ident, bounds, None)
cx.typaram(span, param.ident, bounds, None)
}
GenericParamKind::Const { ty, span, .. } => {
let const_nodefault_kind = GenericParamKind::Const {

View file

@ -1,6 +1,7 @@
// ignore-tidy-filelength
use std::borrow::Cow;
use std::fmt;
use std::ops::Not;
use rustc_abi::ExternAbi;
use rustc_ast::attr::AttributeExt;
@ -1012,10 +1013,14 @@ impl<'hir> Generics<'hir> {
span_for_parentheses.map_or_else(
|| {
// We include bounds that come from a `#[derive(_)]` but point at the user's code,
// as we use this method to get a span appropriate for suggestions.
// We include bounds that come from a `#[derive(_)]` but point at the user's
// code, as we use this method to get a span appropriate for suggestions.
let bs = bound.span();
bs.can_be_used_for_suggestions().then(|| (bs.shrink_to_hi(), None))
// We use `from_expansion` instead of `can_be_used_for_suggestions` because
// the trait bound from imperfect derives do point at the type parameter,
// but expanded to a where clause, so we want to ignore those. This is only
// true for derive intrinsics.
bs.from_expansion().not().then(|| (bs.shrink_to_hi(), None))
},
|span| Some((span.shrink_to_hi(), Some(span.shrink_to_lo()))),
)

View file

@ -526,12 +526,15 @@ pub fn suggest_constraining_type_params<'a>(
//
// fn foo<T>(t: T) { ... }
// - help: consider restricting this type parameter with `T: Foo`
suggestions.push((
param.span.shrink_to_hi(),
post,
format!(": {constraint}"),
SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
));
let span = param.span.shrink_to_hi();
if span.can_be_used_for_suggestions() {
suggestions.push((
span,
post,
format!(": {constraint}"),
SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
));
}
}
// FIXME: remove the suggestions that are from derive, as the span is not correct

View file

@ -3586,6 +3586,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
}
spans.push(self_ty.span);
let mut spans: MultiSpan = spans.into();
let mut derived = false;
if matches!(
self_ty.span.ctxt().outer_expn_data().kind,
ExpnKind::Macro(MacroKind::Derive, _)
@ -3593,6 +3594,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
of_trait.map(|t| t.trait_ref.path.span.ctxt().outer_expn_data().kind),
Some(ExpnKind::Macro(MacroKind::Derive, _))
) {
derived = true;
spans.push_span_label(
data.span,
"unsatisfied trait bound introduced in this `derive` macro",
@ -3621,6 +3623,12 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
}
}
err.span_note(spans, msg);
if derived {
err.help(format!(
"consider manually implementing `{trait_name}` to avoid undesired \
bounds",
));
}
point_at_assoc_type_restriction(
tcx,
err,

View file

@ -102,6 +102,7 @@ LL | #[derive(Debug, Copy, Clone)]
...
LL | Expr: Expression<SqlType=<Col::SqlType as IntoNullable>::Nullable>,
| ------------------------------------------------ unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Debug` to avoid undesired bounds
help: consider further restricting the associated type
|
LL | Expr: Expression<SqlType=<Col::SqlType as IntoNullable>::Nullable>, <Col as Expression>::SqlType: NotNull,
@ -146,6 +147,7 @@ LL | #[derive(Debug, Copy, Clone)]
...
LL | Expr: Expression<SqlType=<Col::SqlType as IntoNullable>::Nullable>,
| ------------------------------------------------ unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Copy` to avoid undesired bounds
help: consider further restricting the associated type
|
LL | Expr: Expression<SqlType=<Col::SqlType as IntoNullable>::Nullable>, <Col as Expression>::SqlType: NotNull,
@ -219,6 +221,7 @@ LL | #[derive(Debug, Copy, Clone)]
...
LL | Expr: Expression<SqlType=<Col::SqlType as IntoNullable>::Nullable>,
| ------------------------------------------------ unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Clone` to avoid undesired bounds
help: consider further restricting the associated type
|
LL | Expr: Expression<SqlType=<Col::SqlType as IntoNullable>::Nullable>, <Col as Expression>::SqlType: NotNull,

View file

@ -65,6 +65,7 @@ LL | #[derive(Debug, PartialEq, Eq, ConstParamTy)]
| ^^^^^
LL | struct Bar<T>(T);
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Debug` to avoid undesired bounds
= note: 2 redundant requirements hidden
= note: required for `&&'static Bar<(dyn Debug + 'static)>` to implement `Debug`
= note: required for the cast from `&&&'static Bar<(dyn Debug + 'static)>` to `&dyn Debug`
@ -96,7 +97,10 @@ note: required for `Bar<dyn Debug>` to implement `Eq`
--> $DIR/unsizing-wfcheck-issue-126272.rs:19:28
|
LL | #[derive(Debug, PartialEq, Eq, ConstParamTy)]
| ^^ unsatisfied trait bound introduced in this `derive` macro
| ^^
LL | struct Bar<T>(T);
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Eq` to avoid undesired bounds
= note: 1 redundant requirement hidden
= note: required for `&'static Bar<dyn Debug>` to implement `Eq`
note: required by a bound in `std::cmp::AssertParamIsEq`

View file

@ -8,7 +8,10 @@ note: required for `Foo<String>` to implement `Copy`
--> $DIR/trait-error.rs:1:10
|
LL | #[derive(Copy, Clone)]
| ^^^^ unsatisfied trait bound introduced in this `derive` macro
| ^^^^
LL | struct Foo<T>(T);
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Copy` to avoid undesired bounds
= note: the `Copy` trait is required because this value will be copied for each element of the array
help: create an inline `const` block
|

View file

@ -10,7 +10,10 @@ note: required for `B<C>` to implement `Copy`
--> $DIR/deriving-copyclone.rs:9:10
|
LL | #[derive(Copy, Clone)]
| ^^^^ unsatisfied trait bound introduced in this `derive` macro
| ^^^^
LL | struct B<T> {
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Copy` to avoid undesired bounds
note: required by a bound in `is_copy`
--> $DIR/deriving-copyclone.rs:18:15
|
@ -33,7 +36,10 @@ note: required for `B<C>` to implement `Clone`
--> $DIR/deriving-copyclone.rs:9:16
|
LL | #[derive(Copy, Clone)]
| ^^^^^ unsatisfied trait bound introduced in this `derive` macro
| ^^^^^
LL | struct B<T> {
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Clone` to avoid undesired bounds
note: required by a bound in `is_clone`
--> $DIR/deriving-copyclone.rs:19:16
|
@ -56,7 +62,10 @@ note: required for `B<D>` to implement `Copy`
--> $DIR/deriving-copyclone.rs:9:10
|
LL | #[derive(Copy, Clone)]
| ^^^^ unsatisfied trait bound introduced in this `derive` macro
| ^^^^
LL | struct B<T> {
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Copy` to avoid undesired bounds
note: required by a bound in `is_copy`
--> $DIR/deriving-copyclone.rs:18:15
|

View file

@ -32,7 +32,10 @@ note: required for `Fooy<T>` to implement `Copy`
--> $DIR/impl_bounds.rs:10:10
|
LL | #[derive(Copy, Clone)]
| ^^^^ unsatisfied trait bound introduced in this `derive` macro
| ^^^^
LL | struct Fooy<T>(T);
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Copy` to avoid undesired bounds
note: the requirement `Fooy<T>: Copy` appears on the `impl`'s associated type `C` but not on the corresponding trait's associated type
--> $DIR/impl_bounds.rs:6:10
|
@ -56,7 +59,10 @@ note: required for `Fooy<T>` to implement `Copy`
--> $DIR/impl_bounds.rs:10:10
|
LL | #[derive(Copy, Clone)]
| ^^^^ unsatisfied trait bound introduced in this `derive` macro
| ^^^^
LL | struct Fooy<T>(T);
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Copy` to avoid undesired bounds
note: the requirement `Fooy<T>: Copy` appears on the `impl`'s associated function `d` but not on the corresponding trait's associated function
--> $DIR/impl_bounds.rs:7:8
|

View file

@ -37,7 +37,11 @@ note: required for `PriorityQueue<T>` to implement `PartialOrd`
--> $DIR/issue-104884-trait-impl-sugg-err.rs:14:10
|
LL | #[derive(PartialOrd, AddImpl)]
| ^^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
| ^^^^^^^^^^
...
LL | struct PriorityQueue<T>(BinaryHeap<PriorityQueueEntry<T>>);
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `PartialOrd` to avoid undesired bounds
note: required by a bound in `Ord`
--> $SRC_DIR/core/src/cmp.rs:LL:COL

View file

@ -30,6 +30,7 @@ LL | #[derive(Debug, Copy, Clone)]
| ^^^^^
LL | pub struct Vector2<T: Debug + Copy + Clone> {
| ---- unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Debug` to avoid undesired bounds
= note: required for the cast from `&Vector2<K>` to `&dyn Debug`
help: consider further restricting type parameter `K` with trait `Copy`
|
@ -71,6 +72,7 @@ LL | #[derive(Debug, Copy, Clone)]
| ^^^^^
LL | pub struct Vector2<T: Debug + Copy + Clone> {
| ---- unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Clone` to avoid undesired bounds
help: consider further restricting type parameter `K` with trait `Copy`
|
LL | pub struct AABB<K: Debug + std::marker::Copy> {

View file

@ -65,6 +65,7 @@ LL | #[derive(Debug, Copy, Clone)]
| ^^^^^
LL | pub struct Vector2<T: Debug + Copy + Clone> {
| ---- unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Debug` to avoid undesired bounds
= note: required for the cast from `&Vector2<K>` to `&dyn Debug`
help: consider restricting type parameter `K` with trait `Copy`
|
@ -134,6 +135,7 @@ LL | #[derive(Debug, Copy, Clone)]
| ^^^^^
LL | pub struct Vector2<T: Debug + Copy + Clone> {
| ---- unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `Clone` to avoid undesired bounds
help: consider restricting type parameter `K` with trait `Copy`
|
LL | pub struct AABB<K: std::marker::Copy> {

View file

@ -0,0 +1,34 @@
// Second case reported in issue #108894.
use std::marker::PhantomData;
#[derive(PartialEq, Eq)]
pub struct Id<T>(PhantomData<T>);
// manual implementation which would break the usage of const patterns
// impl<T> PartialEq for Id<T> { fn eq(&self, _: &Id<T>) -> bool { true } }
// impl<T> Eq for Id<T> {}
// This derive is undesired but cannot be removed without
// breaking the usages below
// #[derive(PartialEq, Eq)]
struct SomeNode();
fn accept_eq(_: &impl PartialEq) { }
fn main() {
let node = Id::<SomeNode>(PhantomData);
// this will only work if
// - `Partial/Eq` is implemented manually, or
// - `SomeNode` also needlessly(?) implements `Partial/Eq`
accept_eq(&node); //~ ERROR can't compare `SomeNode` with `SomeNode`
const CONST_ID: Id::<SomeNode> = Id::<SomeNode>(PhantomData);
// this will work only when `Partial/Eq` is being derived
// otherwise: error: to use a constant of type `Id<SomeNode>` in a pattern,
// `Id<SomeNode>` must be annotated with `#[derive(PartialEq, Eq)]`
match node {
CONST_ID => {}
}
}

View file

@ -0,0 +1,31 @@
error[E0277]: can't compare `SomeNode` with `SomeNode`
--> $DIR/derive-implicit-bound.rs:25:15
|
LL | accept_eq(&node);
| --------- ^^^^^ no implementation for `SomeNode == SomeNode`
| |
| required by a bound introduced by this call
|
= help: the trait `PartialEq` is not implemented for `SomeNode`
note: required for `Id<SomeNode>` to implement `PartialEq`
--> $DIR/derive-implicit-bound.rs:5:10
|
LL | #[derive(PartialEq, Eq)]
| ^^^^^^^^^
LL | pub struct Id<T>(PhantomData<T>);
| - unsatisfied trait bound introduced in this `derive` macro
= help: consider manually implementing `PartialEq` to avoid undesired bounds
note: required by a bound in `accept_eq`
--> $DIR/derive-implicit-bound.rs:17:23
|
LL | fn accept_eq(_: &impl PartialEq) { }
| ^^^^^^^^^ required by this bound in `accept_eq`
help: consider annotating `SomeNode` with `#[derive(PartialEq)]`
|
LL + #[derive(PartialEq)]
LL | struct SomeNode();
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0277`.