From bfcc8ba444180dfc12bb113d1d500b81c9e87956 Mon Sep 17 00:00:00 2001 From: Catherine <114838443+Centri3@users.noreply.github.com> Date: Sat, 24 Jun 2023 07:40:09 -0500 Subject: [PATCH] New lint `tuple_array_conversions` --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/tuple_array_conversions.rs | 187 ++++++++++++++++++++ tests/ui/tuple_array_conversions.rs | 43 +++++ tests/ui/tuple_array_conversions.stderr | 59 ++++++ 6 files changed, 293 insertions(+) create mode 100644 clippy_lints/src/tuple_array_conversions.rs create mode 100644 tests/ui/tuple_array_conversions.rs create mode 100644 tests/ui/tuple_array_conversions.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 4debe304857..04917871c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5262,6 +5262,7 @@ Released 2018-09-13 [`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex [`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref [`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err +[`tuple_array_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions [`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity [`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds [`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 46f4082f0c7..316645e4066 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -624,6 +624,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::transmute::UNSOUND_COLLECTION_TRANSMUTE_INFO, crate::transmute::USELESS_TRANSMUTE_INFO, crate::transmute::WRONG_TRANSMUTE_INFO, + crate::tuple_array_conversions::TUPLE_ARRAY_CONVERSIONS_INFO, crate::types::BORROWED_BOX_INFO, crate::types::BOX_COLLECTION_INFO, crate::types::LINKEDLIST_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 5ab28b5c70c..39301b5344a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -311,6 +311,7 @@ mod to_digit_is_some; mod trailing_empty_array; mod trait_bounds; mod transmute; +mod tuple_array_conversions; mod types; mod undocumented_unsafe_blocks; mod unicode; @@ -1072,6 +1073,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: }); store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns)); store.register_early_pass(|| Box::new(visibility::Visibility)); + store.register_late_pass(|_| Box::new(tuple_array_conversions::TupleArrayConversions)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/tuple_array_conversions.rs b/clippy_lints/src/tuple_array_conversions.rs new file mode 100644 index 00000000000..9640083f764 --- /dev/null +++ b/clippy_lints/src/tuple_array_conversions.rs @@ -0,0 +1,187 @@ +use clippy_utils::{diagnostics::span_lint_and_help, is_from_proc_macro, path_to_local}; +use rustc_hir::*; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::{lint::in_external_macro, ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```rust,ignore + /// let t1 = &[(1, 2), (3, 4)]; + /// let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect(); + /// ``` + /// Use instead: + /// ```rust,ignore + /// let t1 = &[(1, 2), (3, 4)]; + /// let v1: Vec<[u32; 2]> = t1.iter().map(|&t| t.into()).collect(); + /// ``` + #[clippy::version = "1.72.0"] + pub TUPLE_ARRAY_CONVERSIONS, + complexity, + "default lint description" +} +declare_lint_pass!(TupleArrayConversions => [TUPLE_ARRAY_CONVERSIONS]); + +impl LateLintPass<'_> for TupleArrayConversions { + fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if !in_external_macro(cx.sess(), expr.span) { + _ = check_array(cx, expr) || check_tuple(cx, expr); + } + } +} + +fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + let ExprKind::Array(elements) = expr.kind else { + return false; + }; + if !(1..=12).contains(&elements.len()) { + return false; + } + + if let Some(locals) = path_to_locals(cx, elements) + && locals.iter().all(|local| { + matches!( + local, + Node::Pat(pat) if matches!( + cx.typeck_results().pat_ty(backtrack_pat(cx, pat)).peel_refs().kind(), + ty::Tuple(_), + ), + ) + }) + { + return emit_lint(cx, expr, ToType::Array); + } + + if let Some(elements) = elements + .iter() + .map(|expr| { + if let ExprKind::Field(path, _) = expr.kind { + return Some(path); + }; + + None + }) + .collect::>>>() + && let Some(locals) = path_to_locals(cx, elements) + && locals.iter().all(|local| { + matches!( + local, + Node::Pat(pat) if matches!( + cx.typeck_results().pat_ty(backtrack_pat(cx, pat)).peel_refs().kind(), + ty::Tuple(_), + ), + ) + }) + { + return emit_lint(cx, expr, ToType::Array); + } + + false +} + +fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + let ExprKind::Tup(elements) = expr.kind else { + return false; + }; + if !(1..=12).contains(&elements.len()) { + return false; + }; + if let Some(locals) = path_to_locals(cx, elements) + && locals.iter().all(|local| { + matches!( + local, + Node::Pat(pat) if matches!( + cx.typeck_results().pat_ty(backtrack_pat(cx, pat)).peel_refs().kind(), + ty::Array(_, _), + ), + ) + }) + { + return emit_lint(cx, expr, ToType::Tuple); + } + + if let Some(elements) = elements + .iter() + .map(|expr| { + if let ExprKind::Index(path, _) = expr.kind { + return Some(path); + }; + + None + }) + .collect::>>>() + && let Some(locals) = path_to_locals(cx, elements.clone()) + && locals.iter().all(|local| { + matches!( + local, + Node::Pat(pat) if cx.typeck_results() + .pat_ty(backtrack_pat(cx, pat)) + .peel_refs() + .is_array() + ) + }) + { + return emit_lint(cx, expr, ToType::Tuple); + } + + false +} + +/// Walks up the `Pat` until it's reached the final containing `Pat`. +fn backtrack_pat<'tcx>(cx: &LateContext<'tcx>, start: &'tcx Pat<'tcx>) -> &'tcx Pat<'tcx> { + let mut end = start; + for (_, node) in cx.tcx.hir().parent_iter(start.hir_id) { + if let Node::Pat(pat) = node { + end = pat; + } else { + break; + } + } + end +} + +fn path_to_locals<'tcx>( + cx: &LateContext<'tcx>, + exprs: impl IntoIterator>, +) -> Option>> { + exprs + .into_iter() + .map(|element| path_to_local(element).and_then(|local| cx.tcx.hir().find(local))) + .collect() +} + +#[derive(Clone, Copy)] +enum ToType { + Array, + Tuple, +} + +impl ToType { + fn help(self) -> &'static str { + match self { + ToType::Array => "it looks like you're trying to convert a tuple to an array", + ToType::Tuple => "it looks like you're trying to convert an array to a tuple", + } + } +} + +fn emit_lint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, to_type: ToType) -> bool { + if !is_from_proc_macro(cx, expr) { + span_lint_and_help( + cx, + TUPLE_ARRAY_CONVERSIONS, + expr.span, + to_type.help(), + None, + "use `.into()` instead", + ); + + return true; + } + + false +} diff --git a/tests/ui/tuple_array_conversions.rs b/tests/ui/tuple_array_conversions.rs new file mode 100644 index 00000000000..061c05e98d0 --- /dev/null +++ b/tests/ui/tuple_array_conversions.rs @@ -0,0 +1,43 @@ +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::useless_vec, unused)] +#![warn(clippy::tuple_array_conversions)] + +#[macro_use] +extern crate proc_macros; + +fn main() { + let x = [1, 2]; + let x = (x[0], x[1]); + let x = [x.0, x.1]; + let x = &[1, 2]; + let x = (x[0], x[1]); + + let t1: &[(u32, u32)] = &[(1, 2), (3, 4)]; + let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect(); + t1.iter().for_each(|&(a, b)| _ = [a, b]); + let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect(); + t1.iter().for_each(|&(a, b)| _ = [a, b]); + // Do not lint + let v2: Vec<[u32; 2]> = t1.iter().map(|&t| t.into()).collect(); + let t3: Vec<(u32, u32)> = v2.iter().map(|&v| v.into()).collect(); + let x = [1; 13]; + 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]); + 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]; + let x = (1, 2); + let x = (x.0, x.1); + let x = [1, 2]; + let x = [x[0], x[1]]; + let x = vec![1, 2]; + let x = (x[0], x[1]); + external! { + let t1: &[(u32, u32)] = &[(1, 2), (3, 4)]; + let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect(); + let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect(); + } + with_span! { + span + let t1: &[(u32, u32)] = &[(1, 2), (3, 4)]; + let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect(); + let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect(); + } +} diff --git a/tests/ui/tuple_array_conversions.stderr b/tests/ui/tuple_array_conversions.stderr new file mode 100644 index 00000000000..aba461e51f7 --- /dev/null +++ b/tests/ui/tuple_array_conversions.stderr @@ -0,0 +1,59 @@ +error: it looks like you're trying to convert an array to a tuple + --> $DIR/tuple_array_conversions.rs:10:13 + | +LL | let x = (x[0], x[1]); + | ^^^^^^^^^^^^ + | + = help: use `.into()` instead + = note: `-D clippy::tuple-array-conversions` implied by `-D warnings` + +error: it looks like you're trying to convert a tuple to an array + --> $DIR/tuple_array_conversions.rs:11:13 + | +LL | let x = [x.0, x.1]; + | ^^^^^^^^^^ + | + = help: use `.into()` instead + +error: it looks like you're trying to convert an array to a tuple + --> $DIR/tuple_array_conversions.rs:13:13 + | +LL | let x = (x[0], x[1]); + | ^^^^^^^^^^^^ + | + = help: use `.into()` instead + +error: it looks like you're trying to convert a tuple to an array + --> $DIR/tuple_array_conversions.rs:16:53 + | +LL | let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect(); + | ^^^^^^ + | + = help: use `.into()` instead + +error: it looks like you're trying to convert a tuple to an array + --> $DIR/tuple_array_conversions.rs:17:38 + | +LL | t1.iter().for_each(|&(a, b)| _ = [a, b]); + | ^^^^^^ + | + = help: use `.into()` instead + +error: it looks like you're trying to convert an array to a tuple + --> $DIR/tuple_array_conversions.rs:18:55 + | +LL | let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect(); + | ^^^^^^ + | + = help: use `.into()` instead + +error: it looks like you're trying to convert a tuple to an array + --> $DIR/tuple_array_conversions.rs:19:38 + | +LL | t1.iter().for_each(|&(a, b)| _ = [a, b]); + | ^^^^^^ + | + = help: use `.into()` instead + +error: aborting due to 7 previous errors +