Skip to content

Commit b426004

Browse files
committed
diagnostics: suggest Clone bounds when noop clone()
1 parent 9c3ad80 commit b426004

File tree

3 files changed

+94
-16
lines changed

3 files changed

+94
-16
lines changed

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

+56-16
Original file line numberDiff line numberDiff line change
@@ -975,29 +975,58 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
975975
err: &mut Diag<'_>,
976976
trait_pred: ty::PolyTraitPredicate<'tcx>,
977977
) -> bool {
978+
let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, .. } =
979+
obligation.cause.code()
980+
else {
981+
return false;
982+
};
983+
let clone_trait = self.tcx.require_lang_item(LangItem::Clone, None);
978984
let self_ty = self.resolve_vars_if_possible(trait_pred.self_ty());
979985
self.enter_forall(self_ty, |ty: Ty<'_>| {
980986
let Some(generics) = self.tcx.hir().get_generics(obligation.cause.body_id) else {
981987
return false;
982988
};
983989
let ty::Ref(_, inner_ty, hir::Mutability::Not) = ty.kind() else { return false };
984990
let ty::Param(param) = inner_ty.kind() else { return false };
985-
let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, .. } =
986-
obligation.cause.code()
987-
else {
988-
return false;
989-
};
990-
let arg_node = self.tcx.hir_node(*arg_hir_id);
991-
let Node::Expr(Expr { kind: hir::ExprKind::Path(_), .. }) = arg_node else {
992-
return false;
993-
};
994991

995-
let clone_trait = self.tcx.require_lang_item(LangItem::Clone, None);
996992
let has_clone = |ty| {
997993
self.type_implements_trait(clone_trait, [ty], obligation.param_env)
998994
.must_apply_modulo_regions()
999995
};
1000996

997+
let existing_clone_call = match self.tcx.hir_node(*arg_hir_id) {
998+
// It's just a variable. Propose cloning it.
999+
Node::Expr(Expr { kind: hir::ExprKind::Path(_), .. }) => None,
1000+
// It's already a call to `clone()`. We might be able to suggest
1001+
// adding a `+ Clone` bound, though.
1002+
Node::Expr(Expr {
1003+
kind:
1004+
hir::ExprKind::MethodCall(
1005+
hir::PathSegment { ident, .. },
1006+
_receiver,
1007+
&[],
1008+
call_span,
1009+
),
1010+
hir_id,
1011+
..
1012+
}) if ident.name == sym::clone
1013+
&& !call_span.from_expansion()
1014+
&& !has_clone(*inner_ty) =>
1015+
{
1016+
// We only care about method calls corresponding to the real `Clone` trait.
1017+
let Some(typeck_results) = self.typeck_results.as_ref() else { return false };
1018+
let Some((DefKind::AssocFn, did)) = typeck_results.type_dependent_def(*hir_id)
1019+
else {
1020+
return false;
1021+
};
1022+
if self.tcx.trait_of_item(did) != Some(clone_trait) {
1023+
return false;
1024+
}
1025+
Some(ident.span)
1026+
}
1027+
_ => return false,
1028+
};
1029+
10011030
let new_obligation = self.mk_trait_obligation_with_new_self_ty(
10021031
obligation.param_env,
10031032
trait_pred.map_bound(|trait_pred| (trait_pred, *inner_ty)),
@@ -1015,12 +1044,23 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
10151044
None,
10161045
);
10171046
}
1018-
err.span_suggestion_verbose(
1019-
obligation.cause.span.shrink_to_hi(),
1020-
"consider using clone here",
1021-
".clone()".to_string(),
1022-
Applicability::MaybeIncorrect,
1023-
);
1047+
if let Some(existing_clone_call) = existing_clone_call {
1048+
err.span_note(
1049+
existing_clone_call,
1050+
format!(
1051+
"because `{inner_ty}` does not implement `Clone`, \
1052+
this call to `clone()` copies the reference, \
1053+
which does not do anything"
1054+
),
1055+
);
1056+
} else {
1057+
err.span_suggestion_verbose(
1058+
obligation.cause.span.shrink_to_hi(),
1059+
"consider using clone here",
1060+
".clone()".to_string(),
1061+
Applicability::MaybeIncorrect,
1062+
);
1063+
}
10241064
return true;
10251065
}
10261066
false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#[derive(Clone)]
2+
struct ThingThatDoesAThing;
3+
4+
trait DoesAThing {}
5+
6+
impl DoesAThing for ThingThatDoesAThing {}
7+
8+
fn clones_impl_ref_inline(thing: &impl DoesAThing) {
9+
//~^ HELP consider further restricting this bound
10+
drops_impl_owned(thing.clone()); //~ ERROR E0277
11+
//~^ NOTE copies the reference
12+
//~| NOTE the trait `DoesAThing` is not implemented for `&impl DoesAThing`
13+
}
14+
15+
fn drops_impl_owned(_thing: impl DoesAThing) { }
16+
17+
fn main() {
18+
clones_impl_ref_inline(&ThingThatDoesAThing);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error[E0277]: the trait bound `&impl DoesAThing: DoesAThing` is not satisfied
2+
--> $DIR/clone-bounds-121524.rs:10:22
3+
|
4+
LL | drops_impl_owned(thing.clone());
5+
| ^^^^^^^^^^^^^ the trait `DoesAThing` is not implemented for `&impl DoesAThing`
6+
|
7+
note: because `impl DoesAThing` does not implement `Clone`, this call to `clone()` copies the reference, which does not do anything
8+
--> $DIR/clone-bounds-121524.rs:10:28
9+
|
10+
LL | drops_impl_owned(thing.clone());
11+
| ^^^^^
12+
help: consider further restricting this bound
13+
|
14+
LL | fn clones_impl_ref_inline(thing: &impl DoesAThing + Clone) {
15+
| +++++++
16+
17+
error: aborting due to 1 previous error
18+
19+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)