Skip to content

Commit 350d3e6

Browse files
committed
Suggest derive(Trait) or T: Trait from transitive obligation in some 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; | ```
1 parent 3811f40 commit 350d3e6

File tree

5 files changed

+192
-11
lines changed

5 files changed

+192
-11
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

+95-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use rustc_span::{edit_distance, ErrorGuaranteed, ExpnKind, FileName, MacroKind,
3737
use rustc_span::{Symbol, DUMMY_SP};
3838
use rustc_trait_selection::error_reporting::traits::on_unimplemented::OnUnimplementedNote;
3939
use rustc_trait_selection::error_reporting::traits::on_unimplemented::TypeErrCtxtExt as _;
40+
use rustc_trait_selection::error_reporting::traits::suggestions::TypeErrCtxtExt;
4041
use rustc_trait_selection::infer::InferCtxtExt;
4142
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
4243
use rustc_trait_selection::traits::{
@@ -1368,7 +1369,100 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13681369
"the following trait bounds were not satisfied:\n{bound_list}"
13691370
));
13701371
}
1371-
suggested_derive = self.suggest_derive(&mut err, unsatisfied_predicates);
1372+
1373+
let mut suggest_derive = true;
1374+
for (pred, _, cause) in unsatisfied_predicates {
1375+
let Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred))) =
1376+
pred.kind().no_bound_vars()
1377+
else {
1378+
continue;
1379+
};
1380+
let (adt, params) = match trait_pred.self_ty().kind() {
1381+
ty::Adt(adt, params) if adt.did().is_local() => (*adt, params),
1382+
_ => continue,
1383+
};
1384+
if self
1385+
.tcx
1386+
.all_impls(trait_pred.def_id())
1387+
.filter_map(|imp_did| {
1388+
self.tcx.impl_trait_header(imp_did).map(|h| (imp_did, h))
1389+
})
1390+
.filter(|(did, header)| {
1391+
let imp = header.trait_ref.instantiate_identity();
1392+
let impl_adt = match imp.self_ty().ty_adt_def() {
1393+
Some(impl_adt) if adt.did().is_local() => impl_adt,
1394+
_ => return false,
1395+
};
1396+
header.polarity == ty::ImplPolarity::Positive
1397+
&& impl_adt == adt
1398+
&& self.tcx.is_automatically_derived(*did)
1399+
})
1400+
.count()
1401+
== 1
1402+
{
1403+
// We now know that for this predicate, there *was* a `derive(Trait)` for
1404+
// the trait at hand, so we don't want to suggest writing that again.
1405+
for param in &params[..] {
1406+
// Look at the type parameters for the currently obligated type to see
1407+
// if a restriciton of `TypeParam: Trait` would help. If the
1408+
// instantiated type param is not a type param but instead an actual
1409+
// type, see if we can suggest `derive(Trait)` on *that* type.
1410+
// See `tests/ui/suggestions/f1000.rs`
1411+
let Some(ty) = param.as_type() else {
1412+
continue;
1413+
};
1414+
match ty.kind() {
1415+
ty::Adt(adt, _) if adt.did().is_local() => {
1416+
// The type param at hand is a local type, try to suggest
1417+
// `derive(Trait)`.
1418+
let trait_ref =
1419+
ty::TraitRef::new(tcx, trait_pred.trait_ref.def_id, [ty]);
1420+
let trait_pred = ty::Binder::dummy(ty::TraitPredicate {
1421+
trait_ref,
1422+
polarity: ty::PredicatePolarity::Positive,
1423+
});
1424+
suggested_derive = self.suggest_derive(
1425+
&mut err,
1426+
&[(
1427+
<_ as ty::UpcastFrom<_, _>>::upcast_from(
1428+
trait_pred, self.tcx,
1429+
),
1430+
None,
1431+
cause.clone(),
1432+
)],
1433+
);
1434+
}
1435+
ty::Param(_) => {
1436+
// It was a type param. See if it corresponds to the current
1437+
// `fn` and suggest `T: Trait`.
1438+
if let Some(obligation) = cause {
1439+
let trait_ref = ty::TraitRef::new(
1440+
tcx,
1441+
trait_pred.trait_ref.def_id,
1442+
[ty],
1443+
);
1444+
let trait_pred = ty::Binder::dummy(ty::TraitPredicate {
1445+
trait_ref,
1446+
polarity: ty::PredicatePolarity::Positive,
1447+
});
1448+
suggested_derive =
1449+
self.err_ctxt().suggest_restricting_param_bound(
1450+
&mut err,
1451+
trait_pred,
1452+
None,
1453+
obligation.body_id,
1454+
);
1455+
}
1456+
}
1457+
_ => {}
1458+
}
1459+
}
1460+
suggest_derive = false
1461+
}
1462+
}
1463+
if suggest_derive {
1464+
suggested_derive = self.suggest_derive(&mut err, unsatisfied_predicates);
1465+
}
13721466

13731467
unsatisfied_bounds = true;
13741468
}

compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
249249
trait_pred: ty::PolyTraitPredicate<'tcx>,
250250
associated_ty: Option<(&'static str, Ty<'tcx>)>,
251251
mut body_id: LocalDefId,
252-
) {
252+
) -> bool {
253253
if trait_pred.skip_binder().polarity != ty::PredicatePolarity::Positive {
254-
return;
254+
return false;
255255
}
256256

257257
let trait_pred = self.resolve_numeric_literals_with_default(trait_pred);
@@ -286,7 +286,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
286286
trait_pred,
287287
Some((ident, bounds)),
288288
);
289-
return;
289+
return true;
290290
}
291291

292292
hir::Node::TraitItem(hir::TraitItem {
@@ -300,7 +300,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
300300
self.tcx, body_id, generics, "`Self`", err, None, projection, trait_pred,
301301
None,
302302
);
303-
return;
303+
return true;
304304
}
305305

306306
hir::Node::TraitItem(hir::TraitItem {
@@ -328,7 +328,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
328328
trait_pred,
329329
None,
330330
);
331-
return;
331+
return true;
332332
}
333333
hir::Node::Item(hir::Item {
334334
kind:
@@ -348,7 +348,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
348348
trait_pred,
349349
None,
350350
);
351-
return;
351+
return true;
352352
}
353353

354354
hir::Node::Item(hir::Item {
@@ -381,7 +381,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
381381
.iter()
382382
.all(|g| g.is_suggestable(self.tcx, false))
383383
{
384-
return;
384+
return false;
385385
}
386386
// Missing generic type parameter bound.
387387
let param_name = self_ty.to_string();
@@ -413,7 +413,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
413413
Some(trait_pred.def_id()),
414414
None,
415415
) {
416-
return;
416+
return true;
417417
}
418418
}
419419

@@ -439,10 +439,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
439439
trait_pred,
440440
associated_ty,
441441
) {
442-
return;
442+
return true;
443443
}
444444
}
445-
hir::Node::Crate(..) => return,
445+
hir::Node::Crate(..) => return false,
446446

447447
_ => {}
448448
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@ run-rustfix
2+
#![allow(warnings)]
3+
use std::collections::HashMap;
4+
5+
#[derive(Clone)]
6+
struct Ctx<A> {
7+
a_map: HashMap<String, B<A>>,
8+
}
9+
10+
#[derive(Clone)]
11+
struct B<A> {
12+
a: A,
13+
}
14+
15+
fn foo<Z: std::clone::Clone>(ctx: &mut Ctx<Z>) {
16+
let a_map = ctx.a_map.clone(); //~ ERROR E0599
17+
}
18+
19+
#[derive(Clone)]
20+
struct S;
21+
fn bar(ctx: &mut Ctx<S>) {
22+
let a_map = ctx.a_map.clone(); //~ ERROR E0599
23+
}
24+
25+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//@ run-rustfix
2+
#![allow(warnings)]
3+
use std::collections::HashMap;
4+
5+
#[derive(Clone)]
6+
struct Ctx<A> {
7+
a_map: HashMap<String, B<A>>,
8+
}
9+
10+
#[derive(Clone)]
11+
struct B<A> {
12+
a: A,
13+
}
14+
15+
fn foo<Z>(ctx: &mut Ctx<Z>) {
16+
let a_map = ctx.a_map.clone(); //~ ERROR E0599
17+
}
18+
19+
struct S;
20+
fn bar(ctx: &mut Ctx<S>) {
21+
let a_map = ctx.a_map.clone(); //~ ERROR E0599
22+
}
23+
24+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error[E0599]: the method `clone` exists for struct `HashMap<String, B<Z>>`, but its trait bounds were not satisfied
2+
--> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:16:27
3+
|
4+
LL | struct B<A> {
5+
| ----------- doesn't satisfy `B<Z>: Clone`
6+
...
7+
LL | let a_map = ctx.a_map.clone();
8+
| ^^^^^ method cannot be called on `HashMap<String, B<Z>>` due to unsatisfied trait bounds
9+
|
10+
= note: the following trait bounds were not satisfied:
11+
`B<Z>: Clone`
12+
which is required by `HashMap<String, B<Z>>: Clone`
13+
help: consider restricting type parameter `Z`
14+
|
15+
LL | fn foo<Z: std::clone::Clone>(ctx: &mut Ctx<Z>) {
16+
| +++++++++++++++++++
17+
18+
error[E0599]: the method `clone` exists for struct `HashMap<String, B<S>>`, but its trait bounds were not satisfied
19+
--> $DIR/type-or-type-param-missing-transitive-trait-contraint.rs:21:27
20+
|
21+
LL | struct B<A> {
22+
| ----------- doesn't satisfy `B<S>: Clone`
23+
...
24+
LL | let a_map = ctx.a_map.clone();
25+
| ^^^^^ method cannot be called on `HashMap<String, B<S>>` due to unsatisfied trait bounds
26+
|
27+
= note: the following trait bounds were not satisfied:
28+
`B<S>: Clone`
29+
which is required by `HashMap<String, B<S>>: Clone`
30+
help: consider annotating `S` with `#[derive(Clone)]`
31+
|
32+
LL + #[derive(Clone)]
33+
LL | struct S;
34+
|
35+
36+
error: aborting due to 2 previous errors
37+
38+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)