1+ use std:: ops:: ControlFlow ;
2+
13use crate :: { is_scoped_ty, method_call} ;
24use clippy_utils:: {
35 diagnostics:: span_lint_and_help,
4- get_expr_use_or_unification_node , get_parent_expr,
6+ get_enclosing_block , get_parent_expr, path_to_local_id ,
57 paths:: { PathNS , lookup_path_str} ,
6- potential_return_of_enclosing_body,
78 ty:: implements_trait,
8- usage :: local_used_after_expr ,
9+ visitors :: for_each_expr ,
910} ;
10- use rustc_hir:: { Expr , Node } ;
11+
12+ use rustc_hir:: { Expr , ExprKind , HirId , Node , PatKind , StmtKind } ;
1113use rustc_lint:: { LateContext , LateLintPass } ;
1214use rustc_middle:: ty:: Ty ;
1315
@@ -54,45 +56,144 @@ impl<'tcx> LateLintPass<'tcx> for ImmediatelyBindScoped {
5456 // First we check if we have found a `Scoped<Value>::get` call
5557 if is_scoped_get_method_call ( cx, expr) {
5658 // Which is followed by a trait method call to `bind` in which case
57- // it is all done properly and we can exit out of the lint
59+ // it is all done properly and we can exit out of the lint.
5860 if let Some ( parent) = get_parent_expr ( cx, expr)
5961 && is_bindable_bind_method_call ( cx, parent)
6062 {
6163 return ;
6264 }
6365
64- // If the `Scoped<Value>::get` call is never used or unified we can
65- // safely exit out of the rule, otherwise we need to look into how
66- // it's used.
67- let Some ( ( usage, hir_id) ) = get_expr_use_or_unification_node ( cx. tcx , expr) else {
68- return ;
69- } ;
70-
71- if !local_used_after_expr ( cx, hir_id, expr) {
66+ // Check if the unbound value is used in an argument position of a
67+ // method or function call where binding can be safely skipped.
68+ if is_in_argument_position ( cx, expr) {
7269 return ;
7370 }
7471
75- // Now we are onto something! If the expression is returned, used
76- // after the expression or assigned to a variable we might have
77- // found an issue.
78- if let Some ( ( usage, hir_id) ) = get_expr_use_or_unification_node ( cx. tcx , expr)
79- && ( potential_return_of_enclosing_body ( cx, expr)
80- || local_used_after_expr ( cx, hir_id, expr)
81- || matches ! ( usage, Node :: LetStmt ( _) ) )
72+ // If the expression is assigned to a local variable, we need to
73+ // check that it's next use is binding or as a function argument.
74+ if let Some ( local_hir_id) = get_assigned_local ( cx, expr)
75+ && let Some ( enclosing_block) = get_enclosing_block ( cx, expr. hir_id )
8276 {
83- span_lint_and_help (
84- cx,
85- IMMEDIATELY_BIND_SCOPED ,
86- expr. span ,
87- "the result of `Scoped<Value>::get` should be immediately bound" ,
88- None ,
89- "immediately bind the value" ,
90- ) ;
77+ let mut found_valid_next_use = false ;
78+
79+ // Look for the next use of this local after the current expression.
80+ // We need to traverse the statements in the block to find proper usage
81+ for stmt in enclosing_block
82+ . stmts
83+ . iter ( )
84+ . skip_while ( |s| s. span . lo ( ) < expr. span . hi ( ) )
85+ {
86+ // Extract relevant expressions from the statement and check
87+ // it for a use valid of the local variable.
88+ let Some ( stmt_expr) = ( match & stmt. kind {
89+ StmtKind :: Expr ( expr) | StmtKind :: Semi ( expr) => Some ( * expr) ,
90+ StmtKind :: Let ( local) => local. init ,
91+ _ => None ,
92+ } ) else {
93+ continue ;
94+ } ;
95+
96+ // Check each expression in the current statement for use
97+ // of the value, breaking when found and optionally marking
98+ // it as valid.
99+ if for_each_expr ( cx, stmt_expr, |expr_in_stmt| {
100+ if path_to_local_id ( expr_in_stmt, local_hir_id) {
101+ if is_valid_use_of_unbound_value ( cx, expr_in_stmt, local_hir_id) {
102+ found_valid_next_use = true ;
103+ }
104+
105+ return ControlFlow :: Break ( true ) ;
106+ }
107+ ControlFlow :: Continue ( ( ) )
108+ } )
109+ . unwrap_or ( false )
110+ {
111+ break ;
112+ }
113+ }
114+
115+ if !found_valid_next_use {
116+ span_lint_and_help (
117+ cx,
118+ IMMEDIATELY_BIND_SCOPED ,
119+ expr. span ,
120+ "the result of `Scoped<Value>::get` should be immediately bound" ,
121+ None ,
122+ "immediately bind the value" ,
123+ ) ;
124+ }
91125 }
92126 }
93127 }
94128}
95129
130+ /// Check if an expression is assigned to a local variable and return the local's HirId
131+ fn get_assigned_local ( cx : & LateContext < ' _ > , expr : & Expr ) -> Option < HirId > {
132+ let parent_node = cx. tcx . parent_hir_id ( expr. hir_id ) ;
133+
134+ if let Node :: LetStmt ( local) = cx. tcx . hir_node ( parent_node)
135+ && let Some ( init) = local. init
136+ && init. hir_id == expr. hir_id
137+ && let PatKind :: Binding ( _, hir_id, _, _) = local. pat . kind
138+ {
139+ Some ( hir_id)
140+ } else {
141+ None
142+ }
143+ }
144+
145+ /// Check if a use of an unbound value is valid (binding or function argument)
146+ fn is_valid_use_of_unbound_value ( cx : & LateContext < ' _ > , expr : & Expr , hir_id : HirId ) -> bool {
147+ // Check if we're in a method call and if so, check if it's a bind call
148+ if let Some ( parent) = get_parent_expr ( cx, expr)
149+ && is_bindable_bind_method_call ( cx, parent)
150+ {
151+ return true ;
152+ }
153+
154+ // If this is a method call to bind() on our local, it's valid
155+ if is_bindable_bind_method_call ( cx, expr) {
156+ return true ;
157+ }
158+
159+ // If this is the local being used as a function argument, it's valid
160+ if path_to_local_id ( expr, hir_id) && is_in_argument_position ( cx, expr) {
161+ return true ;
162+ }
163+
164+ false
165+ }
166+
167+ /// Check if an expression is in an argument position where binding can be skipped
168+ fn is_in_argument_position ( cx : & LateContext < ' _ > , expr : & Expr ) -> bool {
169+ let mut current_expr = expr;
170+
171+ // Walk up the parent chain to see if we're in a function call argument
172+ while let Some ( parent) = get_parent_expr ( cx, current_expr) {
173+ match parent. kind {
174+ // If we find a method call where our expression is an argument (not receiver)
175+ ExprKind :: MethodCall ( _, receiver, args, _) => {
176+ if receiver. hir_id != current_expr. hir_id
177+ && args. iter ( ) . any ( |arg| arg. hir_id == current_expr. hir_id )
178+ {
179+ return true ;
180+ }
181+ }
182+ // If we find a function call where our expression is an argument
183+ ExprKind :: Call ( _, args) => {
184+ if args. iter ( ) . any ( |arg| arg. hir_id == current_expr. hir_id ) {
185+ return true ;
186+ }
187+ }
188+ // Continue walking up for other expression types
189+ _ => { }
190+ }
191+ current_expr = parent;
192+ }
193+
194+ false
195+ }
196+
96197fn is_scoped_get_method_call ( cx : & LateContext < ' _ > , expr : & Expr ) -> bool {
97198 if let Some ( ( method, recv, _, _, _) ) = method_call ( expr)
98199 && method == "get"
@@ -118,18 +219,6 @@ fn is_bindable_bind_method_call(cx: &LateContext<'_>, expr: &Expr) -> bool {
118219 }
119220}
120221
121- fn is_bindable_unbind_method_call ( cx : & LateContext < ' _ > , expr : & Expr ) -> bool {
122- if let Some ( ( method, _, _, _, _) ) = method_call ( expr)
123- && method == "unbind"
124- && let expr_ty = cx. typeck_results ( ) . expr_ty ( expr)
125- && implements_bindable_trait ( cx, & expr_ty)
126- {
127- true
128- } else {
129- false
130- }
131- }
132-
133222fn implements_bindable_trait < ' tcx > ( cx : & LateContext < ' tcx > , ty : & Ty < ' tcx > ) -> bool {
134223 lookup_path_str ( cx. tcx , PathNS :: Type , "nova_vm::engine::context::Bindable" )
135224 . first ( )
0 commit comments