Skip to content

Commit 747df85

Browse files
committed
Auto merge of #11171 - Centri3:tuple_array_conversions, r=llogiq
Rewrite [`tuple_array_conversions`] Fixes #11100 Fixes #11144 Fixes #11124 #11082 still needs discussion and #11085 likely can't be fixed. changelog: [`tuple_array_conversions`]: Move to `pedantic` changelog: [`tuple_array_conversions`]: Don't lint if mutability of references changes changelog: [`tuple_array_conversions`]: Don't lint if bindings don't come from the exact same pattern changelog: [`tuple_array_conversions`]: Don't lint if bindings are used for more than just the conversion
2 parents 3e0170b + 74a7704 commit 747df85

File tree

3 files changed

+195
-185
lines changed

3 files changed

+195
-185
lines changed
+147-167
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
use clippy_utils::diagnostics::span_lint_and_help;
22
use clippy_utils::msrvs::{self, Msrv};
3+
use clippy_utils::visitors::for_each_local_use_after_expr;
34
use clippy_utils::{is_from_proc_macro, path_to_local};
5+
use itertools::Itertools;
46
use rustc_ast::LitKind;
5-
use rustc_hir::{Expr, ExprKind, HirId, Node, Pat};
7+
use rustc_hir::{Expr, ExprKind, Node, PatKind};
68
use rustc_lint::{LateContext, LateLintPass, LintContext};
79
use rustc_middle::lint::in_external_macro;
8-
use rustc_middle::ty;
10+
use rustc_middle::ty::{self, Ty};
911
use rustc_session::{declare_tool_lint, impl_lint_pass};
1012
use std::iter::once;
13+
use std::ops::ControlFlow;
1114

1215
declare_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

4250
impl 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-
)]
6066
fn 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-
}

tests/ui/tuple_array_conversions.rs

+30
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,36 @@ fn main() {
5252
let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();
5353
let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect();
5454
}
55+
// FP #11082; needs discussion
56+
let (a, b) = (1.0f64, 2.0f64);
57+
let _: &[f64] = &[a, b];
58+
// FP #11085; impossible to fix
59+
let [src, dest]: [_; 2] = [1, 2];
60+
(src, dest);
61+
// FP #11100
62+
fn issue_11100_array_to_tuple(this: [&mut i32; 2]) -> (&i32, &mut i32) {
63+
let [input, output] = this;
64+
(input, output)
65+
}
66+
67+
fn issue_11100_tuple_to_array<'a>(this: (&'a mut i32, &'a mut i32)) -> [&'a i32; 2] {
68+
let (input, output) = this;
69+
[input, output]
70+
}
71+
// FP #11124
72+
// tuple=>array
73+
let (a, b) = (1, 2);
74+
[a, b];
75+
let x = a;
76+
// array=>tuple
77+
let [a, b] = [1, 2];
78+
(a, b);
79+
let x = a;
80+
// FP #11144
81+
let (a, (b, c)) = (1, (2, 3));
82+
[a, c];
83+
let [[a, b], [c, d]] = [[1, 2], [3, 4]];
84+
(a, c);
5585
}
5686

5787
#[clippy::msrv = "1.70.0"]

0 commit comments

Comments
 (0)