11use clippy_utils:: diagnostics:: span_lint_and_sugg;
2- use clippy_utils:: source:: snippet;
3- use clippy_utils:: visitors:: for_each_expr_without_closures;
2+ use clippy_utils:: visitors:: { for_each_expr, for_each_expr_without_closures} ;
43use core:: ops:: ControlFlow ;
54use rustc_data_structures:: fx:: FxHashMap ;
65use rustc_errors:: Applicability ;
7- use rustc_hir:: def:: Res ;
8- use rustc_hir:: { Block , Body , ExprKind , HirId , LetStmt , PatKind , StmtKind } ;
6+ use rustc_hir:: def:: { DefKind , Res } ;
7+ use rustc_hir:: { Body , Expr , ExprKind , HirId , LetStmt , PatKind , StmtKind } ;
98use rustc_lint:: LateContext ;
10- use rustc_middle:: ty:: Ty ;
9+ use rustc_middle:: ty:: { Ty , TypeVisitableExt } ;
1110use rustc_span:: Span ;
1211
1312use super :: NEEDLESS_TYPE_CAST ;
1413
1514struct BindingInfo < ' a > {
1615 source_ty : Ty < ' a > ,
17- ty_span : Option < Span > ,
18- pat_span : Span ,
16+ ty_span : Span ,
1917}
2018
2119struct UsageInfo < ' a > {
22- is_cast : bool ,
2320 cast_to : Option < Ty < ' a > > ,
21+ in_generic_context : bool ,
2422}
2523
2624pub ( super ) fn check < ' a > ( cx : & LateContext < ' a > , body : & Body < ' a > ) {
2725 let mut bindings: FxHashMap < HirId , BindingInfo < ' a > > = FxHashMap :: default ( ) ;
2826
29- collect_bindings_from_block ( cx, body. value , & mut bindings) ;
30-
3127 for_each_expr_without_closures ( body. value , |expr| {
32- if let ExprKind :: Let ( let_expr) = expr. kind {
33- collect_binding_from_let ( cx, let_expr, & mut bindings) ;
28+ match expr. kind {
29+ ExprKind :: Block ( block, _) => {
30+ for stmt in block. stmts {
31+ if let StmtKind :: Let ( let_stmt) = stmt. kind {
32+ collect_binding_from_local ( cx, let_stmt, & mut bindings) ;
33+ }
34+ }
35+ } ,
36+ ExprKind :: Let ( let_expr) => {
37+ collect_binding_from_let ( cx, let_expr, & mut bindings) ;
38+ } ,
39+ _ => { } ,
3440 }
3541 ControlFlow :: < ( ) > :: Continue ( ( ) )
3642 } ) ;
3743
3844 #[ allow( rustc:: potential_query_instability) ]
3945 let mut binding_vec: Vec < _ > = bindings. into_iter ( ) . collect ( ) ;
40- binding_vec. sort_by_key ( |( _, info) | info. pat_span . lo ( ) ) ;
46+ binding_vec. sort_by_key ( |( _, info) | info. ty_span . lo ( ) ) ;
4147
4248 for ( hir_id, binding_info) in binding_vec {
4349 check_binding_usages ( cx, body, hir_id, & binding_info) ;
4450 }
4551}
4652
47- fn collect_bindings_from_block < ' a > (
48- cx : & LateContext < ' a > ,
49- expr : & rustc_hir:: Expr < ' a > ,
50- bindings : & mut FxHashMap < HirId , BindingInfo < ' a > > ,
51- ) {
52- if let ExprKind :: Block ( block, _) = expr. kind {
53- collect_bindings_from_block_inner ( cx, block, bindings) ;
54- }
55- }
56-
57- fn collect_bindings_from_block_inner < ' a > (
58- cx : & LateContext < ' a > ,
59- block : & Block < ' a > ,
60- bindings : & mut FxHashMap < HirId , BindingInfo < ' a > > ,
61- ) {
62- for stmt in block. stmts {
63- if let StmtKind :: Let ( let_stmt) = stmt. kind {
64- collect_binding_from_local ( cx, let_stmt, bindings) ;
65- }
66- }
67- }
68-
6953fn collect_binding_from_let < ' a > (
7054 cx : & LateContext < ' a > ,
7155 let_expr : & rustc_hir:: LetExpr < ' a > ,
7256 bindings : & mut FxHashMap < HirId , BindingInfo < ' a > > ,
7357) {
74- if let_expr. ty . is_none ( ) {
58+ if let_expr. ty . is_none ( ) || let_expr . span . from_expansion ( ) || has_generic_return_type ( cx , let_expr . init ) {
7559 return ;
7660 }
7761
78- if let PatKind :: Binding ( _, hir_id, _, _) = let_expr. pat . kind {
62+ if let PatKind :: Binding ( _, hir_id, _, _) = let_expr. pat . kind
63+ && let Some ( ty_hir) = let_expr. ty
64+ {
7965 let ty = cx. typeck_results ( ) . pat_ty ( let_expr. pat ) ;
8066 if ty. is_numeric ( ) {
8167 bindings. insert (
8268 hir_id,
8369 BindingInfo {
8470 source_ty : ty,
85- ty_span : let_expr. ty . map ( |t| t. span ) ,
86- pat_span : let_expr. pat . span ,
71+ ty_span : ty_hir. span ,
8772 } ,
8873 ) ;
8974 }
@@ -95,105 +80,165 @@ fn collect_binding_from_local<'a>(
9580 let_stmt : & LetStmt < ' a > ,
9681 bindings : & mut FxHashMap < HirId , BindingInfo < ' a > > ,
9782) {
98- // Only check bindings with explicit type annotations
99- // Otherwise, the suggestion to change the type may not be valid
100- // (e.g., `let x = 42u8;` cannot just change to `let x: i64 = 42u8;` )
101- if let_stmt . ty . is_none ( ) {
83+ if let_stmt . ty . is_none ( )
84+ || let_stmt . span . from_expansion ( )
85+ || let_stmt . init . is_some_and ( |init| has_generic_return_type ( cx , init ) )
86+ {
10287 return ;
10388 }
10489
105- if let PatKind :: Binding ( _, hir_id, _, _) = let_stmt. pat . kind {
90+ if let PatKind :: Binding ( _, hir_id, _, _) = let_stmt. pat . kind
91+ && let Some ( ty_hir) = let_stmt. ty
92+ {
10693 let ty = cx. typeck_results ( ) . pat_ty ( let_stmt. pat ) ;
10794 if ty. is_numeric ( ) {
10895 bindings. insert (
10996 hir_id,
11097 BindingInfo {
11198 source_ty : ty,
112- ty_span : let_stmt. ty . map ( |t| t. span ) ,
113- pat_span : let_stmt. pat . span ,
99+ ty_span : ty_hir. span ,
114100 } ,
115101 ) ;
116102 }
117103 }
118104}
119105
106+ fn has_generic_return_type ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
107+ match & expr. kind {
108+ ExprKind :: MethodCall ( ..) => {
109+ if let Some ( def_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id ) {
110+ let sig = cx. tcx . fn_sig ( def_id) . instantiate_identity ( ) ;
111+ let ret_ty = sig. output ( ) . skip_binder ( ) ;
112+ return ret_ty. has_param ( ) ;
113+ }
114+ false
115+ } ,
116+ ExprKind :: Call ( callee, _) => {
117+ if let ExprKind :: Path ( qpath) = & callee. kind {
118+ let res = cx. qpath_res ( qpath, callee. hir_id ) ;
119+ if let Res :: Def ( DefKind :: Fn | DefKind :: AssocFn , def_id) = res {
120+ let sig = cx. tcx . fn_sig ( def_id) . instantiate_identity ( ) ;
121+ let ret_ty = sig. output ( ) . skip_binder ( ) ;
122+ return ret_ty. has_param ( ) ;
123+ }
124+ }
125+ false
126+ } ,
127+ _ => false ,
128+ }
129+ }
130+
131+ fn is_generic_res ( cx : & LateContext < ' _ > , res : Res ) -> bool {
132+ let has_type_params = |def_id| {
133+ cx. tcx
134+ . generics_of ( def_id)
135+ . own_params
136+ . iter ( )
137+ . any ( |p| p. kind . is_ty_or_const ( ) )
138+ } ;
139+ match res {
140+ Res :: Def ( DefKind :: Fn | DefKind :: AssocFn , def_id) => has_type_params ( def_id) ,
141+ // Ctor → Variant → ADT: constructor's parent is variant, variant's parent is the ADT
142+ Res :: Def ( DefKind :: Ctor ( ..) , def_id) => has_type_params ( cx. tcx . parent ( cx. tcx . parent ( def_id) ) ) ,
143+ _ => false ,
144+ }
145+ }
146+
147+ fn is_cast_in_generic_context < ' a > ( cx : & LateContext < ' a > , cast_expr : & Expr < ' a > ) -> bool {
148+ let mut current_id = cast_expr. hir_id ;
149+
150+ loop {
151+ let parent_id = cx. tcx . parent_hir_id ( current_id) ;
152+ if parent_id == current_id {
153+ return false ;
154+ }
155+
156+ let parent = cx. tcx . hir_node ( parent_id) ;
157+
158+ match parent {
159+ rustc_hir:: Node :: Expr ( parent_expr) => {
160+ match & parent_expr. kind {
161+ // Closure body is a separate type inference context
162+ ExprKind :: Closure ( _) => return false ,
163+ ExprKind :: Call ( callee, _) => {
164+ if let ExprKind :: Path ( qpath) = & callee. kind {
165+ let res = cx. qpath_res ( qpath, callee. hir_id ) ;
166+ if is_generic_res ( cx, res) {
167+ return true ;
168+ }
169+ }
170+ } ,
171+ ExprKind :: MethodCall ( ..) => {
172+ if let Some ( def_id) = cx. typeck_results ( ) . type_dependent_def_id ( parent_expr. hir_id )
173+ && cx
174+ . tcx
175+ . generics_of ( def_id)
176+ . own_params
177+ . iter ( )
178+ . any ( |p| p. kind . is_ty_or_const ( ) )
179+ {
180+ return true ;
181+ }
182+ } ,
183+ _ => { } ,
184+ }
185+ current_id = parent_id;
186+ } ,
187+ _ => return false ,
188+ }
189+ }
190+ }
191+
120192fn check_binding_usages < ' a > ( cx : & LateContext < ' a > , body : & Body < ' a > , hir_id : HirId , binding_info : & BindingInfo < ' a > ) {
121- let mut usages: Vec < UsageInfo < ' a > > = Vec :: new ( ) ;
193+ let mut usages = Vec :: new ( ) ;
122194
123- for_each_expr_without_closures ( body. value , |expr| {
195+ for_each_expr ( cx , body. value , |expr| {
124196 if let ExprKind :: Path ( ref qpath) = expr. kind
197+ && !expr. span . from_expansion ( )
125198 && let Res :: Local ( id) = cx. qpath_res ( qpath, expr. hir_id )
126199 && id == hir_id
127200 {
128201 let parent_id = cx. tcx . parent_hir_id ( expr. hir_id ) ;
129202 let parent = cx. tcx . hir_node ( parent_id) ;
130203
131- if let rustc_hir:: Node :: Expr ( parent_expr) = parent {
132- if let ExprKind :: Cast ( _, _) = parent_expr. kind {
133- let target_ty = cx. typeck_results ( ) . expr_ty ( parent_expr) ;
134- usages. push ( UsageInfo {
135- is_cast : true ,
136- cast_to : Some ( target_ty) ,
137- } ) ;
138- } else {
139- usages. push ( UsageInfo {
140- is_cast : false ,
141- cast_to : None ,
142- } ) ;
204+ let usage = if let rustc_hir:: Node :: Expr ( parent_expr) = parent
205+ && let ExprKind :: Cast ( ..) = parent_expr. kind
206+ && !parent_expr. span . from_expansion ( )
207+ {
208+ UsageInfo {
209+ cast_to : Some ( cx. typeck_results ( ) . expr_ty ( parent_expr) ) ,
210+ in_generic_context : is_cast_in_generic_context ( cx, parent_expr) ,
143211 }
144212 } else {
145- usages. push ( UsageInfo {
146- is_cast : false ,
213+ UsageInfo {
147214 cast_to : None ,
148- } ) ;
149- }
215+ in_generic_context : false ,
216+ }
217+ } ;
218+ usages. push ( usage) ;
150219 }
151220 ControlFlow :: < ( ) > :: Continue ( ( ) )
152221 } ) ;
153222
154- if usages. is_empty ( ) {
155- return ;
156- }
157-
158- if !usages. iter ( ) . all ( |u| u. is_cast ) {
159- return ;
160- }
161-
162- let Some ( first_target) = usages. first ( ) . and_then ( |u| u. cast_to ) else {
223+ let Some ( first_target) = usages
224+ . first ( )
225+ . and_then ( |u| u. cast_to )
226+ . filter ( |& t| t != binding_info. source_ty )
227+ . filter ( |& t| usages. iter ( ) . all ( |u| u. cast_to == Some ( t) && !u. in_generic_context ) )
228+ else {
163229 return ;
164230 } ;
165231
166- if !usages. iter ( ) . all ( |u| u. cast_to == Some ( first_target) ) {
167- return ;
168- }
169-
170- if first_target == binding_info. source_ty {
171- return ;
172- }
173-
174- let suggestion = if binding_info. ty_span . is_some ( ) {
175- format ! ( "{first_target}" )
176- } else {
177- format ! ( ": {first_target}" )
178- } ;
179-
180- let span = binding_info. ty_span . unwrap_or ( binding_info. pat_span ) ;
181- let current_snippet = snippet ( cx, span, "_" ) ;
182-
183232 span_lint_and_sugg (
184233 cx,
185234 NEEDLESS_TYPE_CAST ,
186- span ,
235+ binding_info . ty_span ,
187236 format ! (
188237 "this binding is defined as `{}` but is always cast to `{}`" ,
189238 binding_info. source_ty, first_target
190239 ) ,
191240 "consider defining it as" ,
192- if binding_info. ty_span . is_some ( ) {
193- suggestion
194- } else {
195- format ! ( "{current_snippet}{suggestion}" )
196- } ,
241+ first_target. to_string ( ) ,
197242 Applicability :: MaybeIncorrect ,
198243 ) ;
199244}
0 commit comments