]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs
bump version to 1.80.1+dfsg1-1~bpo12+pve1
[rustc.git] / src / tools / clippy / clippy_lints / src / tuple_array_conversions.rs
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;
12 use std::iter::once;
13 use std::ops::ControlFlow;
14
15 declare_clippy_lint! {
16 /// ### What it does
17 /// Checks for tuple<=>array conversions that are not done with `.into()`.
18 ///
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
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.
27 ///
28 /// ### Example
29 /// ```rust,ignore
30 /// let t1 = &[(1, 2), (3, 4)];
31 /// let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();
32 /// ```
33 /// Use instead:
34 /// ```rust,ignore
35 /// let t1 = &[(1, 2), (3, 4)];
36 /// let v1: Vec<[u32; 2]> = t1.iter().map(|&t| t.into()).collect();
37 /// ```
38 #[clippy::version = "1.72.0"]
39 pub TUPLE_ARRAY_CONVERSIONS,
40 nursery,
41 "checks for tuple<=>array conversions that are not done with `.into()`"
42 }
43 impl_lint_pass!(TupleArrayConversions => [TUPLE_ARRAY_CONVERSIONS]);
44
45 #[derive(Clone)]
46 pub struct TupleArrayConversions {
47 pub msrv: Msrv,
48 }
49
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) {
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 _ => {},
60 }
61 }
62
63 extract_msrv_attr!(LateContext);
64 }
65
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`");
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 }
99
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 );
134 }
135 }
136
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>],
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 local_parents = locals.iter().map(|l| cx.tcx.parent_hir_node(*l)).collect::<Vec<_>>();
157
158 local_parents
159 .iter()
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),
163 _ => None,
164 })
165 .all_equal()
166 // Fix #11124, very convenient utils function! ❤️
167 && locals
168 .iter()
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),
174 _ => None,
175 }
176 .map(|hir_id| cx.typeck_results().node_type(hir_id)) else {
177 return false;
178 };
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()
184 },
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()
188 },
189 _ => false,
190 }
191 })
192 }
193
194 #[derive(Clone, Copy)]
195 enum ToType {
196 Array,
197 Tuple,
198 }
199
200 impl PartialEq<PatKind<'_>> for ToType {
201 fn eq(&self, other: &PatKind<'_>) -> bool {
202 match self {
203 ToType::Array => matches!(other, PatKind::Tuple(_, _)),
204 ToType::Tuple => matches!(other, PatKind::Slice(_, _, _)),
205 }
206 }
207 }