use super::SINGLE_ELEMENT_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, snippet, snippet_with_applicability}; use clippy_utils::visitors::contains_break_or_continue; use rustc_ast::Mutability; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind, is_range_literal}; use rustc_lint::LateContext; use rustc_span::edition::Edition; use rustc_span::sym; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>, expr: &'tcx Expr<'_>, ) { let (arg_expression, prefix) = match arg.kind { ExprKind::AddrOf( BorrowKind::Ref, Mutability::Not, Expr { kind: ExprKind::Array([arg]), .. }, ) => (arg, "&"), ExprKind::AddrOf( BorrowKind::Ref, Mutability::Mut, Expr { kind: ExprKind::Array([arg]), .. }, ) => (arg, "&mut "), ExprKind::MethodCall( method, Expr { kind: ExprKind::Array([arg]), .. }, [], _, ) if method.ident.name == sym::iter => (arg, "&"), ExprKind::MethodCall( method, Expr { kind: ExprKind::Array([arg]), .. }, [], _, ) if method.ident.name == sym::iter_mut => (arg, "&mut "), ExprKind::MethodCall( method, Expr { kind: ExprKind::Array([arg]), .. }, [], _, ) if method.ident.name == sym::into_iter => (arg, ""), // Only check for arrays edition 2021 or later, as this case will trigger a compiler error otherwise. ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""), _ => return, }; if let ExprKind::Block(block, _) = body.kind && !block.stmts.is_empty() && !contains_break_or_continue(body) { let mut applicability = Applicability::MachineApplicable; let mut pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability); if matches!(pat.kind, PatKind::Or(..)) { pat_snip = format!("({pat_snip})").into(); } let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability); let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned(); block_str.remove(0); block_str.pop(); let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)); // Reference iterator from `&(mut) []` or `[].iter(_mut)()`. if !prefix.is_empty() && ( // Precedence of internal expression is less than or equal to precedence of `&expr`. arg_expression.precedence() <= ExprPrecedence::Prefix || is_range_literal(arg_expression) ) { arg_snip = format!("({arg_snip})").into(); } if clippy_utils::higher::Range::hir(arg_expression).is_some() { let range_expr = snippet(cx, arg_expression.span, "?").to_string(); let sugg = snippet(cx, arg_expression.span, ".."); span_lint_and_sugg( cx, SINGLE_ELEMENT_LOOP, arg.span, format!("this loops only once with `{pat_snip}` being `{range_expr}`"), "did you mean to iterate over the range instead?", sugg.to_string(), Applicability::Unspecified, ); } else { span_lint_and_sugg( cx, SINGLE_ELEMENT_LOOP, expr.span, "for loop over a single element", "try", format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"), applicability, ); } } }