| use arrayvec::ArrayVec; |
| use clippy_config::Conf; |
| use clippy_utils::diagnostics::span_lint_and_help; |
| use clippy_utils::msrvs::{self, Msrv}; |
| use clippy_utils::res::MaybeResPath; |
| use clippy_utils::visitors::{Visitable, for_each_expr}; |
| use clippy_utils::{SpanlessEq, is_from_proc_macro}; |
| use core::ops::ControlFlow::{Break, Continue}; |
| use core::{iter, mem}; |
| use rustc_ast::LitKind; |
| use rustc_ast::visit::{VisitorResult, try_visit, visit_opt, walk_list}; |
| use rustc_data_structures::packed::Pu128; |
| use rustc_hir::intravisit::Visitor; |
| use rustc_hir::{Arm, Expr, ExprKind, HirId, ImplItemKind, ItemKind, Node, PatKind, Stmt, TraitFn, TraitItemKind}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_middle::ty::{self, TyCtxt}; |
| use rustc_session::impl_lint_pass; |
| use rustc_span::sym; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for tuple<=>array conversions that are not done with `.into()`. |
| /// |
| /// ### Why is this bad? |
| /// It may be unnecessary complexity. `.into()` works for converting tuples<=> arrays of up to |
| /// 12 elements and conveys the intent more clearly, while also leaving less room for hard to |
| /// spot bugs! |
| /// |
| /// ### Known issues |
| /// The suggested code may hide potential asymmetry in some cases. See |
| /// [#11085](https://github.com/rust-lang/rust-clippy/issues/11085) for more info. |
| /// |
| /// ### 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, |
| nursery, |
| "checks for tuple<=>array conversions that are not done with `.into()`" |
| } |
| |
| impl_lint_pass!(TupleArrayConversions => [TUPLE_ARRAY_CONVERSIONS]); |
| |
| pub struct TupleArrayConversions { |
| msrv: Msrv, |
| } |
| impl TupleArrayConversions { |
| pub fn new(conf: &'static Conf) -> Self { |
| Self { msrv: conf.msrv } |
| } |
| } |
| |
| /// The maximum size of a tuple/array for which `From` is implemented. |
| const MAX_CVT_COUNT: usize = 12; |
| |
| impl LateLintPass<'_> for TupleArrayConversions { |
| #[expect(clippy::too_many_lines)] |
| fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) { |
| match e.kind { |
| ExprKind::Array([]) | ExprKind::Tup([]) => {}, |
| ExprKind::Array(es) | ExprKind::Tup(es) if es.len() > MAX_CVT_COUNT => {}, |
| |
| // Create an array using every item in a tuple (e.g. `[x.0, x.1, x.2]`). |
| ExprKind::Array([first, rest @ ..]) if let ExprKind::Field(first_base, first_field) = first.kind => { |
| if first_field.name == sym::integer(0) |
| && let ctxt = e.span.ctxt() |
| && ctxt == first.span.ctxt() |
| && ctxt == first_field.span.ctxt() |
| // Check that the remaining elements are accesses to the expected field. |
| && let Some(bases) = rest.iter().enumerate().map(|(i, e)| { |
| if let ExprKind::Field(base, field) = e.kind |
| && field.name == sym::integer(i + 1) |
| && ctxt == e.span.ctxt() |
| && ctxt == field.span.ctxt() |
| { |
| Some(base) |
| } else { |
| None |
| } |
| }).collect::<Option<ArrayVec<_, MAX_CVT_COUNT>>>() |
| // Check that the source and destination types are the same and copyable. |
| && let ty::Tuple(src_tys) = *cx.typeck_results().expr_ty_adjusted(first_base).kind() |
| && src_tys.len() == rest.len() + 1 |
| && let ty::Array(dst_ty, _) = *cx.typeck_results().expr_ty(e).kind() |
| && src_tys.iter().all(|ty| ty == dst_ty) |
| && cx.tcx.type_is_copy_modulo_regions(cx.typing_env(), dst_ty) |
| // Check that all accesses are to the same base last as that can be a complex check. |
| && let mut eq = SpanlessEq::new(cx).deny_side_effects() |
| && bases.iter().all(|e| eq.eq_expr(ctxt, first_base, e)) |
| && self.msrv.meets(cx, msrvs::TUPLE_ARRAY_CONVERSIONS) |
| && !ctxt.in_external_macro(cx.tcx.sess.source_map()) |
| && !is_from_proc_macro(cx, e) |
| { |
| span_lint_and_help( |
| cx, |
| TUPLE_ARRAY_CONVERSIONS, |
| e.span, |
| "it looks like you're trying to convert a tuple to an array", |
| None, |
| "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed", |
| ); |
| } |
| }, |
| |
| // Create a tuple using every item in an array (e.g. `(x[0], x[1], x[2])`). |
| ExprKind::Tup([first, rest @ ..]) if let ExprKind::Index(first_base, first_idx, _) = first.kind => { |
| if let ExprKind::Lit(idx_lit) = first_idx.kind |
| && let LitKind::Int(Pu128(0), _) = idx_lit.node |
| && let ctxt = e.span.ctxt() |
| && ctxt == first.span.ctxt() |
| && ctxt == first_idx.span.ctxt() |
| && ctxt == idx_lit.span.ctxt() |
| // Check that the remaining elements are array accesses with the expected index. |
| && let Some(bases) = rest.iter().enumerate().map(|(i, e)| { |
| if let ExprKind::Index(base, idx, _) = e.kind |
| && let ExprKind::Lit(idx_lit) = idx.kind |
| && let LitKind::Int(idx_num, _) = idx_lit.node |
| && idx_num == Pu128((i + 1) as u128) |
| && ctxt == e.span.ctxt() |
| && ctxt == idx_lit.span.ctxt() |
| { |
| Some(base) |
| } else { |
| None |
| } |
| }).collect::<Option<ArrayVec<_, MAX_CVT_COUNT>>>() |
| // Check that the source and destination types are the same and copyable. |
| && let ty::Array(src_ty, src_len) = *cx.typeck_results().expr_ty_adjusted(first_base).kind() |
| && src_len.try_to_target_usize(cx.tcx) == Some((rest.len() + 1) as u64) |
| && let ty::Tuple(dst_tys) = *cx.typeck_results().expr_ty(e).kind() |
| && dst_tys.iter().all(|ty| ty == src_ty) |
| && cx.tcx.type_is_copy_modulo_regions(cx.typing_env(), src_ty) |
| // Check that all accesses are to the same base last as that can be a complex check. |
| && let mut eq = SpanlessEq::new(cx).deny_side_effects() |
| && bases.iter().all(|e| eq.eq_expr(ctxt, first_base, e)) |
| && self.msrv.meets(cx, msrvs::TUPLE_ARRAY_CONVERSIONS) |
| && !ctxt.in_external_macro(cx.tcx.sess.source_map()) |
| && !is_from_proc_macro(cx, e) |
| { |
| span_lint_and_help( |
| cx, |
| TUPLE_ARRAY_CONVERSIONS, |
| e.span, |
| "it looks like you're trying to convert an array to a tuple", |
| None, |
| "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed", |
| ); |
| } |
| }, |
| |
| // Destructure an array/tuple and create the other by using all the items. |
| // e.g. `|(x, y, z)| [x, y, z]` |
| ExprKind::Array([first, rest @ ..]) | ExprKind::Tup([first, rest @ ..]) |
| if let Some((first_id, first_ident)) = first.res_local_id_and_ident() |
| && let ctxt = e.span.ctxt() |
| && ctxt == first.span.ctxt() |
| && ctxt == first_ident.span.ctxt() |
| // Collect all the remaining local IDs involved. |
| // This is done first so we don't go through `TyCtxt` unless needed. |
| && let Some(mut ids) = rest |
| .iter() |
| .map(|e| { |
| e.res_local_id_and_ident() |
| .filter(|&(_, ident)| ctxt == e.span.ctxt() && ctxt == ident.span.ctxt()) |
| .map(|(id, _)| id) |
| }) |
| .collect::<Option<ArrayVec<_, MAX_CVT_COUNT>>>() |
| // Check that all locals used are from a single destructuring in the same order. |
| // The iterator will be used later to get the enclosing scope for the bindings. |
| && let mut parent_iter = cx.tcx.hir_parent_iter(first_id) |
| && let Some((id_parent, Node::Pat(id_parent_pat))) = parent_iter.next() |
| && let PatKind::Tuple([first_pat, rest_pats @ ..], _) |
| | PatKind::Slice([first_pat, rest_pats @ ..], ..) = id_parent_pat.kind |
| && first_id == first_pat.hir_id |
| && let PatKind::Binding(.., None) = first_pat.kind |
| && iter::zip(&ids, rest_pats) |
| .all(|(&id, pat)| id == pat.hir_id && matches!(pat.kind, PatKind::Binding(.., None))) |
| // Check that the source and destination types are the same. |
| && let Some(src_ty) = match *cx.typeck_results().node_type(id_parent).kind() { |
| ty::Array(src_ty, src_len) |
| if matches!(e.kind, ExprKind::Tup(_)) |
| && src_len.try_to_target_usize(cx.tcx) == Some((rest.len() + 1) as u64) => |
| { |
| Some(src_ty) |
| }, |
| ty::Tuple(src_tys) |
| if let [src_ty, ref rest_tys @ ..] = **src_tys |
| && rest.len() == rest_tys.len() |
| && matches!(e.kind, ExprKind::Array(_)) |
| && rest_tys.iter().all(|&ty| src_ty == ty) => |
| { |
| Some(src_ty) |
| }, |
| _ => None, |
| } |
| && match *cx.typeck_results().expr_ty(e).kind() { |
| ty::Array(dst_ty, _) => dst_ty == src_ty, |
| ty::Tuple(dst_tys) => dst_tys.iter().all(|ty| src_ty == ty), |
| __ => false, |
| } |
| && ctxt == id_parent_pat.span.ctxt() |
| // Check that each binding is used at most once. |
| && let Some(use_scope) = find_binding_use_scope(cx.tcx, parent_iter) |
| && let () = ids.push(first_id) |
| && let mut ids_used = [false; MAX_CVT_COUNT] |
| && let None = for_each_expr(cx.tcx, use_scope, |e| { |
| if let Some(id) = e.res_local_id() |
| && let Some(i) = ids.iter().position(|&x| id == x) |
| && mem::replace(&mut ids_used[i], true) |
| { |
| Break(()) |
| } else { |
| Continue(()) |
| } |
| }) |
| && !ctxt.in_external_macro(cx.tcx.sess.source_map()) |
| && !is_from_proc_macro(cx, e) => |
| { |
| let (msg, help) = if let ExprKind::Array(_) = e.kind { |
| ( |
| "it looks like you're trying to convert a tuple to an array", |
| "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed", |
| ) |
| } else { |
| ( |
| "it looks like you're trying to convert an array to a tuple", |
| "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed", |
| ) |
| }; |
| span_lint_and_help(cx, TUPLE_ARRAY_CONVERSIONS, e.span, msg, None, help); |
| }, |
| |
| _ => {}, |
| } |
| } |
| } |
| |
| #[derive(Clone, Copy)] |
| enum BindingUseRange<'tcx> { |
| Arm(&'tcx Arm<'tcx>), |
| Param(&'tcx Expr<'tcx>), |
| LetStmt(&'tcx [Stmt<'tcx>], Option<&'tcx Expr<'tcx>>), |
| LetExpr(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>), |
| } |
| impl<'tcx> Visitable<'tcx> for BindingUseRange<'tcx> { |
| fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) -> V::Result { |
| match self { |
| Self::Arm(a) => visitor.visit_arm(a), |
| Self::Param(e) => visitor.visit_expr(e), |
| Self::LetStmt(stmts, e) => { |
| walk_list!(visitor, visit_stmt, stmts); |
| visit_opt!(visitor, visit_expr, e); |
| <V::Result as VisitorResult>::output() |
| }, |
| Self::LetExpr(cond, then) => { |
| try_visit!(visitor.visit_expr(cond)); |
| visitor.visit_expr(then) |
| }, |
| } |
| } |
| } |
| |
| /// Given a parent iterator from a binding node, finds the parts of the HIR tree which can |
| /// use that binding. |
| fn find_binding_use_scope<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>, |
| ) -> Option<BindingUseRange<'tcx>> { |
| loop { |
| match iter.next() { |
| Some((_, Node::Pat(p))) if !matches!(p.kind, PatKind::Or(_)) => {}, |
| Some((_, Node::PatField(_))) => {}, |
| Some((_, Node::Arm(a))) => break Some(BindingUseRange::Arm(a)), |
| Some((_, Node::Param(_))) => { |
| let body = match iter.next() { |
| Some((_, Node::Expr(e))) if let ExprKind::Closure(c) = e.kind => c.body, |
| Some((_, Node::Item(i))) if let ItemKind::Fn { body, .. } = i.kind => body, |
| Some((_, Node::TraitItem(i))) if let TraitItemKind::Fn(_, TraitFn::Provided(body)) = i.kind => body, |
| Some((_, Node::ImplItem(i))) if let ImplItemKind::Fn(_, body) = i.kind => body, |
| _ => break None, |
| }; |
| break Some(BindingUseRange::Param(tcx.hir_body(body).value)); |
| }, |
| Some((_, Node::LetStmt(_))) |
| if let Some((id, Node::Stmt(_))) = iter.next() |
| && let Some((_, Node::Block(b))) = iter.next() |
| && let mut stmts = b.stmts.iter() |
| && stmts.any(|s| s.hir_id == id) => |
| { |
| break Some(BindingUseRange::LetStmt(stmts.as_slice(), b.expr)); |
| }, |
| Some((_, Node::Expr(e))) |
| if let ExprKind::Let(_) = e.kind |
| && let Some((cond, then)) = iter.find_map(|(_, n)| match n { |
| Node::Expr(e) if let ExprKind::If(cond, then, _) = e.kind => Some((cond, then)), |
| _ => None, |
| }) => |
| { |
| break Some(BindingUseRange::LetExpr(cond, then)); |
| }, |
| _ => break None, |
| } |
| } |
| } |