Skip to content

Commit c4ef120

Browse files
committed
New lint tuple_array_conversions
1 parent 10ce1a6 commit c4ef120

File tree

6 files changed

+293
-0
lines changed

6 files changed

+293
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5262,6 +5262,7 @@ Released 2018-09-13
52625262
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
52635263
[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
52645264
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
5265+
[`tuple_array_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions
52655266
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
52665267
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
52675268
[`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
624624
crate::transmute::UNSOUND_COLLECTION_TRANSMUTE_INFO,
625625
crate::transmute::USELESS_TRANSMUTE_INFO,
626626
crate::transmute::WRONG_TRANSMUTE_INFO,
627+
crate::tuple_array_conversions::TUPLE_ARRAY_CONVERSIONS_INFO,
627628
crate::types::BORROWED_BOX_INFO,
628629
crate::types::BOX_COLLECTION_INFO,
629630
crate::types::LINKEDLIST_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ mod to_digit_is_some;
311311
mod trailing_empty_array;
312312
mod trait_bounds;
313313
mod transmute;
314+
mod tuple_array_conversions;
314315
mod types;
315316
mod undocumented_unsafe_blocks;
316317
mod unicode;
@@ -1072,6 +1073,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10721073
});
10731074
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
10741075
store.register_early_pass(|| Box::new(visibility::Visibility));
1076+
store.register_late_pass(|_| Box::new(tuple_array_conversions::TupleArrayConversions));
10751077
// add lints here, do not remove this comment, it's used in `new_lint`
10761078
}
10771079

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use clippy_utils::{diagnostics::span_lint_and_help, is_from_proc_macro, path_to_local};
2+
use rustc_hir::*;
3+
use rustc_lint::{LateContext, LateLintPass, LintContext};
4+
use rustc_middle::{lint::in_external_macro, ty};
5+
use rustc_session::{declare_lint_pass, declare_tool_lint};
6+
7+
declare_clippy_lint! {
8+
/// ### What it does
9+
///
10+
/// ### Why is this bad?
11+
///
12+
/// ### Example
13+
/// ```rust,ignore
14+
/// let t1 = &[(1, 2), (3, 4)];
15+
/// let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();
16+
/// ```
17+
/// Use instead:
18+
/// ```rust,ignore
19+
/// let t1 = &[(1, 2), (3, 4)];
20+
/// let v1: Vec<[u32; 2]> = t1.iter().map(|&t| t.into()).collect();
21+
/// ```
22+
#[clippy::version = "1.72.0"]
23+
pub TUPLE_ARRAY_CONVERSIONS,
24+
complexity,
25+
"default lint description"
26+
}
27+
declare_lint_pass!(TupleArrayConversions => [TUPLE_ARRAY_CONVERSIONS]);
28+
29+
impl LateLintPass<'_> for TupleArrayConversions {
30+
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
31+
if !in_external_macro(cx.sess(), expr.span) {
32+
_ = check_array(cx, expr) || check_tuple(cx, expr);
33+
}
34+
}
35+
}
36+
37+
fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
38+
let ExprKind::Array(elements) = expr.kind else {
39+
return false;
40+
};
41+
if !(1..=12).contains(&elements.len()) {
42+
return false;
43+
}
44+
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+
)
54+
})
55+
{
56+
return emit_lint(cx, expr, ToType::Array);
57+
}
58+
59+
if let Some(elements) = elements
60+
.iter()
61+
.map(|expr| {
62+
if let ExprKind::Field(path, _) = expr.kind {
63+
return Some(path);
64+
};
65+
66+
None
67+
})
68+
.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+
)
78+
})
79+
{
80+
return emit_lint(cx, expr, ToType::Array);
81+
}
82+
83+
false
84+
}
85+
86+
fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
87+
let ExprKind::Tup(elements) = expr.kind else {
88+
return false;
89+
};
90+
if !(1..=12).contains(&elements.len()) {
91+
return false;
92+
};
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+
)
102+
})
103+
{
104+
return emit_lint(cx, expr, ToType::Tuple);
105+
}
106+
107+
if let Some(elements) = elements
108+
.iter()
109+
.map(|expr| {
110+
if let ExprKind::Index(path, _) = expr.kind {
111+
return Some(path);
112+
};
113+
114+
None
115+
})
116+
.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+
)
126+
})
127+
{
128+
return emit_lint(cx, expr, ToType::Tuple);
129+
}
130+
131+
false
132+
}
133+
134+
/// 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> {
136+
let mut end = start;
137+
for (_, node) in cx.tcx.hir().parent_iter(start.hir_id) {
138+
if let Node::Pat(pat) = node {
139+
end = pat;
140+
} else {
141+
break;
142+
}
143+
}
144+
end
145+
}
146+
147+
fn path_to_locals<'tcx>(
148+
cx: &LateContext<'tcx>,
149+
exprs: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
150+
) -> Option<Vec<Node<'tcx>>> {
151+
exprs
152+
.into_iter()
153+
.map(|element| path_to_local(element).and_then(|local| cx.tcx.hir().find(local)))
154+
.collect()
155+
}
156+
157+
#[derive(Clone, Copy)]
158+
enum ToType {
159+
Array,
160+
Tuple,
161+
}
162+
163+
impl ToType {
164+
fn help(self) -> &'static str {
165+
match self {
166+
ToType::Array => "it looks like you're trying to convert a tuple to an array",
167+
ToType::Tuple => "it looks like you're trying to convert an array to a tuple",
168+
}
169+
}
170+
}
171+
172+
fn emit_lint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, to_type: ToType) -> bool {
173+
if !is_from_proc_macro(cx, expr) {
174+
span_lint_and_help(
175+
cx,
176+
TUPLE_ARRAY_CONVERSIONS,
177+
expr.span,
178+
to_type.help(),
179+
None,
180+
"use `.into()` instead",
181+
);
182+
183+
return true;
184+
}
185+
186+
false
187+
}

tests/ui/tuple_array_conversions.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//@aux-build:proc_macros.rs:proc-macro
2+
#![allow(clippy::useless_vec, unused)]
3+
#![warn(clippy::tuple_array_conversions)]
4+
5+
#[macro_use]
6+
extern crate proc_macros;
7+
8+
fn main() {
9+
let x = [1, 2];
10+
let x = (x[0], x[1]);
11+
let x = [x.0, x.1];
12+
let x = &[1, 2];
13+
let x = (x[0], x[1]);
14+
15+
let t1: &[(u32, u32)] = &[(1, 2), (3, 4)];
16+
let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();
17+
t1.iter().for_each(|&(a, b)| _ = [a, b]);
18+
let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect();
19+
t1.iter().for_each(|&(a, b)| _ = [a, b]);
20+
// Do not lint
21+
let v2: Vec<[u32; 2]> = t1.iter().map(|&t| t.into()).collect();
22+
let t3: Vec<(u32, u32)> = v2.iter().map(|&v| v.into()).collect();
23+
let x = [1; 13];
24+
let x = (x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12]);
25+
let x = [x.0, x.1, x.2, x.3, x.4, x.5, x.6, x.7, x.8, x.9, x.10, x.11, x.12];
26+
let x = (1, 2);
27+
let x = (x.0, x.1);
28+
let x = [1, 2];
29+
let x = [x[0], x[1]];
30+
let x = vec![1, 2];
31+
let x = (x[0], x[1]);
32+
external! {
33+
let t1: &[(u32, u32)] = &[(1, 2), (3, 4)];
34+
let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();
35+
let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect();
36+
}
37+
with_span! {
38+
span
39+
let t1: &[(u32, u32)] = &[(1, 2), (3, 4)];
40+
let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();
41+
let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect();
42+
}
43+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
error: it looks like you're trying to convert an array to a tuple
2+
--> $DIR/tuple_array_conversions.rs:10:13
3+
|
4+
LL | let x = (x[0], x[1]);
5+
| ^^^^^^^^^^^^
6+
|
7+
= help: use `.into()` instead
8+
= note: `-D clippy::tuple-array-conversions` implied by `-D warnings`
9+
10+
error: it looks like you're trying to convert a tuple to an array
11+
--> $DIR/tuple_array_conversions.rs:11:13
12+
|
13+
LL | let x = [x.0, x.1];
14+
| ^^^^^^^^^^
15+
|
16+
= help: use `.into()` instead
17+
18+
error: it looks like you're trying to convert an array to a tuple
19+
--> $DIR/tuple_array_conversions.rs:13:13
20+
|
21+
LL | let x = (x[0], x[1]);
22+
| ^^^^^^^^^^^^
23+
|
24+
= help: use `.into()` instead
25+
26+
error: it looks like you're trying to convert a tuple to an array
27+
--> $DIR/tuple_array_conversions.rs:16:53
28+
|
29+
LL | let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();
30+
| ^^^^^^
31+
|
32+
= help: use `.into()` instead
33+
34+
error: it looks like you're trying to convert a tuple to an array
35+
--> $DIR/tuple_array_conversions.rs:17:38
36+
|
37+
LL | t1.iter().for_each(|&(a, b)| _ = [a, b]);
38+
| ^^^^^^
39+
|
40+
= help: use `.into()` instead
41+
42+
error: it looks like you're trying to convert an array to a tuple
43+
--> $DIR/tuple_array_conversions.rs:18:55
44+
|
45+
LL | let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect();
46+
| ^^^^^^
47+
|
48+
= help: use `.into()` instead
49+
50+
error: it looks like you're trying to convert a tuple to an array
51+
--> $DIR/tuple_array_conversions.rs:19:38
52+
|
53+
LL | t1.iter().for_each(|&(a, b)| _ = [a, b]);
54+
| ^^^^^^
55+
|
56+
= help: use `.into()` instead
57+
58+
error: aborting due to 7 previous errors
59+

0 commit comments

Comments
 (0)