@@ -2,25 +2,79 @@ use clippy_utils::diagnostics::span_lint_and_then;
22use clippy_utils:: get_parent_expr;
33use clippy_utils:: ty:: implements_trait;
44use rustc_errors:: Applicability ;
5- use rustc_hir:: { Expr , ExprKind } ;
5+ use rustc_hir:: { Expr , ExprKind , QPath } ;
66use rustc_lint:: LateContext ;
77use rustc_middle:: ty;
88use rustc_middle:: ty:: print:: with_forced_trimmed_paths;
99use rustc_span:: { sym, Span } ;
1010
1111use super :: UNNECESSARY_FALLIBLE_CONVERSIONS ;
1212
13+ #[ derive( Copy , Clone ) ]
14+ enum SpansKind {
15+ TraitFn { trait_span : Span , fn_span : Span } ,
16+ Fn { fn_span : Span } ,
17+ }
18+
1319/// What function is being called and whether that call is written as a method call or a function
1420/// call
1521#[ derive( Copy , Clone ) ]
1622#[ expect( clippy:: enum_variant_names) ]
1723enum FunctionKind {
1824 /// `T::try_from(U)`
19- TryFromFunction ,
25+ TryFromFunction ( Option < SpansKind > ) ,
2026 /// `t.try_into()`
2127 TryIntoMethod ,
2228 /// `U::try_into(t)`
23- TryIntoFunction ,
29+ TryIntoFunction ( Option < SpansKind > ) ,
30+ }
31+
32+ impl FunctionKind {
33+ fn appl_sugg ( & self , parent_unwrap_call : Option < Span > , primary_span : Span ) -> ( Applicability , Vec < ( Span , String ) > ) {
34+ let Some ( unwrap_span) = parent_unwrap_call else {
35+ return ( Applicability :: Unspecified , self . default_sugg ( primary_span) ) ;
36+ } ;
37+
38+ match & self {
39+ FunctionKind :: TryFromFunction ( None ) | FunctionKind :: TryIntoFunction ( None ) => {
40+ ( Applicability :: Unspecified , self . default_sugg ( primary_span) )
41+ } ,
42+ _ => (
43+ Applicability :: MachineApplicable ,
44+ self . machine_applicable_sugg ( primary_span, unwrap_span) ,
45+ ) ,
46+ }
47+ }
48+
49+ fn default_sugg ( & self , primary_span : Span ) -> Vec < ( Span , String ) > {
50+ let replacement = match * self {
51+ FunctionKind :: TryFromFunction ( _) => "From::from" ,
52+ FunctionKind :: TryIntoFunction ( _) => "Into::into" ,
53+ FunctionKind :: TryIntoMethod => "into" ,
54+ } ;
55+
56+ vec ! [ ( primary_span, String :: from( replacement) ) ]
57+ }
58+
59+ fn machine_applicable_sugg ( & self , primary_span : Span , unwrap_span : Span ) -> Vec < ( Span , String ) > {
60+ let ( trait_name, fn_name) = match self {
61+ FunctionKind :: TryFromFunction ( _) => ( "From" . to_owned ( ) , "from" . to_owned ( ) ) ,
62+ FunctionKind :: TryIntoFunction ( _) | FunctionKind :: TryIntoMethod => ( "Into" . to_owned ( ) , "into" . to_owned ( ) ) ,
63+ } ;
64+
65+ let mut sugg = match * self {
66+ FunctionKind :: TryFromFunction ( Some ( spans) ) | FunctionKind :: TryIntoFunction ( Some ( spans) ) => match spans {
67+ SpansKind :: TraitFn { trait_span, fn_span } => vec ! [ ( trait_span, trait_name) , ( fn_span, fn_name) ] ,
68+ SpansKind :: Fn { fn_span } => vec ! [ ( fn_span, fn_name) ] ,
69+ } ,
70+ FunctionKind :: TryIntoMethod => vec ! [ ( primary_span, fn_name) ] ,
71+ // Or the suggestion is not machine-applicable
72+ _ => unreachable ! ( ) ,
73+ } ;
74+
75+ sugg. push ( ( unwrap_span, String :: new ( ) ) ) ;
76+ sugg
77+ }
2478}
2579
2680fn check < ' tcx > (
@@ -35,8 +89,8 @@ fn check<'tcx>(
3589 && self_ty != other_ty
3690 && let Some ( self_ty) = self_ty. as_type ( )
3791 && let Some ( from_into_trait) = cx. tcx . get_diagnostic_item ( match kind {
38- FunctionKind :: TryFromFunction => sym:: From ,
39- FunctionKind :: TryIntoMethod | FunctionKind :: TryIntoFunction => sym:: Into ,
92+ FunctionKind :: TryFromFunction ( _ ) => sym:: From ,
93+ FunctionKind :: TryIntoMethod | FunctionKind :: TryIntoFunction ( _ ) => sym:: Into ,
4094 } )
4195 // If `T: TryFrom<U>` and `T: From<U>` both exist, then that means that the `TryFrom`
4296 // _must_ be from the blanket impl and cannot have been manually implemented
@@ -45,49 +99,37 @@ fn check<'tcx>(
4599 && implements_trait ( cx, self_ty, from_into_trait, & [ other_ty] )
46100 && let Some ( other_ty) = other_ty. as_type ( )
47101 {
102+ // Extend the span to include the unwrap/expect call:
103+ // `foo.try_into().expect("..")`
104+ // ^^^^^^^^^^^^^^^^^^^^^^^
105+ //
106+ // `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
107+ // so that can be machine-applicable
48108 let parent_unwrap_call = get_parent_expr ( cx, expr) . and_then ( |parent| {
49109 if let ExprKind :: MethodCall ( path, .., span) = parent. kind
50110 && let sym:: unwrap | sym:: expect = path. ident . name
51111 {
52- Some ( span)
112+ // include `.` before `unwrap`/`expect`
113+ Some ( span. with_lo ( expr. span . hi ( ) ) )
53114 } else {
54115 None
55116 }
56117 } ) ;
57- let ( source_ty, target_ty, sugg, span, applicability) = match kind {
58- FunctionKind :: TryIntoMethod if let Some ( unwrap_span) = parent_unwrap_call => {
59- // Extend the span to include the unwrap/expect call:
60- // `foo.try_into().expect("..")`
61- // ^^^^^^^^^^^^^^^^^^^^^^^
62- //
63- // `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
64- // so that can be machine-applicable
65-
66- (
67- self_ty,
68- other_ty,
69- "into()" ,
70- primary_span. with_hi ( unwrap_span. hi ( ) ) ,
71- Applicability :: MachineApplicable ,
72- )
73- } ,
74- FunctionKind :: TryFromFunction => (
75- other_ty,
76- self_ty,
77- "From::from" ,
78- primary_span,
79- Applicability :: Unspecified ,
80- ) ,
81- FunctionKind :: TryIntoFunction => (
82- self_ty,
83- other_ty,
84- "Into::into" ,
85- primary_span,
86- Applicability :: Unspecified ,
87- ) ,
88- FunctionKind :: TryIntoMethod => ( self_ty, other_ty, "into" , primary_span, Applicability :: Unspecified ) ,
118+
119+ // If there is an unwrap/expect call, extend the span to include the call
120+ let span = if let Some ( unwrap_call) = parent_unwrap_call {
121+ primary_span. with_hi ( unwrap_call. hi ( ) )
122+ } else {
123+ primary_span
124+ } ;
125+
126+ let ( source_ty, target_ty) = match kind {
127+ FunctionKind :: TryIntoMethod | FunctionKind :: TryIntoFunction ( _) => ( self_ty, other_ty) ,
128+ FunctionKind :: TryFromFunction ( _) => ( other_ty, self_ty) ,
89129 } ;
90130
131+ let ( applicability, sugg) = kind. appl_sugg ( parent_unwrap_call, primary_span) ;
132+
91133 span_lint_and_then (
92134 cx,
93135 UNNECESSARY_FALLIBLE_CONVERSIONS ,
@@ -97,7 +139,7 @@ fn check<'tcx>(
97139 with_forced_trimmed_paths ! ( {
98140 diag. note( format!( "converting `{source_ty}` to `{target_ty}` cannot fail" ) ) ;
99141 } ) ;
100- diag. span_suggestion ( span , "use" , sugg, applicability) ;
142+ diag. multipart_suggestion ( "use" , sugg, applicability) ;
101143 } ,
102144 ) ;
103145 }
@@ -125,13 +167,30 @@ pub(super) fn check_function(cx: &LateContext<'_>, expr: &Expr<'_>, callee: &Exp
125167 && let Some ( item_def_id) = cx. qpath_res ( qpath, callee. hir_id ) . opt_def_id ( )
126168 && let Some ( trait_def_id) = cx. tcx . trait_of_item ( item_def_id)
127169 {
170+ let qpath_spans = match qpath {
171+ QPath :: Resolved ( _, path) => {
172+ if let [ trait_seg, fn_seg] = path. segments {
173+ Some ( SpansKind :: TraitFn {
174+ trait_span : trait_seg. ident . span ,
175+ fn_span : fn_seg. ident . span ,
176+ } )
177+ } else {
178+ None
179+ }
180+ } ,
181+ QPath :: TypeRelative ( _, seg) => Some ( SpansKind :: Fn {
182+ fn_span : seg. ident . span ,
183+ } ) ,
184+ QPath :: LangItem ( _, _) => unreachable ! ( "`TryFrom` and `TryInto` are not lang items" ) ,
185+ } ;
186+
128187 check (
129188 cx,
130189 expr,
131190 cx. typeck_results ( ) . node_args ( callee. hir_id ) ,
132191 match cx. tcx . get_diagnostic_name ( trait_def_id) {
133- Some ( sym:: TryFrom ) => FunctionKind :: TryFromFunction ,
134- Some ( sym:: TryInto ) => FunctionKind :: TryIntoFunction ,
192+ Some ( sym:: TryFrom ) => FunctionKind :: TryFromFunction ( qpath_spans ) ,
193+ Some ( sym:: TryInto ) => FunctionKind :: TryIntoFunction ( qpath_spans ) ,
135194 _ => return ,
136195 } ,
137196 callee. span ,
0 commit comments