-
Notifications
You must be signed in to change notification settings - Fork 12.3k
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
Suggest derive(Trait)
or T: Trait
from transitive obligation in some cases
#127997
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
derive(Trait)
or T: Trait
from transitive obligation in s…
…ome cases With code like the following ```rust struct Ctx<A> { a_map: HashMap<String, B<A>>, } struct B<A> { a: A, } ``` the derived trait will have an implicit restriction on `A: Clone` for both types. When referenced as follows: ```rust fn foo<Z>(ctx: &mut Ctx<Z>) { let a_map = ctx.a_map.clone(); //~ ERROR E0599 } ``` suggest constraining `Z`: ``` error[E0599]: the method `clone` exists for struct `HashMap<String, B<Z>>`, but its trait bounds were not satisfied --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:16:27 | LL | struct B<A> { | ----------- doesn't satisfy `B<Z>: Clone` ... LL | let a_map = ctx.a_map.clone(); | ^^^^^ method cannot be called on `HashMap<String, B<Z>>` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: `B<Z>: Clone` which is required by `HashMap<String, B<Z>>: Clone` help: consider restricting type parameter `Z` | LL | fn foo<Z: std::clone::Clone>(ctx: &mut Ctx<Z>) { | +++++++++++++++++++ ``` When referenced as follows, with a specific type `S`: ```rust struct S; fn bar(ctx: &mut Ctx<S>) { let a_map = ctx.a_map.clone(); //~ ERROR E0599 } ``` suggest `derive`ing the appropriate trait on the local type: ``` error[E0599]: the method `clone` exists for struct `HashMap<String, B<S>>`, but its trait bounds were not satisfied --> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:21:27 | LL | struct B<A> { | ----------- doesn't satisfy `B<S>: Clone` ... LL | let a_map = ctx.a_map.clone(); | ^^^^^ method cannot be called on `HashMap<String, B<S>>` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: `B<S>: Clone` which is required by `HashMap<String, B<S>>: Clone` help: consider annotating `S` with `#[derive(Clone)]` | LL + #[derive(Clone)] LL | struct S; | ```
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,7 @@ use rustc_span::{edit_distance, ErrorGuaranteed, ExpnKind, FileName, MacroKind, | |
use rustc_span::{Symbol, DUMMY_SP}; | ||
use rustc_trait_selection::error_reporting::traits::on_unimplemented::OnUnimplementedNote; | ||
use rustc_trait_selection::error_reporting::traits::on_unimplemented::TypeErrCtxtExt as _; | ||
use rustc_trait_selection::error_reporting::traits::suggestions::TypeErrCtxtExt; | ||
use rustc_trait_selection::infer::InferCtxtExt; | ||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; | ||
use rustc_trait_selection::traits::{ | ||
|
@@ -1368,7 +1369,100 @@ 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 (adt, params) = match trait_pred.self_ty().kind() { | ||
ty::Adt(adt, params) if adt.did().is_local() => (*adt, params), | ||
_ => continue, | ||
}; | ||
if self | ||
.tcx | ||
.all_impls(trait_pred.def_id()) | ||
.filter_map(|imp_did| { | ||
self.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 | ||
&& self.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::new(tcx, trait_pred.trait_ref.def_id, [ty]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding that last comment, for example, there is no guarantee that I can create a trait ref out of just this constituent type just by knowing it's an automatically derived trait. This will almost certainly ICE if you slap There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That being said, I don't think that the right solution is to just slap a check for the number of params the trait has onto this logic, since that introduces seemingly arbitrary inconsistencies in how we issue this suggestion (e.g. only for |
||
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; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
//@ run-rustfix | ||
#![allow(warnings)] | ||
use std::collections::HashMap; | ||
|
||
#[derive(Clone)] | ||
struct Ctx<A> { | ||
a_map: HashMap<String, B<A>>, | ||
} | ||
|
||
#[derive(Clone)] | ||
struct B<A> { | ||
a: A, | ||
} | ||
|
||
fn foo<Z: std::clone::Clone>(ctx: &mut Ctx<Z>) { | ||
let a_map = ctx.a_map.clone(); //~ ERROR E0599 | ||
} | ||
|
||
#[derive(Clone)] | ||
struct S; | ||
fn bar(ctx: &mut Ctx<S>) { | ||
let a_map = ctx.a_map.clone(); //~ ERROR E0599 | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
//@ run-rustfix | ||
#![allow(warnings)] | ||
use std::collections::HashMap; | ||
|
||
#[derive(Clone)] | ||
struct Ctx<A> { | ||
a_map: HashMap<String, B<A>>, | ||
} | ||
|
||
#[derive(Clone)] | ||
struct B<A> { | ||
a: A, | ||
} | ||
|
||
fn foo<Z>(ctx: &mut Ctx<Z>) { | ||
let a_map = ctx.a_map.clone(); //~ ERROR E0599 | ||
} | ||
|
||
struct S; | ||
fn bar(ctx: &mut Ctx<S>) { | ||
let a_map = ctx.a_map.clone(); //~ ERROR E0599 | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
error[E0599]: the method `clone` exists for struct `HashMap<String, B<Z>>`, but its trait bounds were not satisfied | ||
--> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:16:27 | ||
| | ||
LL | struct B<A> { | ||
| ----------- doesn't satisfy `B<Z>: Clone` | ||
... | ||
LL | let a_map = ctx.a_map.clone(); | ||
| ^^^^^ method cannot be called on `HashMap<String, B<Z>>` due to unsatisfied trait bounds | ||
| | ||
= note: the following trait bounds were not satisfied: | ||
`B<Z>: Clone` | ||
which is required by `HashMap<String, B<Z>>: Clone` | ||
help: consider restricting type parameter `Z` | ||
| | ||
LL | fn foo<Z: std::clone::Clone>(ctx: &mut Ctx<Z>) { | ||
| +++++++++++++++++++ | ||
|
||
error[E0599]: the method `clone` exists for struct `HashMap<String, B<S>>`, but its trait bounds were not satisfied | ||
--> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:21:27 | ||
| | ||
LL | struct B<A> { | ||
| ----------- doesn't satisfy `B<S>: Clone` | ||
... | ||
LL | let a_map = ctx.a_map.clone(); | ||
| ^^^^^ method cannot be called on `HashMap<String, B<S>>` due to unsatisfied trait bounds | ||
| | ||
= note: the following trait bounds were not satisfied: | ||
`B<S>: Clone` | ||
which is required by `HashMap<String, B<S>>: Clone` | ||
help: consider annotating `S` with `#[derive(Clone)]` | ||
| | ||
LL + #[derive(Clone)] | ||
LL | struct S; | ||
| | ||
|
||
error: aborting due to 2 previous errors | ||
|
||
For more information about this error, try `rustc --explain E0599`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this check is sufficient -- I can slap
automatically_derived
onto any impl I want without any problems. There is no guarantee that this is actually a derived impl in a way that makes this logic sufficient.