| 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 as _; | |
| 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, impl_lint_pass}; | |
| use rustc_middle::ty::consts::ConstExt as _; | |
| use rustc_middle::ty::{self, TyCtxt}; | |
| 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.into() } | |
| } | |
| } | |
| /// 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, | |
| } | |
| } | |
| } |