use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{can_mut_borrow_both, eq_expr_value, in_constant, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, SyntaxContext}; declare_clippy_lint! { /// ### What it does /// Checks for manual swapping. /// /// Note that the lint will not be emitted in const blocks, as the suggestion would not be applicable. /// /// ### Why is this bad? /// The `std::mem::swap` function exposes the intent better /// without deinitializing or copying either variable. /// /// ### Example /// ```no_run /// let mut a = 42; /// let mut b = 1337; /// /// let t = b; /// b = a; /// a = t; /// ``` /// Use std::mem::swap(): /// ```no_run /// let mut a = 1; /// let mut b = 2; /// std::mem::swap(&mut a, &mut b); /// ``` #[clippy::version = "pre 1.29.0"] pub MANUAL_SWAP, complexity, "manual swap of two variables" } declare_clippy_lint! { /// ### What it does /// Checks for `foo = bar; bar = foo` sequences. /// /// ### Why is this bad? /// This looks like a failed attempt to swap. /// /// ### Example /// ```no_run /// # let mut a = 1; /// # let mut b = 2; /// a = b; /// b = a; /// ``` /// If swapping is intended, use `swap()` instead: /// ```no_run /// # let mut a = 1; /// # let mut b = 2; /// std::mem::swap(&mut a, &mut b); /// ``` #[clippy::version = "pre 1.29.0"] pub ALMOST_SWAPPED, correctness, "`foo = bar; bar = foo` sequence" } declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED]); impl<'tcx> LateLintPass<'tcx> for Swap { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { check_manual_swap(cx, block); check_suspicious_swap(cx, block); check_xor_swap(cx, block); } } fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, span: Span, is_xor_based: bool) { let ctxt = span.ctxt(); let mut applicability = Applicability::MachineApplicable; if !can_mut_borrow_both(cx, e1, e2) { if let ExprKind::Index(lhs1, idx1, _) = e1.kind && let ExprKind::Index(lhs2, idx2, _) = e2.kind && eq_expr_value(cx, lhs1, lhs2) && e1.span.ctxt() == ctxt && e2.span.ctxt() == ctxt { let ty = cx.typeck_results().expr_ty(lhs1).peel_refs(); if matches!(ty.kind(), ty::Slice(_)) || matches!(ty.kind(), ty::Array(_, _)) || is_type_diagnostic_item(cx, ty, sym::Vec) || is_type_diagnostic_item(cx, ty, sym::VecDeque) { let slice = Sugg::hir_with_applicability(cx, lhs1, "", &mut applicability); span_lint_and_sugg( cx, MANUAL_SWAP, span, &format!("this looks like you are swapping elements of `{slice}` manually"), "try", format!( "{}.swap({}, {});", slice.maybe_par(), snippet_with_context(cx, idx1.span, ctxt, "..", &mut applicability).0, snippet_with_context(cx, idx2.span, ctxt, "..", &mut applicability).0, ), applicability, ); } } return; } let first = Sugg::hir_with_context(cx, e1, ctxt, "..", &mut applicability); let second = Sugg::hir_with_context(cx, e2, ctxt, "..", &mut applicability); let Some(sugg) = std_or_core(cx) else { return }; span_lint_and_then( cx, MANUAL_SWAP, span, &format!("this looks like you are swapping `{first}` and `{second}` manually"), |diag| { diag.span_suggestion( span, "try", format!("{sugg}::mem::swap({}, {});", first.mut_addr(), second.mut_addr()), applicability, ); if !is_xor_based { diag.note(format!("or maybe you should use `{sugg}::mem::replace`?")); } }, ); } /// Implementation of the `MANUAL_SWAP` lint. fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { if in_constant(cx, block.hir_id) { return; } for [s1, s2, s3] in block.stmts.array_windows::<3>() { if let StmtKind::Let(tmp) = s1.kind // let t = foo(); && let Some(tmp_init) = tmp.init && let PatKind::Binding(.., ident, None) = tmp.pat.kind // foo() = bar(); && let StmtKind::Semi(first) = s2.kind && let ExprKind::Assign(lhs1, rhs1, _) = first.kind // bar() = t; && let StmtKind::Semi(second) = s3.kind && let ExprKind::Assign(lhs2, rhs2, _) = second.kind && let ExprKind::Path(QPath::Resolved(None, rhs2)) = rhs2.kind && rhs2.segments.len() == 1 && ident.name == rhs2.segments[0].ident.name && eq_expr_value(cx, tmp_init, lhs1) && eq_expr_value(cx, rhs1, lhs2) && let ctxt = s1.span.ctxt() && s2.span.ctxt() == ctxt && s3.span.ctxt() == ctxt && first.span.ctxt() == ctxt && second.span.ctxt() == ctxt { let span = s1.span.to(s3.span); generate_swap_warning(cx, lhs1, lhs2, span, false); } } } /// Implementation of the `ALMOST_SWAPPED` lint. fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { for [first, second] in block.stmts.array_windows() { if let Some((lhs0, rhs0)) = parse(first) && let Some((lhs1, rhs1)) = parse(second) && first.span.eq_ctxt(second.span) && !in_external_macro(cx.sess(), first.span) && is_same(cx, lhs0, rhs1) && is_same(cx, lhs1, rhs0) && !is_same(cx, lhs1, rhs1) // Ignore a = b; a = a (#10421) && let Some(lhs_sugg) = match &lhs0 { ExprOrIdent::Expr(expr) => Sugg::hir_opt(cx, expr), ExprOrIdent::Ident(ident) => Some(Sugg::NonParen(ident.as_str().into())), } && let Some(rhs_sugg) = Sugg::hir_opt(cx, rhs0) { let span = first.span.to(rhs1.span); let Some(sugg) = std_or_core(cx) else { return }; span_lint_and_then( cx, ALMOST_SWAPPED, span, &format!("this looks like you are trying to swap `{lhs_sugg}` and `{rhs_sugg}`"), |diag| { diag.span_suggestion( span, "try", format!("{sugg}::mem::swap({}, {})", lhs_sugg.mut_addr(), rhs_sugg.mut_addr()), Applicability::MaybeIncorrect, ); diag.note(format!("or maybe you should use `{sugg}::mem::replace`?")); }, ); } } } fn is_same(cx: &LateContext<'_>, lhs: ExprOrIdent<'_>, rhs: &Expr<'_>) -> bool { match lhs { ExprOrIdent::Expr(expr) => eq_expr_value(cx, expr, rhs), ExprOrIdent::Ident(ident) => { if let ExprKind::Path(QPath::Resolved(None, path)) = rhs.kind && let [segment] = &path.segments && segment.ident == ident { true } else { false } }, } } #[derive(Debug, Clone, Copy)] enum ExprOrIdent<'a> { Expr(&'a Expr<'a>), Ident(Ident), } fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr<'hir>)> { if let StmtKind::Semi(expr) = stmt.kind { if let ExprKind::Assign(lhs, rhs, _) = expr.kind { return Some((ExprOrIdent::Expr(lhs), rhs)); } } else if let StmtKind::Let(expr) = stmt.kind { if let Some(rhs) = expr.init { if let PatKind::Binding(_, _, ident_l, _) = expr.pat.kind { return Some((ExprOrIdent::Ident(ident_l), rhs)); } } } None } /// Implementation of the xor case for `MANUAL_SWAP` lint. fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) { for [s1, s2, s3] in block.stmts.array_windows::<3>() { let ctxt = s1.span.ctxt(); if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(s1, ctxt) && let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(s2, ctxt) && let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(s3, ctxt) && eq_expr_value(cx, lhs0, rhs1) && eq_expr_value(cx, lhs2, rhs1) && eq_expr_value(cx, lhs1, rhs0) && eq_expr_value(cx, lhs1, rhs2) && s2.span.ctxt() == ctxt && s3.span.ctxt() == ctxt { let span = s1.span.to(s3.span); generate_swap_warning(cx, lhs0, rhs0, span, true); }; } } /// Returns the lhs and rhs of an xor assignment statement. fn extract_sides_of_xor_assign<'a, 'hir>( stmt: &'a Stmt<'hir>, ctxt: SyntaxContext, ) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> { if let StmtKind::Semi(expr) = stmt.kind && let ExprKind::AssignOp( Spanned { node: BinOpKind::BitXor, .. }, lhs, rhs, ) = expr.kind && expr.span.ctxt() == ctxt { Some((lhs, rhs)) } else { None } }