@@ -466,7 +466,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
466466 } else if self . suggest_hoisting_call_outside_loop ( err, expr) {
467467 // The place where the the type moves would be misleading to suggest clone.
468468 // #121466
469- self . suggest_cloning ( err, ty, expr) ;
469+ self . suggest_cloning ( err, ty, expr, None ) ;
470470 }
471471 }
472472 if let Some ( pat) = finder. pat {
@@ -977,7 +977,78 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
977977 can_suggest_clone
978978 }
979979
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+ }
9811052 let ty = ty. peel_refs ( ) ;
9821053 if let Some ( clone_trait_def) = self . infcx . tcx . lang_items ( ) . clone_trait ( )
9831054 && self
@@ -1004,12 +1075,17 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10041075 }
10051076 }
10061077
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 {
10081084 let tcx = self . infcx . tcx ;
10091085 if let Some ( _) = self . clone_on_reference ( expr) {
10101086 // Avoid redundant clone suggestion already suggested in `explain_captures`.
10111087 // See `tests/ui/moves/needs-clone-through-deref.rs`
1012- return ;
1088+ return false ;
10131089 }
10141090 // Try to find predicates on *generic params* that would allow copying `ty`
10151091 let suggestion =
@@ -1026,7 +1102,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10261102 if let hir:: ExprKind :: AddrOf ( _, hir:: Mutability :: Mut , _) = inner_expr. kind {
10271103 // We assume that `&mut` refs are desired for their side-effects, so cloning the
10281104 // value wouldn't do what the user wanted.
1029- return ;
1105+ return false ;
10301106 }
10311107 inner_expr = inner;
10321108 }
@@ -1049,6 +1125,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10491125 "consider cloning the value if the performance cost is acceptable"
10501126 } ;
10511127 err. multipart_suggestion_verbose ( msg, sugg, Applicability :: MachineApplicable ) ;
1128+ true
10521129 }
10531130
10541131 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> {
11571234 if let Some ( expr) = self . find_expr ( borrow_span)
11581235 && let Some ( ty) = typeck_results. node_type_opt ( expr. hir_id )
11591236 {
1160- self . suggest_cloning ( & mut err, ty, expr) ;
1237+ self . suggest_cloning ( & mut err, ty, expr, self . find_expr ( span ) ) ;
11611238 }
11621239 self . buffer_error ( err) ;
11631240 }
0 commit comments