1 use clippy_config
::msrvs
::{self, Msrv}
;
2 use clippy_utils
::diagnostics
::span_lint_and_help
;
3 use clippy_utils
::visitors
::for_each_local_use_after_expr
;
4 use clippy_utils
::{is_from_proc_macro, path_to_local}
;
5 use itertools
::Itertools
;
6 use rustc_ast
::LitKind
;
7 use rustc_hir
::{Expr, ExprKind, Node, PatKind}
;
8 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
9 use rustc_middle
::lint
::in_external_macro
;
10 use rustc_middle
::ty
::{self, Ty}
;
11 use rustc_session
::impl_lint_pass
;
13 use std
::ops
::ControlFlow
;
15 declare_clippy_lint
! {
17 /// Checks for tuple<=>array conversions that are not done with `.into()`.
19 /// ### Why is this bad?
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
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.
30 /// let t1 = &[(1, 2), (3, 4)];
31 /// let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();
35 /// let t1 = &[(1, 2), (3, 4)];
36 /// let v1: Vec<[u32; 2]> = t1.iter().map(|&t| t.into()).collect();
38 #[clippy::version = "1.72.0"]
39 pub TUPLE_ARRAY_CONVERSIONS
,
41 "checks for tuple<=>array conversions that are not done with `.into()`"
43 impl_lint_pass
!(TupleArrayConversions
=> [TUPLE_ARRAY_CONVERSIONS
]);
46 pub struct TupleArrayConversions
{
50 impl LateLintPass
<'_
> for TupleArrayConversions
{
51 fn check_expr
<'tcx
>(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'tcx
>) {
52 if in_external_macro(cx
.sess(), expr
.span
) || !self.msrv
.meets(msrvs
::TUPLE_ARRAY_CONVERSIONS
) {
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
),
63 extract_msrv_attr
!(LateContext
);
66 fn check_array
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'tcx
>, elements
: &'tcx
[Expr
<'tcx
>]) {
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`");
71 if let [first
, ..] = elements
72 && let Some(locals
) = (match first
.kind
{
73 ExprKind
::Field(_
, _
) => elements
76 .map(|(i
, f
)| -> Option
<&'tcx Expr
<'tcx
>> {
77 let ExprKind
::Field(lhs
, ident
) = f
.kind
else {
80 (ident
.name
.as_str() == i
.to_string()).then_some(lhs
)
82 .collect
::<Option
<Vec
<_
>>>(),
83 ExprKind
::Path(_
) => Some(elements
.iter().collect()),
86 && all_bindings_are_for_conv(cx
, &[*ty
], expr
, elements
, &locals
, ToType
::Array
)
87 && !is_from_proc_macro(cx
, expr
)
91 TUPLE_ARRAY_CONVERSIONS
,
93 "it looks like you're trying to convert a tuple to an array",
95 "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed",
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
104 && tys
.iter().all_equal()
105 && let Some(locals
) = (match first
.kind
{
106 ExprKind
::Index(..) => elements
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
114 return (val
== i
as u128
).then_some(lhs
);
119 .collect
::<Option
<Vec
<_
>>>(),
120 ExprKind
::Path(_
) => Some(elements
.iter().collect()),
123 && all_bindings_are_for_conv(cx
, tys
, expr
, elements
, &locals
, ToType
::Tuple
)
124 && !is_from_proc_macro(cx
, expr
)
128 TUPLE_ARRAY_CONVERSIONS
,
130 "it looks like you're trying to convert an array to a tuple",
132 "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed",
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
144 #[expect(clippy::cast_possible_truncation)]
145 fn all_bindings_are_for_conv
<'tcx
>(
146 cx
: &LateContext
<'tcx
>,
147 final_tys
: &[Ty
<'tcx
>],
149 elements
: &[Expr
<'_
>],
150 locals
: &[&Expr
<'_
>],
153 let Some(locals
) = locals
.iter().map(|e
| path_to_local(e
)).collect
::<Option
<Vec
<_
>>>() else {
156 let local_parents
= locals
.iter().map(|l
| cx
.tcx
.parent_hir_node(*l
)).collect
::<Vec
<_
>>();
160 .map(|node
| match node
{
161 Node
::Pat(pat
) => kind
.eq(&pat
.kind
).then_some(pat
.hir_id
),
162 Node
::LetStmt(l
) => Some(l
.hir_id
),
166 // Fix #11124, very convenient utils function! ❤️
169 .all(|&l
| for_each_local_use_after_expr(cx
, l
, expr
.hir_id
, |_
| ControlFlow
::Break
::<()>(())).is_continue())
170 && local_parents
.first().is_some_and(|node
| {
171 let Some(ty
) = match node
{
172 Node
::Pat(pat
) => Some(pat
.hir_id
),
173 Node
::LetStmt(l
) => Some(l
.hir_id
),
176 .map(|hir_id
| cx
.typeck_results().node_type(hir_id
)) else {
179 match (kind
, ty
.kind()) {
180 // Ensure the final type and the original type have the same length, and that there
181 // is no implicit `&mut`<=>`&` anywhere (#11100). Bit ugly, I know, but it works.
182 (ToType
::Array
, ty
::Tuple(tys
)) => {
183 tys
.len() == elements
.len() && tys
.iter().chain(final_tys
.iter().copied()).all_equal()
185 (ToType
::Tuple
, ty
::Array(ty
, len
)) => {
186 let Some(len
) = len
.try_eval_target_usize(cx
.tcx
, cx
.param_env
) else { return false }
;
187 len
as usize == elements
.len() && final_tys
.iter().chain(once(ty
)).all_equal()
194 #[derive(Clone, Copy)]
200 impl PartialEq
<PatKind
<'_
>> for ToType
{
201 fn eq(&self, other
: &PatKind
<'_
>) -> bool
{
203 ToType
::Array
=> matches
!(other
, PatKind
::Tuple(_
, _
)),
204 ToType
::Tuple
=> matches
!(other
, PatKind
::Slice(_
, _
, _
)),