@@ -464,7 +464,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
464464 {
465465 // We already suggest cloning for these cases in `explain_captures`.
466466 } else {
467- self . suggest_cloning ( err, ty, expr) ;
467+ self . suggest_cloning ( err, ty, expr, None ) ;
468468 }
469469 }
470470 if let Some ( pat) = finder. pat {
@@ -747,7 +747,78 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
747747 true
748748 }
749749
750- pub ( crate ) fn suggest_cloning ( & self , err : & mut Diag < ' _ > , ty : Ty < ' tcx > , expr : & hir:: Expr < ' _ > ) {
750+ pub ( crate ) fn suggest_cloning (
751+ & self ,
752+ err : & mut Diag < ' _ > ,
753+ ty : Ty < ' tcx > ,
754+ expr : & hir:: Expr < ' _ > ,
755+ other_expr : Option < & hir:: Expr < ' _ > > ,
756+ ) {
757+ ' outer: {
758+ if let ty:: Ref ( ..) = ty. kind ( ) {
759+ // We check for either `let binding = foo(expr, other_expr);` or
760+ // `foo(expr, other_expr);` and if so we don't suggest an incorrect
761+ // `foo(expr, other_expr).clone()`
762+ if let Some ( other_expr) = other_expr
763+ && let Some ( parent_let) =
764+ self . infcx . tcx . hir ( ) . parent_iter ( expr. hir_id ) . find_map ( |n| {
765+ if let ( hir_id, hir:: Node :: Local ( _) | hir:: Node :: Stmt ( _) ) = n {
766+ Some ( hir_id)
767+ } else {
768+ None
769+ }
770+ } )
771+ && let Some ( other_parent_let) =
772+ self . infcx . tcx . hir ( ) . parent_iter ( other_expr. hir_id ) . find_map ( |n| {
773+ if let ( hir_id, hir:: Node :: Local ( _) | hir:: Node :: Stmt ( _) ) = n {
774+ Some ( hir_id)
775+ } else {
776+ None
777+ }
778+ } )
779+ && parent_let == other_parent_let
780+ {
781+ // Explicitly check that we don't have `foo(&*expr, other_expr)`, as cloning the
782+ // result of `foo(...)` won't help.
783+ break ' outer;
784+ }
785+
786+ // We're suggesting `.clone()` on an borrowed value. See if the expression we have
787+ // is an argument to a function or method call, and try to suggest cloning the
788+ // *result* of the call, instead of the argument. This is closest to what people
789+ // would actually be looking for in most cases, with maybe the exception of things
790+ // like `fn(T) -> T`, but even then it is reasonable.
791+ let typeck_results = self . infcx . tcx . typeck ( self . mir_def_id ( ) ) ;
792+ let mut prev = expr;
793+ while let hir:: Node :: Expr ( parent) = self . infcx . tcx . parent_hir_node ( prev. hir_id ) {
794+ if let hir:: ExprKind :: Call ( ..) | hir:: ExprKind :: MethodCall ( ..) = parent. kind
795+ && let Some ( call_ty) = typeck_results. node_type_opt ( parent. hir_id )
796+ && let call_ty = call_ty. peel_refs ( )
797+ && ( !call_ty
798+ . walk ( )
799+ . any ( |t| matches ! ( t. unpack( ) , ty:: GenericArgKind :: Lifetime ( _) ) )
800+ || if let ty:: Alias ( ty:: Projection , _) = call_ty. kind ( ) {
801+ // FIXME: this isn't quite right with lifetimes on assoc types,
802+ // but ignore for now. We will only suggest cloning if
803+ // `<Ty as Trait>::Assoc: Clone`, which should keep false positives
804+ // down to a managable ammount.
805+ true
806+ } else {
807+ false
808+ } )
809+ && let Some ( clone_trait_def) = self . infcx . tcx . lang_items ( ) . clone_trait ( )
810+ && self
811+ . infcx
812+ . type_implements_trait ( clone_trait_def, [ call_ty] , self . param_env )
813+ . must_apply_modulo_regions ( )
814+ && self . suggest_cloning_inner ( err, call_ty, parent)
815+ {
816+ return ;
817+ }
818+ prev = parent;
819+ }
820+ }
821+ }
751822 let ty = ty. peel_refs ( ) ;
752823 if let Some ( clone_trait_def) = self . infcx . tcx . lang_items ( ) . clone_trait ( )
753824 && self
@@ -774,12 +845,17 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
774845 }
775846 }
776847
777- fn suggest_cloning_inner ( & self , err : & mut Diag < ' _ > , ty : Ty < ' tcx > , expr : & hir:: Expr < ' _ > ) {
848+ fn suggest_cloning_inner (
849+ & self ,
850+ err : & mut Diag < ' _ > ,
851+ ty : Ty < ' tcx > ,
852+ expr : & hir:: Expr < ' _ > ,
853+ ) -> bool {
778854 let tcx = self . infcx . tcx ;
779855 if let Some ( _) = self . clone_on_reference ( expr) {
780856 // Avoid redundant clone suggestion already suggested in `explain_captures`.
781857 // See `tests/ui/moves/needs-clone-through-deref.rs`
782- return ;
858+ return false ;
783859 }
784860 // Try to find predicates on *generic params* that would allow copying `ty`
785861 let suggestion =
@@ -796,7 +872,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
796872 if let hir:: ExprKind :: AddrOf ( _, hir:: Mutability :: Mut , _) = inner_expr. kind {
797873 // We assume that `&mut` refs are desired for their side-effects, so cloning the
798874 // value wouldn't do what the user wanted.
799- return ;
875+ return false ;
800876 }
801877 inner_expr = inner;
802878 }
@@ -819,6 +895,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
819895 "consider cloning the value if the performance cost is acceptable"
820896 } ;
821897 err. multipart_suggestion_verbose ( msg, sugg, Applicability :: MachineApplicable ) ;
898+ true
822899 }
823900
824901 fn suggest_adding_bounds ( & self , err : & mut Diag < ' _ > , ty : Ty < ' tcx > , def_id : DefId , span : Span ) {
@@ -927,7 +1004,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
9271004 if let Some ( expr) = self . find_expr ( borrow_span)
9281005 && let Some ( ty) = typeck_results. node_type_opt ( expr. hir_id )
9291006 {
930- self . suggest_cloning ( & mut err, ty, expr) ;
1007+ self . suggest_cloning ( & mut err, ty, expr, self . find_expr ( span ) ) ;
9311008 }
9321009 self . buffer_error ( err) ;
9331010 }
0 commit comments