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 } ;
3
9
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
4
10
use 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;
6
13
7
14
declare_clippy_lint ! {
8
15
/// ### What it does
16
+ /// Checks for tuple<=>array conversions that are not done with `.into()`.
9
17
///
10
18
/// ### 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!
11
21
///
12
22
/// ### Example
13
23
/// ```rust,ignore
@@ -22,16 +32,23 @@ declare_clippy_lint! {
22
32
#[ clippy:: version = "1.72.0" ]
23
33
pub TUPLE_ARRAY_CONVERSIONS ,
24
34
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 ,
26
42
}
27
- declare_lint_pass ! ( TupleArrayConversions => [ TUPLE_ARRAY_CONVERSIONS ] ) ;
28
43
29
44
impl LateLintPass < ' _ > for TupleArrayConversions {
30
45
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 ) {
32
47
_ = check_array ( cx, expr) || check_tuple ( cx, expr) ;
33
48
}
34
49
}
50
+
51
+ extract_msrv_attr ! ( LateContext ) ;
35
52
}
36
53
37
54
fn 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 {
42
59
return false ;
43
60
}
44
61
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
54
78
} )
55
79
{
56
80
return emit_lint ( cx, expr, ToType :: Array ) ;
@@ -66,15 +90,22 @@ fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
66
90
None
67
91
} )
68
92
. 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
78
109
} )
79
110
{
80
111
return emit_lint ( cx, expr, ToType :: Array ) ;
@@ -83,22 +114,31 @@ fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
83
114
false
84
115
}
85
116
117
+ #[ expect( clippy:: cast_possible_truncation) ]
86
118
fn check_tuple < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> bool {
87
119
let ExprKind :: Tup ( elements) = expr. kind else {
88
120
return false ;
89
121
} ;
90
122
if !( 1 ..=12 ) . contains ( & elements. len ( ) ) {
91
123
return false ;
92
124
} ;
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
102
142
} )
103
143
{
104
144
return emit_lint ( cx, expr, ToType :: Tuple ) ;
@@ -114,15 +154,22 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
114
154
None
115
155
} )
116
156
. 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
126
173
} )
127
174
{
128
175
return emit_lint ( cx, expr, ToType :: Tuple ) ;
@@ -132,7 +179,7 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
132
179
}
133
180
134
181
/// 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 > {
136
183
let mut end = start;
137
184
for ( _, node) in cx. tcx . hir ( ) . parent_iter ( start. hir_id ) {
138
185
if let Node :: Pat ( pat) = node {
@@ -144,12 +191,9 @@ fn backtrack_pat<'tcx>(cx: &LateContext<'tcx>, start: &'tcx Pat<'tcx>) -> &'tcx
144
191
end
145
192
}
146
193
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 > > > {
151
195
exprs
152
- . into_iter ( )
196
+ . iter ( )
153
197
. map ( |element| path_to_local ( element) . and_then ( |local| cx. tcx . hir ( ) . find ( local) ) )
154
198
. collect ( )
155
199
}
@@ -161,12 +205,19 @@ enum ToType {
161
205
}
162
206
163
207
impl ToType {
164
- fn help ( self ) -> & ' static str {
208
+ fn msg ( self ) -> & ' static str {
165
209
match self {
166
210
ToType :: Array => "it looks like you're trying to convert a tuple to an array" ,
167
211
ToType :: Tuple => "it looks like you're trying to convert an array to a tuple" ,
168
212
}
169
213
}
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
+ }
170
221
}
171
222
172
223
fn 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
175
226
cx,
176
227
TUPLE_ARRAY_CONVERSIONS ,
177
228
expr. span ,
178
- to_type. help ( ) ,
229
+ to_type. msg ( ) ,
179
230
None ,
180
- "use `.into()` instead" ,
231
+ to_type . help ( ) ,
181
232
) ;
182
233
183
234
return true ;
0 commit comments