diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 94133357a3eb5..427447e481525 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -1363,7 +1363,111 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "the following trait bounds were not satisfied:\n{bound_list}" )); } - suggested_derive = self.suggest_derive(&mut err, unsatisfied_predicates); + + let mut suggest_derive = true; + for (pred, _, cause) in unsatisfied_predicates { + let Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred))) = + pred.kind().no_bound_vars() + else { + continue; + }; + let lang = tcx.lang_items(); + if ![ + lang.copy_trait(), + lang.clone_trait(), + tcx.get_diagnostic_item(sym::Debug), + tcx.get_diagnostic_item(sym::PartialEq), + tcx.get_diagnostic_item(sym::Default), + ] + .contains(&Some(trait_pred.def_id())) + { + // We restrict ourselves only to built-in `derive`s. + continue; + } + let (adt, params) = match trait_pred.self_ty().kind() { + ty::Adt(adt, params) if adt.did().is_local() => (*adt, params), + _ => continue, + }; + if tcx + .all_impls(trait_pred.def_id()) + .filter_map(|imp_did| tcx.impl_trait_header(imp_did).map(|h| (imp_did, h))) + .filter(|(did, header)| { + let imp = header.trait_ref.instantiate_identity(); + let impl_adt = match imp.self_ty().ty_adt_def() { + Some(impl_adt) if adt.did().is_local() => impl_adt, + _ => return false, + }; + header.polarity == ty::ImplPolarity::Positive + && impl_adt == adt + && tcx.is_automatically_derived(*did) + }) + .count() + == 1 + { + // We now know that for this predicate, there *was* a `derive(Trait)` for + // the trait at hand, so we don't want to suggest writing that again. + for param in ¶ms[..] { + // Look at the type parameters for the currently obligated type to see + // if a restriciton of `TypeParam: Trait` would help. If the + // instantiated type param is not a type param but instead an actual + // type, see if we can suggest `derive(Trait)` on *that* type. + // See `tests/ui/suggestions/f1000.rs` + let Some(ty) = param.as_type() else { + continue; + }; + match ty.kind() { + ty::Adt(adt, _) if adt.did().is_local() => { + // The type param at hand is a local type, try to suggest + // `derive(Trait)`. + let trait_ref = + ty::TraitRef::identity(tcx, trait_pred.trait_ref.def_id) + .with_self_ty(tcx, ty); + let trait_pred = ty::Binder::dummy(ty::TraitPredicate { + trait_ref, + polarity: ty::PredicatePolarity::Positive, + }); + suggested_derive = self.suggest_derive( + &mut err, + &[( + <_ as ty::UpcastFrom<_, _>>::upcast_from( + trait_pred, self.tcx, + ), + None, + cause.clone(), + )], + ); + } + ty::Param(_) => { + // It was a type param. See if it corresponds to the current + // `fn` and suggest `T: Trait`. + if let Some(obligation) = cause { + let trait_ref = ty::TraitRef::new( + tcx, + trait_pred.trait_ref.def_id, + [ty], + ); + let trait_pred = ty::Binder::dummy(ty::TraitPredicate { + trait_ref, + polarity: ty::PredicatePolarity::Positive, + }); + suggested_derive = + self.err_ctxt().suggest_restricting_param_bound( + &mut err, + trait_pred, + None, + obligation.body_id, + ); + } + } + _ => {} + } + } + suggest_derive = false + } + } + if suggest_derive { + suggested_derive = self.suggest_derive(&mut err, unsatisfied_predicates); + } unsatisfied_bounds = true; } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 0d15ef55e24e7..150336badef6a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -242,9 +242,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, associated_ty: Option<(&'static str, Ty<'tcx>)>, mut body_id: LocalDefId, - ) { + ) -> bool { if trait_pred.skip_binder().polarity != ty::PredicatePolarity::Positive { - return; + return false; } let trait_pred = self.resolve_numeric_literals_with_default(trait_pred); @@ -279,7 +279,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { trait_pred, Some((ident, bounds)), ); - return; + return true; } hir::Node::TraitItem(hir::TraitItem { @@ -293,7 +293,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { self.tcx, body_id, generics, "`Self`", err, None, projection, trait_pred, None, ); - return; + return true; } hir::Node::TraitItem(hir::TraitItem { @@ -321,7 +321,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { trait_pred, None, ); - return; + return true; } hir::Node::Item(hir::Item { kind: @@ -341,7 +341,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { trait_pred, None, ); - return; + return true; } hir::Node::Item(hir::Item { @@ -374,7 +374,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .iter() .all(|g| g.is_suggestable(self.tcx, false)) { - return; + return false; } // Missing generic type parameter bound. let param_name = self_ty.to_string(); @@ -406,7 +406,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { Some(trait_pred.def_id()), None, ) { - return; + return true; } } @@ -432,10 +432,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { trait_pred, associated_ty, ) { - return; + return true; } } - hir::Node::Crate(..) => return, + hir::Node::Crate(..) => return false, _ => {} } diff --git a/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.fixed b/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.fixed new file mode 100644 index 0000000000000..00339a1de6d08 --- /dev/null +++ b/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.fixed @@ -0,0 +1,37 @@ +//@ run-rustfix +#![allow(warnings)] +use std::collections::HashMap; + +#[derive(Clone)] +struct Ctx { + a_map: HashMap>, +} + +#[derive(Clone, PartialEq, Eq)] +struct B { + a: A, +} + +fn foo(ctx: &mut Ctx) { + let a_map = ctx.a_map.clone(); //~ ERROR E0599 +} + +#[derive(Clone)] +#[derive(PartialEq)] +#[derive(Eq)] +struct S; +fn bar(ctx: &mut Ctx) { + let a_map = ctx.a_map.clone(); //~ ERROR E0599 +} + +fn qux(ctx: &mut Ctx) where Z: PartialEq { + ctx.a_map["a"].eq(&ctx.a_map["a"]); //~ ERROR E0599 + <_ as Eq>::assert_receiver_is_total_eq(&ctx.a_map["a"]); //~ ERROR E0277 +} + +fn qut(ctx: &mut Ctx) { + ctx.a_map["a"].eq(&ctx.a_map["a"]); //~ ERROR E0599 + <_ as Eq>::assert_receiver_is_total_eq(&ctx.a_map["a"]); //~ ERROR E0277 +} + +fn main() {} diff --git a/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.rs b/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.rs new file mode 100644 index 0000000000000..99b67145f1596 --- /dev/null +++ b/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.rs @@ -0,0 +1,34 @@ +//@ run-rustfix +#![allow(warnings)] +use std::collections::HashMap; + +#[derive(Clone)] +struct Ctx { + a_map: HashMap>, +} + +#[derive(Clone, PartialEq, Eq)] +struct B { + a: A, +} + +fn foo(ctx: &mut Ctx) { + let a_map = ctx.a_map.clone(); //~ ERROR E0599 +} + +struct S; +fn bar(ctx: &mut Ctx) { + let a_map = ctx.a_map.clone(); //~ ERROR E0599 +} + +fn qux(ctx: &mut Ctx) { + ctx.a_map["a"].eq(&ctx.a_map["a"]); //~ ERROR E0599 + <_ as Eq>::assert_receiver_is_total_eq(&ctx.a_map["a"]); //~ ERROR E0277 +} + +fn qut(ctx: &mut Ctx) { + ctx.a_map["a"].eq(&ctx.a_map["a"]); //~ ERROR E0599 + <_ as Eq>::assert_receiver_is_total_eq(&ctx.a_map["a"]); //~ ERROR E0277 +} + +fn main() {} diff --git a/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.stderr b/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.stderr new file mode 100644 index 0000000000000..41fcd807c5269 --- /dev/null +++ b/tests/ui/suggestions/type-or-type-param-missing-transitive-trait-contraint.stderr @@ -0,0 +1,127 @@ +error[E0599]: the method `clone` exists for struct `HashMap>`, but its trait bounds were not satisfied + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:16:27 + | +LL | struct B { + | ----------- doesn't satisfy `B: Clone` +... +LL | let a_map = ctx.a_map.clone(); + | ^^^^^ method cannot be called on `HashMap>` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `B: Clone` + which is required by `HashMap>: Clone` +help: consider restricting type parameter `Z` + | +LL | fn foo(ctx: &mut Ctx) { + | +++++++++++++++++++ + +error[E0599]: the method `clone` exists for struct `HashMap>`, but its trait bounds were not satisfied + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:21:27 + | +LL | struct B { + | ----------- doesn't satisfy `B: Clone` +... +LL | let a_map = ctx.a_map.clone(); + | ^^^^^ method cannot be called on `HashMap>` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `B: Clone` + which is required by `HashMap>: Clone` +help: consider annotating `S` with `#[derive(Clone)]` + | +LL + #[derive(Clone)] +LL | struct S; + | + +error[E0599]: the method `eq` exists for struct `B`, but its trait bounds were not satisfied + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:25:20 + | +LL | struct B { + | ----------- method `eq` not found for this struct because it doesn't satisfy `B: Iterator` or `B: PartialEq` +... +LL | ctx.a_map["a"].eq(&ctx.a_map["a"]); + | ^^ method cannot be called on `B` due to unsatisfied trait bounds + | +note: trait bound `Z: PartialEq` was not satisfied + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:10:17 + | +LL | #[derive(Clone, PartialEq, Eq)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro + = note: the following trait bounds were not satisfied: + `B: Iterator` + which is required by `&mut B: Iterator` +note: the trait `Iterator` must be implemented + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL +help: consider restricting the type parameter to satisfy the trait bound + | +LL | fn qux(ctx: &mut Ctx) where Z: PartialEq { + | ++++++++++++++++++ + +error[E0277]: the trait bound `Z: Eq` is not satisfied + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:26:6 + | +LL | <_ as Eq>::assert_receiver_is_total_eq(&ctx.a_map["a"]); + | ^ the trait `Eq` is not implemented for `Z`, which is required by `B: Eq` + | +note: required for `B` to implement `Eq` + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:10:28 + | +LL | #[derive(Clone, PartialEq, Eq)] + | ^^ unsatisfied trait bound introduced in this `derive` macro + = note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider restricting type parameter `Z` + | +LL | fn qux(ctx: &mut Ctx) { + | ++++++++++++++ + +error[E0599]: the method `eq` exists for struct `B`, but its trait bounds were not satisfied + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:30:20 + | +LL | struct B { + | ----------- method `eq` not found for this struct because it doesn't satisfy `B: Iterator` or `B: PartialEq` +... +LL | struct S; + | -------- doesn't satisfy `S: PartialEq` +... +LL | ctx.a_map["a"].eq(&ctx.a_map["a"]); + | ^^ method cannot be called on `B` due to unsatisfied trait bounds + | +note: trait bound `S: PartialEq` was not satisfied + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:10:17 + | +LL | #[derive(Clone, PartialEq, Eq)] + | ^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro + = note: the following trait bounds were not satisfied: + `B: Iterator` + which is required by `&mut B: Iterator` +note: the trait `Iterator` must be implemented + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL +help: consider annotating `S` with `#[derive(PartialEq)]` + | +LL + #[derive(PartialEq)] +LL | struct S; + | + +error[E0277]: the trait bound `S: Eq` is not satisfied + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:31:6 + | +LL | <_ as Eq>::assert_receiver_is_total_eq(&ctx.a_map["a"]); + | ^ the trait `Eq` is not implemented for `S`, which is required by `B: Eq` + | + = help: the trait `Eq` is implemented for `B` +note: required for `B` to implement `Eq` + --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:10:28 + | +LL | #[derive(Clone, PartialEq, Eq)] + | ^^ unsatisfied trait bound introduced in this `derive` macro + = note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `S` with `#[derive(Eq)]` + | +LL + #[derive(Eq)] +LL | struct S; + | + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`.