1
+ use super :: implicit_clone:: is_clone_like;
1
2
use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
3
use clippy_utils:: msrvs:: { self , Msrv } ;
3
4
use clippy_utils:: source:: snippet_with_applicability;
@@ -8,8 +9,8 @@ use rustc_hir::def_id::DefId;
8
9
use rustc_hir:: { self as hir, LangItem } ;
9
10
use rustc_lint:: LateContext ;
10
11
use rustc_middle:: mir:: Mutability ;
11
- use rustc_middle:: ty;
12
12
use rustc_middle:: ty:: adjustment:: Adjust ;
13
+ use rustc_middle:: ty:: { self , Ty } ;
13
14
use rustc_span:: symbol:: Ident ;
14
15
use rustc_span:: { Span , sym} ;
15
16
@@ -67,22 +68,29 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_
67
68
}
68
69
} ,
69
70
hir:: ExprKind :: MethodCall ( method, obj, [ ] , _) => {
70
- if ident_eq ( name, obj) && method . ident . name == sym :: clone
71
+ if ident_eq ( name, obj)
71
72
&& let Some ( fn_id) = cx. typeck_results ( ) . type_dependent_def_id ( closure_expr. hir_id )
72
- && let Some ( trait_id ) = cx . tcx . trait_of_item ( fn_id)
73
- && cx . tcx . lang_items ( ) . clone_trait ( ) == Some ( trait_id )
73
+ && ( is_clone ( cx , method . ident . name , fn_id)
74
+ || is_clone_like ( cx , method . ident . as_str ( ) , fn_id ) )
74
75
// no autoderefs
75
76
&& !cx. typeck_results ( ) . expr_adjustments ( obj) . iter ( )
76
77
. any ( |a| matches ! ( a. kind, Adjust :: Deref ( Some ( ..) ) ) )
78
+ && let obj_ty = cx. typeck_results ( ) . expr_ty_adjusted ( obj)
79
+ && let ty:: Ref ( _, ty, Mutability :: Not ) = obj_ty. kind ( )
80
+ // Verify that the method call's output type is the same as its input type. This is to
81
+ // avoid cases like `to_string` being called on a `&str`, or `to_vec` being called on a
82
+ // slice.
83
+ && * ty == cx. typeck_results ( ) . expr_ty_adjusted ( closure_expr)
77
84
{
78
- let obj_ty = cx. typeck_results ( ) . expr_ty ( obj) ;
79
- if let ty:: Ref ( _, ty, mutability) = obj_ty. kind ( ) {
80
- if matches ! ( mutability, Mutability :: Not ) {
81
- let copy = is_copy ( cx, * ty) ;
82
- lint_explicit_closure ( cx, e. span , recv. span , copy, msrv) ;
83
- }
85
+ let obj_ty_unadjusted = cx. typeck_results ( ) . expr_ty ( obj) ;
86
+ if obj_ty == obj_ty_unadjusted {
87
+ let copy = is_copy ( cx, * ty) ;
88
+ lint_explicit_closure ( cx, e. span , recv. span , copy, msrv) ;
84
89
} else {
85
- lint_needless_cloning ( cx, e. span , recv. span ) ;
90
+ // To avoid issue #6299, ensure `obj` is not a mutable reference.
91
+ if !matches ! ( obj_ty_unadjusted. kind( ) , ty:: Ref ( _, _, Mutability :: Mut ) ) {
92
+ lint_needless_cloning ( cx, e. span , recv. span ) ;
93
+ }
86
94
}
87
95
}
88
96
} ,
@@ -105,29 +113,66 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_
105
113
}
106
114
}
107
115
116
+ fn is_clone ( cx : & LateContext < ' _ > , method : rustc_span:: Symbol , fn_id : DefId ) -> bool {
117
+ if method == sym:: clone
118
+ && let Some ( trait_id) = cx. tcx . trait_of_item ( fn_id)
119
+ && cx. tcx . lang_items ( ) . clone_trait ( ) == Some ( trait_id)
120
+ {
121
+ true
122
+ } else {
123
+ false
124
+ }
125
+ }
126
+
108
127
fn handle_path (
109
128
cx : & LateContext < ' _ > ,
110
129
arg : & hir:: Expr < ' _ > ,
111
130
qpath : & hir:: QPath < ' _ > ,
112
131
e : & hir:: Expr < ' _ > ,
113
132
recv : & hir:: Expr < ' _ > ,
114
133
) {
134
+ let method = || match qpath {
135
+ hir:: QPath :: TypeRelative ( _, method) => Some ( method) ,
136
+ _ => None ,
137
+ } ;
115
138
if let Some ( path_def_id) = cx. qpath_res ( qpath, arg. hir_id ) . opt_def_id ( )
116
- && cx. tcx . lang_items ( ) . get ( LangItem :: CloneFn ) == Some ( path_def_id)
139
+ && ( cx. tcx . lang_items ( ) . get ( LangItem :: CloneFn ) == Some ( path_def_id)
140
+ || method ( ) . is_some_and ( |method| is_clone_like ( cx, method. ident . name . as_str ( ) , path_def_id) ) )
117
141
// The `copied` and `cloned` methods are only available on `&T` and `&mut T` in `Option`
118
142
// and `Result`.
119
- && let ty :: Adt ( _ , args ) = cx . typeck_results ( ) . expr_ty ( recv) . kind ( )
120
- && let args = args . as_slice ( )
121
- && let Some ( ty ) = args . iter ( ) . find_map ( |generic_arg| generic_arg . as_type ( ) )
122
- && let ty :: Ref ( _ , ty , Mutability :: Not ) = ty . kind ( )
123
- && let ty :: FnDef ( _ , lst ) = cx . typeck_results ( ) . expr_ty ( arg ) . kind ( )
124
- && lst . iter ( ) . all ( |l| l . as_type ( ) == Some ( * ty ) )
125
- && !should_call_clone_as_function ( cx, * ty)
143
+ // Previously, `handle_path` would make this determination by checking the type of ` recv`.
144
+ // Specifically, `handle_path` would check whether `recv`'s type was instantiated with a
145
+ // reference. However, that approach can produce false negatives. Consider
146
+ // `std::slice::Iter`, for example. Even if it is instantiated with a non-reference type,
147
+ // its `map` method will expect a function that operates on references.
148
+ && let Some ( ty ) = clone_like_referent ( cx , arg )
149
+ && !should_call_clone_as_function ( cx, ty)
126
150
{
127
151
lint_path ( cx, e. span , recv. span , is_copy ( cx, ty. peel_refs ( ) ) ) ;
128
152
}
129
153
}
130
154
155
+ /// Determine whether `expr` is function whose input type is `&T` and whose output type is `T`. If
156
+ /// so, return `T`.
157
+ fn clone_like_referent < ' tcx > ( cx : & LateContext < ' tcx > , expr : & hir:: Expr < ' _ > ) -> Option < Ty < ' tcx > > {
158
+ let ty = cx. typeck_results ( ) . expr_ty_adjusted ( expr) ;
159
+ if let ty:: FnDef ( def_id, generic_args) = ty. kind ( )
160
+ && let tys = cx
161
+ . tcx
162
+ . fn_sig ( def_id)
163
+ . instantiate ( cx. tcx , generic_args)
164
+ . skip_binder ( )
165
+ . inputs_and_output
166
+ && let [ input_ty, output_ty] = tys. as_slice ( )
167
+ && let ty:: Ref ( _, referent_ty, Mutability :: Not ) = input_ty. kind ( )
168
+ && referent_ty == output_ty
169
+ {
170
+ Some ( * referent_ty)
171
+ } else {
172
+ None
173
+ }
174
+ }
175
+
131
176
fn ident_eq ( name : Ident , path : & hir:: Expr < ' _ > ) -> bool {
132
177
if let hir:: ExprKind :: Path ( hir:: QPath :: Resolved ( None , path) ) = path. kind {
133
178
path. segments . len ( ) == 1 && path. segments [ 0 ] . ident == name
0 commit comments