Skip to content

Commit b0c41eb

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 d3a3939 commit b0c41eb

File tree

5 files changed

+195
-12
lines changed

5 files changed

+195
-12
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

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

13681464
unsatisfied_bounds = true;
13691465
}

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

+10-10
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
242242
trait_pred: ty::PolyTraitPredicate<'tcx>,
243243
associated_ty: Option<(&'static str, Ty<'tcx>)>,
244244
mut body_id: LocalDefId,
245-
) {
245+
) -> bool {
246246
if trait_pred.skip_binder().polarity != ty::PredicatePolarity::Positive {
247-
return;
247+
return false;
248248
}
249249

250250
let trait_pred = self.resolve_numeric_literals_with_default(trait_pred);
@@ -279,7 +279,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
279279
trait_pred,
280280
Some((ident, bounds)),
281281
);
282-
return;
282+
return true;
283283
}
284284

285285
hir::Node::TraitItem(hir::TraitItem {
@@ -293,7 +293,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
293293
self.tcx, body_id, generics, "`Self`", err, None, projection, trait_pred,
294294
None,
295295
);
296-
return;
296+
return true;
297297
}
298298

299299
hir::Node::TraitItem(hir::TraitItem {
@@ -321,7 +321,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
321321
trait_pred,
322322
None,
323323
);
324-
return;
324+
return true;
325325
}
326326
hir::Node::Item(hir::Item {
327327
kind:
@@ -341,7 +341,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
341341
trait_pred,
342342
None,
343343
);
344-
return;
344+
return true;
345345
}
346346

347347
hir::Node::Item(hir::Item {
@@ -374,7 +374,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
374374
.iter()
375375
.all(|g| g.is_suggestable(self.tcx, false))
376376
{
377-
return;
377+
return false;
378378
}
379379
// Missing generic type parameter bound.
380380
let param_name = self_ty.to_string();
@@ -406,7 +406,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
406406
Some(trait_pred.def_id()),
407407
None,
408408
) {
409-
return;
409+
return true;
410410
}
411411
}
412412

@@ -432,10 +432,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
432432
trait_pred,
433433
associated_ty,
434434
) {
435-
return;
435+
return true;
436436
}
437437
}
438-
hir::Node::Crate(..) => return,
438+
hir::Node::Crate(..) => return false,
439439

440440
_ => {}
441441
}
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)