@@ -466,7 +466,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
466
466
} else if self . suggest_hoisting_call_outside_loop ( err, expr) {
467
467
// The place where the the type moves would be misleading to suggest clone.
468
468
// #121466
469
- self . suggest_cloning ( err, ty, expr) ;
469
+ self . suggest_cloning ( err, ty, expr, None ) ;
470
470
}
471
471
}
472
472
if let Some ( pat) = finder. pat {
@@ -977,7 +977,78 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
977
977
can_suggest_clone
978
978
}
979
979
980
- pub ( crate ) fn suggest_cloning ( & self , err : & mut Diag < ' _ > , ty : Ty < ' tcx > , expr : & hir:: Expr < ' _ > ) {
980
+ pub ( crate ) fn suggest_cloning (
981
+ & self ,
982
+ err : & mut Diag < ' _ > ,
983
+ ty : Ty < ' tcx > ,
984
+ expr : & hir:: Expr < ' _ > ,
985
+ other_expr : Option < & hir:: Expr < ' _ > > ,
986
+ ) {
987
+ ' outer: {
988
+ if let ty:: Ref ( ..) = ty. kind ( ) {
989
+ // We check for either `let binding = foo(expr, other_expr);` or
990
+ // `foo(expr, other_expr);` and if so we don't suggest an incorrect
991
+ // `foo(expr, other_expr).clone()`
992
+ if let Some ( other_expr) = other_expr
993
+ && let Some ( parent_let) =
994
+ self . infcx . tcx . hir ( ) . parent_iter ( expr. hir_id ) . find_map ( |n| {
995
+ if let ( hir_id, hir:: Node :: Local ( _) | hir:: Node :: Stmt ( _) ) = n {
996
+ Some ( hir_id)
997
+ } else {
998
+ None
999
+ }
1000
+ } )
1001
+ && let Some ( other_parent_let) =
1002
+ self . infcx . tcx . hir ( ) . parent_iter ( other_expr. hir_id ) . find_map ( |n| {
1003
+ if let ( hir_id, hir:: Node :: Local ( _) | hir:: Node :: Stmt ( _) ) = n {
1004
+ Some ( hir_id)
1005
+ } else {
1006
+ None
1007
+ }
1008
+ } )
1009
+ && parent_let == other_parent_let
1010
+ {
1011
+ // Explicitly check that we don't have `foo(&*expr, other_expr)`, as cloning the
1012
+ // result of `foo(...)` won't help.
1013
+ break ' outer;
1014
+ }
1015
+
1016
+ // We're suggesting `.clone()` on an borrowed value. See if the expression we have
1017
+ // is an argument to a function or method call, and try to suggest cloning the
1018
+ // *result* of the call, instead of the argument. This is closest to what people
1019
+ // would actually be looking for in most cases, with maybe the exception of things
1020
+ // like `fn(T) -> T`, but even then it is reasonable.
1021
+ let typeck_results = self . infcx . tcx . typeck ( self . mir_def_id ( ) ) ;
1022
+ let mut prev = expr;
1023
+ while let hir:: Node :: Expr ( parent) = self . infcx . tcx . parent_hir_node ( prev. hir_id ) {
1024
+ if let hir:: ExprKind :: Call ( ..) | hir:: ExprKind :: MethodCall ( ..) = parent. kind
1025
+ && let Some ( call_ty) = typeck_results. node_type_opt ( parent. hir_id )
1026
+ && let call_ty = call_ty. peel_refs ( )
1027
+ && ( !call_ty
1028
+ . walk ( )
1029
+ . any ( |t| matches ! ( t. unpack( ) , ty:: GenericArgKind :: Lifetime ( _) ) )
1030
+ || if let ty:: Alias ( ty:: Projection , _) = call_ty. kind ( ) {
1031
+ // FIXME: this isn't quite right with lifetimes on assoc types,
1032
+ // but ignore for now. We will only suggest cloning if
1033
+ // `<Ty as Trait>::Assoc: Clone`, which should keep false positives
1034
+ // down to a managable ammount.
1035
+ true
1036
+ } else {
1037
+ false
1038
+ } )
1039
+ && let Some ( clone_trait_def) = self . infcx . tcx . lang_items ( ) . clone_trait ( )
1040
+ && self
1041
+ . infcx
1042
+ . type_implements_trait ( clone_trait_def, [ call_ty] , self . param_env )
1043
+ . must_apply_modulo_regions ( )
1044
+ && self . suggest_cloning_inner ( err, call_ty, parent)
1045
+ {
1046
+ return ;
1047
+ }
1048
+ prev = parent;
1049
+ }
1050
+ }
1051
+ }
981
1052
let ty = ty. peel_refs ( ) ;
982
1053
if let Some ( clone_trait_def) = self . infcx . tcx . lang_items ( ) . clone_trait ( )
983
1054
&& self
@@ -1004,12 +1075,17 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
1004
1075
}
1005
1076
}
1006
1077
1007
- fn suggest_cloning_inner ( & self , err : & mut Diag < ' _ > , ty : Ty < ' tcx > , expr : & hir:: Expr < ' _ > ) {
1078
+ fn suggest_cloning_inner (
1079
+ & self ,
1080
+ err : & mut Diag < ' _ > ,
1081
+ ty : Ty < ' tcx > ,
1082
+ expr : & hir:: Expr < ' _ > ,
1083
+ ) -> bool {
1008
1084
let tcx = self . infcx . tcx ;
1009
1085
if let Some ( _) = self . clone_on_reference ( expr) {
1010
1086
// Avoid redundant clone suggestion already suggested in `explain_captures`.
1011
1087
// See `tests/ui/moves/needs-clone-through-deref.rs`
1012
- return ;
1088
+ return false ;
1013
1089
}
1014
1090
// Try to find predicates on *generic params* that would allow copying `ty`
1015
1091
let suggestion =
@@ -1026,7 +1102,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
1026
1102
if let hir:: ExprKind :: AddrOf ( _, hir:: Mutability :: Mut , _) = inner_expr. kind {
1027
1103
// We assume that `&mut` refs are desired for their side-effects, so cloning the
1028
1104
// value wouldn't do what the user wanted.
1029
- return ;
1105
+ return false ;
1030
1106
}
1031
1107
inner_expr = inner;
1032
1108
}
@@ -1049,6 +1125,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
1049
1125
"consider cloning the value if the performance cost is acceptable"
1050
1126
} ;
1051
1127
err. multipart_suggestion_verbose ( msg, sugg, Applicability :: MachineApplicable ) ;
1128
+ true
1052
1129
}
1053
1130
1054
1131
fn suggest_adding_bounds ( & self , err : & mut Diag < ' _ > , ty : Ty < ' tcx > , def_id : DefId , span : Span ) {
@@ -1157,7 +1234,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
1157
1234
if let Some ( expr) = self . find_expr ( borrow_span)
1158
1235
&& let Some ( ty) = typeck_results. node_type_opt ( expr. hir_id )
1159
1236
{
1160
- self . suggest_cloning ( & mut err, ty, expr) ;
1237
+ self . suggest_cloning ( & mut err, ty, expr, self . find_expr ( span ) ) ;
1161
1238
}
1162
1239
self . buffer_error ( err) ;
1163
1240
}
0 commit comments