11use clippy_utils:: diagnostics:: span_lint_and_help;
22use clippy_utils:: msrvs:: { self , Msrv } ;
3+ use clippy_utils:: visitors:: for_each_local_use_after_expr;
34use clippy_utils:: { is_from_proc_macro, path_to_local} ;
5+ use itertools:: Itertools ;
46use rustc_ast:: LitKind ;
5- use rustc_hir:: { Expr , ExprKind , HirId , Node , Pat } ;
7+ use rustc_hir:: { Expr , ExprKind , Node , PatKind } ;
68use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
79use rustc_middle:: lint:: in_external_macro;
8- use rustc_middle:: ty;
10+ use rustc_middle:: ty:: { self , Ty } ;
911use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
1012use std:: iter:: once;
13+ use std:: ops:: ControlFlow ;
1114
1215declare_clippy_lint ! {
1316 /// ### What it does
1417 /// Checks for tuple<=>array conversions that are not done with `.into()`.
1518 ///
1619 /// ### Why is this bad?
17- /// It may be unnecessary complexity. `.into()` works for converting tuples
18- /// <=> arrays of up to 12 elements and may convey intent more clearly.
20+ /// It may be unnecessary complexity. `.into()` works for converting tuples<=> arrays of up to
21+ /// 12 elements and conveys the intent more clearly, while also leaving less room for hard to
22+ /// spot bugs!
23+ ///
24+ /// ### Known issues
25+ /// The suggested code may hide potential asymmetry in some cases. See
26+ /// [#11085](https://github.com/rust-lang/rust-clippy/issues/11085) for more info.
1927 ///
2028 /// ### Example
2129 /// ```rust,ignore
@@ -41,130 +49,152 @@ pub struct TupleArrayConversions {
4149
4250impl LateLintPass < ' _ > for TupleArrayConversions {
4351 fn check_expr < ' tcx > ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
44- if !in_external_macro ( cx. sess ( ) , expr. span ) && self . msrv . meets ( msrvs:: TUPLE_ARRAY_CONVERSIONS ) {
45- match expr. kind {
46- ExprKind :: Array ( elements) if ( 1 ..=12 ) . contains ( & elements. len ( ) ) => check_array ( cx, expr, elements) ,
47- ExprKind :: Tup ( elements) if ( 1 ..=12 ) . contains ( & elements. len ( ) ) => check_tuple ( cx, expr, elements) ,
48- _ => { } ,
49- }
52+ if in_external_macro ( cx. sess ( ) , expr. span ) || !self . msrv . meets ( msrvs:: TUPLE_ARRAY_CONVERSIONS ) {
53+ return ;
54+ }
55+
56+ match expr. kind {
57+ ExprKind :: Array ( elements) if ( 1 ..=12 ) . contains ( & elements. len ( ) ) => check_array ( cx, expr, elements) ,
58+ ExprKind :: Tup ( elements) if ( 1 ..=12 ) . contains ( & elements. len ( ) ) => check_tuple ( cx, expr, elements) ,
59+ _ => { } ,
5060 }
5161 }
5262
5363 extract_msrv_attr ! ( LateContext ) ;
5464}
5565
56- #[ expect(
57- clippy:: blocks_in_if_conditions,
58- reason = "not a FP, but this is much easier to understand"
59- ) ]
6066fn check_array < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , elements : & ' tcx [ Expr < ' tcx > ] ) {
61- if should_lint (
62- cx,
63- elements,
64- // This is cursed.
65- Some ,
66- |( first_id, 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
78- } ,
79- ) || should_lint (
80- cx,
81- elements,
82- |( i, expr) | {
83- if let ExprKind :: Field ( path, field) = expr. kind && field. as_str ( ) == i. to_string ( ) {
84- return Some ( ( i, path) ) ;
85- } ;
86-
87- None
88- } ,
89- |( first_id, local) | {
90- if let Node :: Pat ( pat) = local
91- && let parent = parent_pat ( cx, pat)
92- && parent. hir_id == first_id
93- {
94- return matches ! (
95- cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
96- ty:: Tuple ( len) if len. len( ) == elements. len( )
97- ) ;
98- }
67+ let ( ty:: Array ( ty, _) | ty:: Slice ( ty) ) = cx. typeck_results ( ) . expr_ty ( expr) . kind ( ) else {
68+ unreachable ! ( "`expr` must be an array or slice due to `ExprKind::Array`" ) ;
69+ } ;
70+
71+ if let [ first, ..] = elements
72+ && let Some ( locals) = ( match first. kind {
73+ ExprKind :: Field ( _, _) => elements
74+ . iter ( )
75+ . enumerate ( )
76+ . map ( |( i, f) | -> Option < & ' tcx Expr < ' tcx > > {
77+ let ExprKind :: Field ( lhs, ident) = f. kind else {
78+ return None ;
79+ } ;
80+ ( ident. name . as_str ( ) == i. to_string ( ) ) . then_some ( lhs)
81+ } )
82+ . collect :: < Option < Vec < _ > > > ( ) ,
83+ ExprKind :: Path ( _) => Some ( elements. iter ( ) . collect ( ) ) ,
84+ _ => None ,
85+ } )
86+ && all_bindings_are_for_conv ( cx, & [ * ty] , expr, elements, & locals, ToType :: Array )
87+ && !is_from_proc_macro ( cx, expr)
88+ {
89+ span_lint_and_help (
90+ cx,
91+ TUPLE_ARRAY_CONVERSIONS ,
92+ expr. span ,
93+ "it looks like you're trying to convert a tuple to an array" ,
94+ None ,
95+ "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed" ,
96+ ) ;
97+ }
98+ }
9999
100- false
101- } ,
102- ) {
103- emit_lint ( cx, expr, ToType :: Array ) ;
100+ fn check_tuple < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , elements : & ' tcx [ Expr < ' tcx > ] ) {
101+ if let ty:: Tuple ( tys) = cx. typeck_results ( ) . expr_ty ( expr) . kind ( )
102+ && let [ first, ..] = elements
103+ // Fix #11100
104+ && tys. iter ( ) . all_equal ( )
105+ && let Some ( locals) = ( match first. kind {
106+ ExprKind :: Index ( _, _) => elements
107+ . iter ( )
108+ . enumerate ( )
109+ . map ( |( i, i_expr) | -> Option < & ' tcx Expr < ' tcx > > {
110+ if let ExprKind :: Index ( lhs, index) = i_expr. kind
111+ && let ExprKind :: Lit ( lit) = index. kind
112+ && let LitKind :: Int ( val, _) = lit. node
113+ {
114+ return ( val == i as u128 ) . then_some ( lhs) ;
115+ } ;
116+
117+ None
118+ } )
119+ . collect :: < Option < Vec < _ > > > ( ) ,
120+ ExprKind :: Path ( _) => Some ( elements. iter ( ) . collect ( ) ) ,
121+ _ => None ,
122+ } )
123+ && all_bindings_are_for_conv ( cx, tys, expr, elements, & locals, ToType :: Tuple )
124+ && !is_from_proc_macro ( cx, expr)
125+ {
126+ span_lint_and_help (
127+ cx,
128+ TUPLE_ARRAY_CONVERSIONS ,
129+ expr. span ,
130+ "it looks like you're trying to convert an array to a tuple" ,
131+ None ,
132+ "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed" ,
133+ ) ;
104134 }
105135}
106136
107- #[ expect(
108- clippy:: blocks_in_if_conditions,
109- reason = "not a FP, but this is much easier to understand"
110- ) ]
137+ /// Checks that every binding in `elements` comes from the same parent `Pat` with the kind if there
138+ /// is a parent `Pat`. Returns false in any of the following cases:
139+ /// * `kind` does not match `pat.kind`
140+ /// * one or more elements in `elements` is not a binding
141+ /// * one or more bindings does not have the same parent `Pat`
142+ /// * one or more bindings are used after `expr`
143+ /// * the bindings do not all have the same type
111144#[ expect( clippy:: cast_possible_truncation) ]
112- fn check_tuple < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , elements : & ' tcx [ Expr < ' tcx > ] ) {
113- if should_lint ( cx, elements, Some , |( first_id, local) | {
114- if let Node :: Pat ( pat) = local
115- && let parent = parent_pat ( cx, pat)
116- && parent. hir_id == first_id
117- {
118- return matches ! (
119- cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
120- ty:: Array ( _, len) if len. eval_target_usize( cx. tcx, cx. param_env) as usize == elements. len( )
121- ) ;
145+ fn all_bindings_are_for_conv < ' tcx > (
146+ cx : & LateContext < ' tcx > ,
147+ final_tys : & [ Ty < ' tcx > ] ,
148+ expr : & Expr < ' _ > ,
149+ elements : & [ Expr < ' _ > ] ,
150+ locals : & [ & Expr < ' _ > ] ,
151+ kind : ToType ,
152+ ) -> bool {
153+ let Some ( locals) = locals. iter ( ) . map ( |e| path_to_local ( e) ) . collect :: < Option < Vec < _ > > > ( ) else {
154+ return false ;
155+ } ;
156+ let Some ( local_parents) = locals
157+ . iter ( )
158+ . map ( |& l| cx. tcx . hir ( ) . find_parent ( l) )
159+ . collect :: < Option < Vec < _ > > > ( )
160+ else {
161+ return false ;
162+ } ;
163+
164+ local_parents
165+ . iter ( )
166+ . map ( |node| match node {
167+ Node :: Pat ( pat) => kind. eq ( & pat. kind ) . then_some ( pat. hir_id ) ,
168+ Node :: Local ( l) => Some ( l. hir_id ) ,
169+ _ => None ,
170+ } )
171+ . all_equal ( )
172+ // Fix #11124, very convenient utils function! ❤️
173+ && locals
174+ . iter ( )
175+ . all ( |& l| for_each_local_use_after_expr ( cx, l, expr. hir_id , |_| ControlFlow :: Break :: < ( ) > ( ( ) ) ) . is_continue ( ) )
176+ && local_parents. first ( ) . is_some_and ( |node| {
177+ let Some ( ty) = match node {
178+ Node :: Pat ( pat) => Some ( pat. hir_id ) ,
179+ Node :: Local ( l) => Some ( l. hir_id ) ,
180+ _ => None ,
122181 }
123-
124- false
125- } ) || should_lint (
126- cx,
127- elements,
128- |( i, expr) | {
129- if let ExprKind :: Index ( path, index) = expr. kind
130- && let ExprKind :: Lit ( lit) = index. kind
131- && let LitKind :: Int ( val, _) = lit. node
132- && val as usize == i
133- {
134- return Some ( ( i, path) ) ;
182+ . map ( |hir_id| cx. typeck_results ( ) . node_type ( hir_id) ) else {
183+ return false ;
135184 } ;
136-
137- None
138- } ,
139- |( first_id, local) | {
140- if let Node :: Pat ( pat) = local
141- && let parent = parent_pat ( cx, pat)
142- && parent. hir_id == first_id
143- {
144- return matches ! (
145- cx. typeck_results( ) . pat_ty( parent) . peel_refs( ) . kind( ) ,
146- ty:: Array ( _, len) if len. eval_target_usize( cx. tcx, cx. param_env) as usize == elements. len( )
147- ) ;
185+ match ( kind, ty. kind ( ) ) {
186+ // Ensure the final type and the original type have the same length, and that there
187+ // is no implicit `&mut`<=>`&` anywhere (#11100). Bit ugly, I know, but it works.
188+ ( ToType :: Array , ty:: Tuple ( tys) ) => {
189+ tys. len ( ) == elements. len ( ) && tys. iter ( ) . chain ( final_tys. iter ( ) . copied ( ) ) . all_equal ( )
190+ } ,
191+ ( ToType :: Tuple , ty:: Array ( ty, len) ) => {
192+ len. eval_target_usize ( cx. tcx , cx. param_env ) as usize == elements. len ( )
193+ && final_tys. iter ( ) . chain ( once ( ty) ) . all_equal ( )
194+ } ,
195+ _ => false ,
148196 }
149-
150- false
151- } ,
152- ) {
153- emit_lint ( cx, expr, ToType :: Tuple ) ;
154- }
155- }
156-
157- /// Walks up the `Pat` until it's reached the final containing `Pat`.
158- fn parent_pat < ' tcx > ( cx : & LateContext < ' tcx > , start : & ' tcx Pat < ' tcx > ) -> & ' tcx Pat < ' tcx > {
159- let mut end = start;
160- for ( _, node) in cx. tcx . hir ( ) . parent_iter ( start. hir_id ) {
161- if let Node :: Pat ( pat) = node {
162- end = pat;
163- } else {
164- break ;
165- }
166- }
167- end
197+ } )
168198}
169199
170200#[ derive( Clone , Copy ) ]
@@ -173,61 +203,11 @@ enum ToType {
173203 Tuple ,
174204}
175205
176- impl ToType {
177- fn msg ( self ) -> & ' static str {
178- match self {
179- ToType :: Array => "it looks like you're trying to convert a tuple to an array" ,
180- ToType :: Tuple => "it looks like you're trying to convert an array to a tuple" ,
181- }
182- }
183-
184- fn help ( self ) -> & ' static str {
206+ impl PartialEq < PatKind < ' _ > > for ToType {
207+ fn eq ( & self , other : & PatKind < ' _ > ) -> bool {
185208 match self {
186- ToType :: Array => "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed" ,
187- ToType :: Tuple => "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed" ,
209+ ToType :: Array => matches ! ( other , PatKind :: Tuple ( _ , _ ) ) ,
210+ ToType :: Tuple => matches ! ( other , PatKind :: Slice ( _ , _ , _ ) ) ,
188211 }
189212 }
190213}
191-
192- fn emit_lint < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , to_type : ToType ) -> bool {
193- if !is_from_proc_macro ( cx, expr) {
194- span_lint_and_help (
195- cx,
196- TUPLE_ARRAY_CONVERSIONS ,
197- expr. span ,
198- to_type. msg ( ) ,
199- None ,
200- to_type. help ( ) ,
201- ) ;
202-
203- return true ;
204- }
205-
206- false
207- }
208-
209- fn should_lint < ' tcx > (
210- cx : & LateContext < ' tcx > ,
211- elements : & ' tcx [ Expr < ' tcx > ] ,
212- map : impl FnMut ( ( usize , & ' tcx Expr < ' tcx > ) ) -> Option < ( usize , & Expr < ' _ > ) > ,
213- predicate : impl FnMut ( ( HirId , & Node < ' tcx > ) ) -> bool ,
214- ) -> bool {
215- if let Some ( elements) = elements
216- . iter ( )
217- . enumerate ( )
218- . map ( map)
219- . collect :: < Option < Vec < _ > > > ( )
220- && let Some ( locals) = elements
221- . iter ( )
222- . map ( |( _, element) | path_to_local ( element) . and_then ( |local| cx. tcx . hir ( ) . find ( local) ) )
223- . collect :: < Option < Vec < _ > > > ( )
224- && let [ first, rest @ ..] = & * locals
225- && let Node :: Pat ( first_pat) = first
226- && let parent = parent_pat ( cx, first_pat) . hir_id
227- && rest. iter ( ) . chain ( once ( first) ) . map ( |i| ( parent, i) ) . all ( predicate)
228- {
229- return true ;
230- }
231-
232- false
233- }
0 commit comments