11use super :: SAME_ITEM_PUSH ;
22use clippy_utils:: diagnostics:: span_lint_and_help;
3+ use clippy_utils:: path_to_local;
34use clippy_utils:: source:: snippet_with_macro_callsite;
45use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item} ;
56use if_chain:: if_chain;
7+ use rustc_data_structures:: fx:: FxHashSet ;
68use rustc_hir:: def:: { DefKind , Res } ;
79use rustc_hir:: intravisit:: { walk_expr, NestedVisitorMap , Visitor } ;
8- use rustc_hir:: { BindingAnnotation , Block , Expr , ExprKind , Node , Pat , PatKind , Stmt , StmtKind } ;
10+ use rustc_hir:: { BindingAnnotation , Block , Expr , ExprKind , HirId , Node , Pat , PatKind , Stmt , StmtKind } ;
911use rustc_lint:: LateContext ;
1012use rustc_middle:: hir:: map:: Map ;
1113use rustc_span:: symbol:: sym;
@@ -41,70 +43,94 @@ pub(super) fn check<'tcx>(
4143 }
4244
4345 // Determine whether it is safe to lint the body
44- let mut same_item_push_visitor = SameItemPushVisitor {
45- should_lint : true ,
46- vec_push : None ,
47- cx,
48- } ;
46+ let mut same_item_push_visitor = SameItemPushVisitor :: new ( cx) ;
4947 walk_expr ( & mut same_item_push_visitor, body) ;
50- if same_item_push_visitor . should_lint {
51- if let Some ( ( vec , pushed_item ) ) = same_item_push_visitor. vec_push {
52- let vec_ty = cx . typeck_results ( ) . expr_ty ( vec) ;
53- let ty = vec_ty . walk ( ) . nth ( 1 ) . unwrap ( ) . expect_ty ( ) ;
54- if cx
55- . tcx
56- . lang_items ( )
57- . clone_trait ( )
58- . map_or ( false , |id| implements_trait ( cx , ty , id , & [ ] ) )
59- {
60- // Make sure that the push does not involve possibly mutating values
61- match pushed_item . kind {
62- ExprKind :: Path ( ref qpath ) => {
63- match cx . qpath_res ( qpath , pushed_item . hir_id ) {
64- // immutable bindings that are initialized with literal or constant
65- Res :: Local ( hir_id ) => {
66- let node = cx . tcx . hir ( ) . get ( hir_id) ;
67- if_chain ! {
68- if let Node :: Binding ( pat ) = node ;
69- if let PatKind :: Binding ( bind_ann , .. ) = pat . kind ;
70- if !matches! ( bind_ann, BindingAnnotation :: RefMut | BindingAnnotation :: Mutable ) ;
71- let parent_node = cx . tcx . hir ( ) . get_parent_node ( hir_id ) ;
72- if let Some ( Node :: Local ( parent_let_expr ) ) = cx. tcx. hir( ) . find ( parent_node ) ;
73- if let Some ( init ) = parent_let_expr . init ;
74- then {
75- match init . kind {
76- // immutable bindings that are initialized with literal
77- ExprKind :: Lit ( .. ) => emit_lint ( cx , vec , pushed_item ) ,
78- // immutable bindings that are initialized with constant
79- ExprKind :: Path ( ref path ) => {
80- if let Res :: Def ( DefKind :: Const , .. ) = cx . qpath_res ( path , init . hir_id ) {
81- emit_lint ( cx , vec , pushed_item ) ;
82- }
48+ if_chain ! {
49+ if same_item_push_visitor. should_lint ( ) ;
50+ if let Some ( ( vec, pushed_item ) ) = same_item_push_visitor . vec_push ;
51+ let vec_ty = cx . typeck_results ( ) . expr_ty ( vec ) ;
52+ let ty = vec_ty . walk ( ) . nth ( 1 ) . unwrap ( ) . expect_ty ( ) ;
53+ if cx
54+ . tcx
55+ . lang_items ( )
56+ . clone_trait ( )
57+ . map_or ( false , |id| implements_trait ( cx , ty , id , & [ ] ) ) ;
58+ then {
59+ // Make sure that the push does not involve possibly mutating values
60+ match pushed_item . kind {
61+ ExprKind :: Path ( ref qpath ) => {
62+ match cx . qpath_res ( qpath , pushed_item . hir_id ) {
63+ // immutable bindings that are initialized with literal or constant
64+ Res :: Local ( hir_id) => {
65+ let node = cx . tcx . hir ( ) . get ( hir_id ) ;
66+ if_chain! {
67+ if let Node :: Binding ( pat ) = node ;
68+ if let PatKind :: Binding ( bind_ann, .. ) = pat . kind ;
69+ if !matches! ( bind_ann , BindingAnnotation :: RefMut | BindingAnnotation :: Mutable ) ;
70+ let parent_node = cx. tcx. hir( ) . get_parent_node ( hir_id ) ;
71+ if let Some ( Node :: Local ( parent_let_expr ) ) = cx . tcx . hir ( ) . find ( parent_node ) ;
72+ if let Some ( init ) = parent_let_expr . init ;
73+ then {
74+ match init . kind {
75+ // immutable bindings that are initialized with literal
76+ ExprKind :: Lit ( .. ) => emit_lint ( cx , vec , pushed_item ) ,
77+ // immutable bindings that are initialized with constant
78+ ExprKind :: Path ( ref path ) => {
79+ if let Res :: Def ( DefKind :: Const , .. ) = cx . qpath_res ( path , init . hir_id ) {
80+ emit_lint ( cx , vec , pushed_item ) ;
8381 }
84- _ => { } ,
8582 }
83+ _ => { } ,
8684 }
8785 }
88- } ,
89- // constant
90- Res :: Def ( DefKind :: Const , .. ) => emit_lint ( cx , vec , pushed_item ) ,
91- _ => { } ,
92- }
93- } ,
94- ExprKind :: Lit ( .. ) => emit_lint ( cx , vec , pushed_item ) ,
95- _ => { } ,
96- }
86+ }
87+ } ,
88+ // constant
89+ Res :: Def ( DefKind :: Const , .. ) => emit_lint ( cx , vec , pushed_item ) ,
90+ _ => { } ,
91+ }
92+ } ,
93+ ExprKind :: Lit ( .. ) => emit_lint ( cx , vec , pushed_item ) ,
94+ _ => { } ,
9795 }
9896 }
9997 }
10098}
10199
102100// Scans the body of the for loop and determines whether lint should be given
103101struct SameItemPushVisitor < ' a , ' tcx > {
104- should_lint : bool ,
102+ non_deterministic_expr : bool ,
103+ multiple_pushes : bool ,
105104 // this field holds the last vec push operation visited, which should be the only push seen
106105 vec_push : Option < ( & ' tcx Expr < ' tcx > , & ' tcx Expr < ' tcx > ) > ,
107106 cx : & ' a LateContext < ' tcx > ,
107+ used_locals : FxHashSet < HirId > ,
108+ }
109+
110+ impl < ' a , ' tcx > SameItemPushVisitor < ' a , ' tcx > {
111+ fn new ( cx : & ' a LateContext < ' tcx > ) -> Self {
112+ Self {
113+ non_deterministic_expr : false ,
114+ multiple_pushes : false ,
115+ vec_push : None ,
116+ cx,
117+ used_locals : FxHashSet :: default ( ) ,
118+ }
119+ }
120+
121+ fn should_lint ( & self ) -> bool {
122+ if_chain ! {
123+ if !self . non_deterministic_expr;
124+ if !self . multiple_pushes;
125+ if let Some ( ( vec, _) ) = self . vec_push;
126+ if let Some ( hir_id) = path_to_local( vec) ;
127+ then {
128+ !self . used_locals. contains( & hir_id)
129+ } else {
130+ false
131+ }
132+ }
133+ }
108134}
109135
110136impl < ' a , ' tcx > Visitor < ' tcx > for SameItemPushVisitor < ' a , ' tcx > {
@@ -113,9 +139,14 @@ impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
113139 fn visit_expr ( & mut self , expr : & ' tcx Expr < ' _ > ) {
114140 match & expr. kind {
115141 // Non-determinism may occur ... don't give a lint
116- ExprKind :: Loop ( ..) | ExprKind :: Match ( ..) => self . should_lint = false ,
142+ ExprKind :: Loop ( ..) | ExprKind :: Match ( ..) | ExprKind :: If ( .. ) => self . non_deterministic_expr = true ,
117143 ExprKind :: Block ( block, _) => self . visit_block ( block) ,
118- _ => { } ,
144+ _ => {
145+ if let Some ( hir_id) = path_to_local ( expr) {
146+ self . used_locals . insert ( hir_id) ;
147+ }
148+ walk_expr ( self , expr) ;
149+ } ,
119150 }
120151 }
121152
@@ -140,7 +171,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
140171 self . vec_push = vec_push_option;
141172 } else {
142173 // There are multiple pushes ... don't lint
143- self . should_lint = false ;
174+ self . multiple_pushes = true ;
144175 }
145176 }
146177 }
0 commit comments