11use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_hir_and_then} ;
2+ use clippy_utils:: mir:: { enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap } ;
23use clippy_utils:: source:: { snippet_with_applicability, snippet_with_context} ;
34use clippy_utils:: sugg:: has_enclosing_paren;
45use clippy_utils:: ty:: { expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res} ;
@@ -11,13 +12,16 @@ use rustc_data_structures::fx::FxIndexMap;
1112use rustc_errors:: Applicability ;
1213use rustc_hir:: intravisit:: { walk_ty, Visitor } ;
1314use rustc_hir:: {
14- self as hir, def_id:: DefId , BindingAnnotation , Body , BodyId , BorrowKind , Closure , Expr , ExprKind , FnRetTy ,
15- GenericArg , HirId , ImplItem , ImplItemKind , Item , ItemKind , Local , MatchSource , Mutability , Node , Pat , PatKind ,
16- Path , QPath , TraitItem , TraitItemKind , TyKind , UnOp ,
15+ self as hir,
16+ def_id:: { DefId , LocalDefId } ,
17+ BindingAnnotation , Body , BodyId , BorrowKind , Closure , Expr , ExprKind , FnRetTy , GenericArg , HirId , ImplItem ,
18+ ImplItemKind , Item , ItemKind , Local , MatchSource , Mutability , Node , Pat , PatKind , Path , QPath , TraitItem ,
19+ TraitItemKind , TyKind , UnOp ,
1720} ;
1821use rustc_index:: bit_set:: BitSet ;
1922use rustc_infer:: infer:: TyCtxtInferExt ;
2023use rustc_lint:: { LateContext , LateLintPass } ;
24+ use rustc_middle:: mir:: { Rvalue , StatementKind } ;
2125use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , AutoBorrow , AutoBorrowMutability } ;
2226use rustc_middle:: ty:: {
2327 self , Binder , BoundVariableKind , EarlyBinder , FnSig , GenericArgKind , List , ParamTy , PredicateKind ,
@@ -141,15 +145,15 @@ declare_clippy_lint! {
141145 "dereferencing when the compiler would automatically dereference"
142146}
143147
144- impl_lint_pass ! ( Dereferencing => [
148+ impl_lint_pass ! ( Dereferencing < ' _> => [
145149 EXPLICIT_DEREF_METHODS ,
146150 NEEDLESS_BORROW ,
147151 REF_BINDING_TO_REFERENCE ,
148152 EXPLICIT_AUTO_DEREF ,
149153] ) ;
150154
151155#[ derive( Default ) ]
152- pub struct Dereferencing {
156+ pub struct Dereferencing < ' tcx > {
153157 state : Option < ( State , StateData ) > ,
154158
155159 // While parsing a `deref` method call in ufcs form, the path to the function is itself an
@@ -170,11 +174,16 @@ pub struct Dereferencing {
170174 /// e.g. `m!(x) | Foo::Bar(ref x)`
171175 ref_locals : FxIndexMap < HirId , Option < RefPat > > ,
172176
177+ /// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
178+ /// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
179+ /// be moved.
180+ possible_borrowers : Vec < ( LocalDefId , PossibleBorrowerMap < ' tcx , ' tcx > ) > ,
181+
173182 // `IntoIterator` for arrays requires Rust 1.53.
174183 msrv : Option < RustcVersion > ,
175184}
176185
177- impl Dereferencing {
186+ impl < ' tcx > Dereferencing < ' tcx > {
178187 #[ must_use]
179188 pub fn new ( msrv : Option < RustcVersion > ) -> Self {
180189 Self {
@@ -244,7 +253,7 @@ struct RefPat {
244253 hir_id : HirId ,
245254}
246255
247- impl < ' tcx > LateLintPass < ' tcx > for Dereferencing {
256+ impl < ' tcx > LateLintPass < ' tcx > for Dereferencing < ' tcx > {
248257 #[ expect( clippy:: too_many_lines) ]
249258 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
250259 // Skip path expressions from deref calls. e.g. `Deref::deref(e)`
@@ -278,7 +287,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
278287 match ( self . state . take ( ) , kind) {
279288 ( None , kind) => {
280289 let expr_ty = typeck. expr_ty ( expr) ;
281- let ( position, adjustments) = walk_parents ( cx, expr, self . msrv ) ;
290+ let ( position, adjustments) = walk_parents ( cx, & mut self . possible_borrowers , expr, self . msrv ) ;
282291 match kind {
283292 RefOp :: Deref => {
284293 if let Position :: FieldAccess {
@@ -550,6 +559,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
550559 }
551560
552561 fn check_body_post ( & mut self , cx : & LateContext < ' tcx > , body : & ' tcx Body < ' _ > ) {
562+ if self . possible_borrowers . last ( ) . map_or ( false , |& ( local_def_id, _) | {
563+ local_def_id == cx. tcx . hir ( ) . body_owner_def_id ( body. id ( ) )
564+ } ) {
565+ self . possible_borrowers . pop ( ) ;
566+ }
567+
553568 if Some ( body. id ( ) ) == self . current_body {
554569 for pat in self . ref_locals . drain ( ..) . filter_map ( |( _, x) | x) {
555570 let replacements = pat. replacements ;
@@ -682,6 +697,7 @@ impl Position {
682697#[ expect( clippy:: too_many_lines) ]
683698fn walk_parents < ' tcx > (
684699 cx : & LateContext < ' tcx > ,
700+ possible_borrowers : & mut Vec < ( LocalDefId , PossibleBorrowerMap < ' tcx , ' tcx > ) > ,
685701 e : & ' tcx Expr < ' _ > ,
686702 msrv : Option < RustcVersion > ,
687703) -> ( Position , & ' tcx [ Adjustment < ' tcx > ] ) {
@@ -796,7 +812,16 @@ fn walk_parents<'tcx>(
796812 Some ( hir_ty) => binding_ty_auto_deref_stability ( cx, hir_ty, precedence, ty. bound_vars ( ) ) ,
797813 None => {
798814 if let ty:: Param ( param_ty) = ty. skip_binder ( ) . kind ( ) {
799- needless_borrow_impl_arg_position ( cx, parent, i, * param_ty, e, precedence, msrv)
815+ needless_borrow_impl_arg_position (
816+ cx,
817+ possible_borrowers,
818+ parent,
819+ i,
820+ * param_ty,
821+ e,
822+ precedence,
823+ msrv,
824+ )
800825 } else {
801826 ty_auto_deref_stability ( cx, cx. tcx . erase_late_bound_regions ( ty) , precedence)
802827 . position_for_arg ( )
@@ -844,7 +869,16 @@ fn walk_parents<'tcx>(
844869 args. iter ( ) . position ( |arg| arg. hir_id == child_id) . map ( |i| {
845870 let ty = cx. tcx . fn_sig ( id) . skip_binder ( ) . inputs ( ) [ i + 1 ] ;
846871 if let ty:: Param ( param_ty) = ty. kind ( ) {
847- needless_borrow_impl_arg_position ( cx, parent, i + 1 , * param_ty, e, precedence, msrv)
872+ needless_borrow_impl_arg_position (
873+ cx,
874+ possible_borrowers,
875+ parent,
876+ i + 1 ,
877+ * param_ty,
878+ e,
879+ precedence,
880+ msrv,
881+ )
848882 } else {
849883 ty_auto_deref_stability (
850884 cx,
@@ -1018,8 +1052,10 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
10181052// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
10191053// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
10201054// be moved, but it cannot be.
1055+ #[ expect( clippy:: too_many_arguments) ]
10211056fn needless_borrow_impl_arg_position < ' tcx > (
10221057 cx : & LateContext < ' tcx > ,
1058+ possible_borrowers : & mut Vec < ( LocalDefId , PossibleBorrowerMap < ' tcx , ' tcx > ) > ,
10231059 parent : & Expr < ' tcx > ,
10241060 arg_index : usize ,
10251061 param_ty : ParamTy ,
@@ -1082,10 +1118,13 @@ fn needless_borrow_impl_arg_position<'tcx>(
10821118 // elements are modified each time `check_referent` is called.
10831119 let mut substs_with_referent_ty = substs_with_expr_ty. to_vec ( ) ;
10841120
1085- let mut check_referent = |referent| {
1121+ let mut check_reference_and_referent = |reference , referent| {
10861122 let referent_ty = cx. typeck_results ( ) . expr_ty ( referent) ;
10871123
1088- if !is_copy ( cx, referent_ty) {
1124+ if !is_copy ( cx, referent_ty)
1125+ && ( referent_ty. has_significant_drop ( cx. tcx , cx. param_env )
1126+ || !referent_used_exactly_once ( cx, possible_borrowers, reference) )
1127+ {
10891128 return false ;
10901129 }
10911130
@@ -1127,7 +1166,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
11271166
11281167 let mut needless_borrow = false ;
11291168 while let ExprKind :: AddrOf ( _, _, referent) = expr. kind {
1130- if !check_referent ( referent) {
1169+ if !check_reference_and_referent ( expr , referent) {
11311170 break ;
11321171 }
11331172 expr = referent;
@@ -1155,6 +1194,36 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
11551194 } )
11561195}
11571196
1197+ fn referent_used_exactly_once < ' a , ' tcx > (
1198+ cx : & ' a LateContext < ' tcx > ,
1199+ possible_borrowers : & mut Vec < ( LocalDefId , PossibleBorrowerMap < ' tcx , ' tcx > ) > ,
1200+ reference : & Expr < ' tcx > ,
1201+ ) -> bool {
1202+ let mir = enclosing_mir ( cx. tcx , reference. hir_id ) ;
1203+ if let Some ( local) = expr_local ( cx. tcx , reference)
1204+ && let [ location] = * local_assignments ( mir, local) . as_slice ( )
1205+ && let StatementKind :: Assign ( box ( _, Rvalue :: Ref ( _, _, place) ) ) =
1206+ mir. basic_blocks [ location. block ] . statements [ location. statement_index ] . kind
1207+ && !place. has_deref ( )
1208+ {
1209+ let body_owner_local_def_id = cx. tcx . hir ( ) . enclosing_body_owner ( reference. hir_id ) ;
1210+ if possible_borrowers
1211+ . last ( )
1212+ . map_or ( true , |& ( local_def_id, _) | local_def_id != body_owner_local_def_id)
1213+ {
1214+ possible_borrowers. push ( ( body_owner_local_def_id, PossibleBorrowerMap :: new ( cx, mir) ) ) ;
1215+ }
1216+ let possible_borrower = & mut possible_borrowers. last_mut ( ) . unwrap ( ) . 1 ;
1217+ // If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
1218+ // that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
1219+ // itself. See the comment in that method for an explanation as to why.
1220+ possible_borrower. bounded_borrowers ( & [ local] , & [ local, place. local ] , place. local , location)
1221+ && used_exactly_once ( mir, place. local ) . unwrap_or ( false )
1222+ } else {
1223+ false
1224+ }
1225+ }
1226+
11581227// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting
11591228// projected type that is a type parameter. Returns `false` if replacing the types would have an
11601229// effect on the function signature beyond substituting `new_ty` for `param_ty`.
@@ -1439,8 +1508,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
14391508 }
14401509}
14411510
1442- impl Dereferencing {
1443- fn check_local_usage < ' tcx > ( & mut self , cx : & LateContext < ' tcx > , e : & Expr < ' tcx > , local : HirId ) {
1511+ impl < ' tcx > Dereferencing < ' tcx > {
1512+ fn check_local_usage ( & mut self , cx : & LateContext < ' tcx > , e : & Expr < ' tcx > , local : HirId ) {
14441513 if let Some ( outer_pat) = self . ref_locals . get_mut ( & local) {
14451514 if let Some ( pat) = outer_pat {
14461515 // Check for auto-deref
0 commit comments