1- use clippy_utils:: diagnostics:: span_lint_and_sugg;
1+ use clippy_utils:: diagnostics:: span_lint_and_then;
2+ use clippy_utils:: sugg:: Sugg ;
23use clippy_utils:: visitors:: { Descend , for_each_expr, for_each_expr_without_closures} ;
34use core:: ops:: ControlFlow ;
5+ use rustc_ast:: ast:: { LitFloatType , LitIntType , LitKind } ;
46use rustc_data_structures:: fx:: FxHashMap ;
57use rustc_errors:: Applicability ;
68use rustc_hir:: def:: { DefKind , Res } ;
@@ -14,6 +16,7 @@ use super::NEEDLESS_TYPE_CAST;
1416struct BindingInfo < ' a > {
1517 source_ty : Ty < ' a > ,
1618 ty_span : Span ,
19+ init : Option < & ' a Expr < ' a > > ,
1720}
1821
1922struct UsageInfo < ' a > {
@@ -73,6 +76,7 @@ fn collect_binding_from_let<'a>(
7376 BindingInfo {
7477 source_ty : ty,
7578 ty_span : ty_hir. span ,
79+ init : Some ( let_expr. init ) ,
7680 } ,
7781 ) ;
7882 }
@@ -103,6 +107,7 @@ fn collect_binding_from_local<'a>(
103107 BindingInfo {
104108 source_ty : ty,
105109 ty_span : ty_hir. span ,
110+ init : let_stmt. init ,
106111 } ,
107112 ) ;
108113 }
@@ -182,12 +187,7 @@ fn is_generic_res(cx: &LateContext<'_>, res: Res) -> bool {
182187 . iter ( )
183188 . any ( |p| p. kind . is_ty_or_const ( ) )
184189 } ;
185- match res {
186- Res :: Def ( DefKind :: Fn | DefKind :: AssocFn , def_id) => has_type_params ( def_id) ,
187- // Ctor → Variant → ADT: constructor's parent is variant, variant's parent is the ADT
188- Res :: Def ( DefKind :: Ctor ( ..) , def_id) => has_type_params ( cx. tcx . parent ( cx. tcx . parent ( def_id) ) ) ,
189- _ => false ,
190- }
190+ cx. tcx . res_generics_def_id ( res) . is_some_and ( has_type_params)
191191}
192192
193193fn is_cast_in_generic_context < ' a > ( cx : & LateContext < ' a > , cast_expr : & Expr < ' a > ) -> bool {
@@ -234,6 +234,18 @@ fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) ->
234234 }
235235}
236236
237+ fn can_coerce_to_target_type ( expr : & Expr < ' _ > ) -> bool {
238+ match expr. kind {
239+ ExprKind :: Lit ( lit) => matches ! (
240+ lit. node,
241+ LitKind :: Int ( _, LitIntType :: Unsuffixed ) | LitKind :: Float ( _, LitFloatType :: Unsuffixed )
242+ ) ,
243+ ExprKind :: Unary ( rustc_hir:: UnOp :: Neg , inner) => can_coerce_to_target_type ( inner) ,
244+ ExprKind :: Binary ( _, lhs, rhs) => can_coerce_to_target_type ( lhs) && can_coerce_to_target_type ( rhs) ,
245+ _ => false ,
246+ }
247+ }
248+
237249fn check_binding_usages < ' a > ( cx : & LateContext < ' a > , body : & Body < ' a > , hir_id : HirId , binding_info : & BindingInfo < ' a > ) {
238250 let mut usages = Vec :: new ( ) ;
239251
@@ -274,16 +286,48 @@ fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId
274286 return ;
275287 } ;
276288
277- span_lint_and_sugg (
289+ // Don't lint if there's exactly one use and the initializer cannot be coerced to the
290+ // target type (i.e., would require an explicit cast). In such cases, the fix would add
291+ // a cast to the initializer rather than eliminating one - the cast isn't truly "needless."
292+ // See: https://github.com/rust-lang/rust-clippy/issues/16240
293+ if usages. len ( ) == 1
294+ && binding_info
295+ . init
296+ . is_some_and ( |init| !can_coerce_to_target_type ( init) && !init. span . from_expansion ( ) )
297+ {
298+ return ;
299+ }
300+
301+ span_lint_and_then (
278302 cx,
279303 NEEDLESS_TYPE_CAST ,
280304 binding_info. ty_span ,
281305 format ! (
282306 "this binding is defined as `{}` but is always cast to `{}`" ,
283307 binding_info. source_ty, first_target
284308 ) ,
285- "consider defining it as" ,
286- first_target. to_string ( ) ,
287- Applicability :: MaybeIncorrect ,
309+ |diag| {
310+ if let Some ( init) = binding_info
311+ . init
312+ . filter ( |i| !can_coerce_to_target_type ( i) && !i. span . from_expansion ( ) )
313+ {
314+ let sugg = Sugg :: hir ( cx, init, ".." ) . as_ty ( first_target) ;
315+ diag. multipart_suggestion (
316+ format ! ( "consider defining it as `{first_target}` and casting the initializer" ) ,
317+ vec ! [
318+ ( binding_info. ty_span, first_target. to_string( ) ) ,
319+ ( init. span, sugg. to_string( ) ) ,
320+ ] ,
321+ Applicability :: MachineApplicable ,
322+ ) ;
323+ } else {
324+ diag. span_suggestion (
325+ binding_info. ty_span ,
326+ "consider defining it as" ,
327+ first_target. to_string ( ) ,
328+ Applicability :: MachineApplicable ,
329+ ) ;
330+ }
331+ } ,
288332 ) ;
289333}
0 commit comments