1- use clippy_utils:: { diagnostics:: span_lint_and_help, is_from_proc_macro, path_to_local} ;
2- use rustc_hir:: * ;
1+ use clippy_utils:: {
2+ diagnostics:: span_lint_and_help,
3+ is_from_proc_macro,
4+ msrvs:: { self , Msrv } ,
5+ path_to_local,
6+ } ;
7+ use itertools:: Itertools ;
8+ use rustc_hir:: { Expr , ExprKind , Node , Pat } ;
39use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
410use rustc_middle:: { lint:: in_external_macro, ty} ;
5- use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
11+ use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
12+ use std:: iter:: once;
613
714declare_clippy_lint ! {
815 /// ### What it does
16+ /// Checks for tuple<=>array conversions that are not done with `.into()`.
917 ///
1018 /// ### Why is this bad?
19+ /// It's overly complex. `.into()` works for tuples<=>arrays with less than 13 elements and
20+ /// conveys the intent a lot better, while also leaving less room for bugs!
1121 ///
1222 /// ### Example
1323 /// ```rust,ignore
@@ -22,16 +32,23 @@ declare_clippy_lint! {
2232 #[ clippy:: version = "1.72.0" ]
2333 pub TUPLE_ARRAY_CONVERSIONS ,
2434 complexity,
25- "default lint description"
35+ "checks for tuple<=>array conversions that are not done with `.into()`"
36+ }
37+ impl_lint_pass ! ( TupleArrayConversions => [ TUPLE_ARRAY_CONVERSIONS ] ) ;
38+
39+ #[ derive( Clone ) ]
40+ pub struct TupleArrayConversions {
41+ pub msrv : Msrv ,
2642}
27- declare_lint_pass ! ( TupleArrayConversions => [ TUPLE_ARRAY_CONVERSIONS ] ) ;
2843
2944impl LateLintPass < ' _ > for TupleArrayConversions {
3045 fn check_expr < ' tcx > ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
31- if !in_external_macro ( cx. sess ( ) , expr. span ) {
46+ if !in_external_macro ( cx. sess ( ) , expr. span ) && self . msrv . meets ( msrvs :: TUPLE_ARRAY_CONVERSIONS ) {
3247 _ = check_array ( cx, expr) || check_tuple ( cx, expr) ;
3348 }
3449 }
50+
51+ extract_msrv_attr ! ( LateContext ) ;
3552}
3653
3754fn check_array < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> bool {
@@ -42,15 +59,22 @@ fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
4259 return false ;
4360 }
4461
45- if let Some ( locals) = path_to_locals ( cx, elements)
46- && locals. iter ( ) . all ( |local| {
47- matches ! (
48- local,
49- Node :: Pat ( pat) if matches!(
50- cx. typeck_results( ) . pat_ty( backtrack_pat( cx, pat) ) . peel_refs( ) . kind( ) ,
51- ty:: Tuple ( _) ,
52- ) ,
53- )
62+ if let Some ( locals) = path_to_locals ( cx, & elements. iter ( ) . collect_vec ( ) )
63+ && let [ first, rest @ ..] = & * locals
64+ && let Node :: Pat ( first_pat) = first
65+ && let first_id = parent_pat ( cx, first_pat) . hir_id
66+ && rest. iter ( ) . chain ( once ( first) ) . all ( |local| {
67+ if let Node :: Pat ( pat) = local
68+ && let parent = parent_pat ( cx, pat)
69+ && parent. hir_id == first_id
70+ {
71+ return matches ! (
72+ cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
73+ ty:: Tuple ( len) if len. len( ) == elements. len( )
74+ ) ;
75+ }
76+
77+ false
5478 } )
5579 {
5680 return emit_lint ( cx, expr, ToType :: Array ) ;
@@ -66,15 +90,22 @@ fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
6690 None
6791 } )
6892 . collect :: < Option < Vec < & Expr < ' _ > > > > ( )
69- && let Some ( locals) = path_to_locals ( cx, elements)
70- && locals. iter ( ) . all ( |local| {
71- matches ! (
72- local,
73- Node :: Pat ( pat) if matches!(
74- cx. typeck_results( ) . pat_ty( backtrack_pat( cx, pat) ) . peel_refs( ) . kind( ) ,
75- ty:: Tuple ( _) ,
76- ) ,
77- )
93+ && let Some ( locals) = path_to_locals ( cx, & elements)
94+ && let [ first, rest @ ..] = & * locals
95+ && let Node :: Pat ( first_pat) = first
96+ && let first_id = parent_pat ( cx, first_pat) . hir_id
97+ && rest. iter ( ) . chain ( once ( first) ) . all ( |local| {
98+ if let Node :: Pat ( pat) = local
99+ && let parent = parent_pat ( cx, pat)
100+ && parent. hir_id == first_id
101+ {
102+ return matches ! (
103+ cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
104+ ty:: Tuple ( len) if len. len( ) == elements. len( )
105+ ) ;
106+ }
107+
108+ false
78109 } )
79110 {
80111 return emit_lint ( cx, expr, ToType :: Array ) ;
@@ -83,22 +114,31 @@ fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
83114 false
84115}
85116
117+ #[ expect( clippy:: cast_possible_truncation) ]
86118fn check_tuple < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> bool {
87119 let ExprKind :: Tup ( elements) = expr. kind else {
88120 return false ;
89121 } ;
90122 if !( 1 ..=12 ) . contains ( & elements. len ( ) ) {
91123 return false ;
92124 } ;
93- if let Some ( locals) = path_to_locals ( cx, elements)
94- && locals. iter ( ) . all ( |local| {
95- matches ! (
96- local,
97- Node :: Pat ( pat) if matches!(
98- cx. typeck_results( ) . pat_ty( backtrack_pat( cx, pat) ) . peel_refs( ) . kind( ) ,
99- ty:: Array ( _, _) ,
100- ) ,
101- )
125+
126+ if let Some ( locals) = path_to_locals ( cx, & elements. iter ( ) . collect_vec ( ) )
127+ && let [ first, rest @ ..] = & * locals
128+ && let Node :: Pat ( first_pat) = first
129+ && let first_id = parent_pat ( cx, first_pat) . hir_id
130+ && rest. iter ( ) . chain ( once ( first) ) . all ( |local| {
131+ if let Node :: Pat ( pat) = local
132+ && let parent = parent_pat ( cx, pat)
133+ && parent. hir_id == first_id
134+ {
135+ return matches ! (
136+ cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
137+ ty:: Array ( _, len) if len. eval_target_usize( cx. tcx, cx. param_env) as usize == elements. len( )
138+ ) ;
139+ }
140+
141+ false
102142 } )
103143 {
104144 return emit_lint ( cx, expr, ToType :: Tuple ) ;
@@ -114,15 +154,22 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
114154 None
115155 } )
116156 . collect :: < Option < Vec < & Expr < ' _ > > > > ( )
117- && let Some ( locals) = path_to_locals ( cx, elements. clone ( ) )
118- && locals. iter ( ) . all ( |local| {
119- matches ! (
120- local,
121- Node :: Pat ( pat) if cx. typeck_results( )
122- . pat_ty( backtrack_pat( cx, pat) )
123- . peel_refs( )
124- . is_array( )
125- )
157+ && let Some ( locals) = path_to_locals ( cx, & elements)
158+ && let [ first, rest @ ..] = & * locals
159+ && let Node :: Pat ( first_pat) = first
160+ && let first_id = parent_pat ( cx, first_pat) . hir_id
161+ && rest. iter ( ) . chain ( once ( first) ) . all ( |local| {
162+ if let Node :: Pat ( pat) = local
163+ && let parent = parent_pat ( cx, pat)
164+ && parent. hir_id == first_id
165+ {
166+ return matches ! (
167+ cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
168+ ty:: Array ( _, len) if len. eval_target_usize( cx. tcx, cx. param_env) as usize == elements. len( )
169+ ) ;
170+ }
171+
172+ false
126173 } )
127174 {
128175 return emit_lint ( cx, expr, ToType :: Tuple ) ;
@@ -132,7 +179,7 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
132179}
133180
134181/// Walks up the `Pat` until it's reached the final containing `Pat`.
135- fn backtrack_pat < ' tcx > ( cx : & LateContext < ' tcx > , start : & ' tcx Pat < ' tcx > ) -> & ' tcx Pat < ' tcx > {
182+ fn parent_pat < ' tcx > ( cx : & LateContext < ' tcx > , start : & ' tcx Pat < ' tcx > ) -> & ' tcx Pat < ' tcx > {
136183 let mut end = start;
137184 for ( _, node) in cx. tcx . hir ( ) . parent_iter ( start. hir_id ) {
138185 if let Node :: Pat ( pat) = node {
@@ -144,12 +191,9 @@ fn backtrack_pat<'tcx>(cx: &LateContext<'tcx>, start: &'tcx Pat<'tcx>) -> &'tcx
144191 end
145192}
146193
147- fn path_to_locals < ' tcx > (
148- cx : & LateContext < ' tcx > ,
149- exprs : impl IntoIterator < Item = & ' tcx Expr < ' tcx > > ,
150- ) -> Option < Vec < Node < ' tcx > > > {
194+ fn path_to_locals < ' tcx > ( cx : & LateContext < ' tcx > , exprs : & [ & ' tcx Expr < ' tcx > ] ) -> Option < Vec < Node < ' tcx > > > {
151195 exprs
152- . into_iter ( )
196+ . iter ( )
153197 . map ( |element| path_to_local ( element) . and_then ( |local| cx. tcx . hir ( ) . find ( local) ) )
154198 . collect ( )
155199}
@@ -161,12 +205,19 @@ enum ToType {
161205}
162206
163207impl ToType {
164- fn help ( self ) -> & ' static str {
208+ fn msg ( self ) -> & ' static str {
165209 match self {
166210 ToType :: Array => "it looks like you're trying to convert a tuple to an array" ,
167211 ToType :: Tuple => "it looks like you're trying to convert an array to a tuple" ,
168212 }
169213 }
214+
215+ fn help ( self ) -> & ' static str {
216+ match self {
217+ ToType :: Array => "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed" ,
218+ ToType :: Tuple => "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed" ,
219+ }
220+ }
170221}
171222
172223fn emit_lint < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , to_type : ToType ) -> bool {
@@ -175,9 +226,9 @@ fn emit_lint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, to_type: ToTy
175226 cx,
176227 TUPLE_ARRAY_CONVERSIONS ,
177228 expr. span ,
178- to_type. help ( ) ,
229+ to_type. msg ( ) ,
179230 None ,
180- "use `.into()` instead" ,
231+ to_type . help ( ) ,
181232 ) ;
182233
183234 return true ;
0 commit comments