blob: a0c701b6c24a99d81a38d19dc434b5fee7bf49d4 [file] [log] [blame] [edit]
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{MEM_TAKE, Msrv};
use clippy_utils::source::snippet_with_context;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Detects manual re-implementations of `std::mem::take`.
///
/// ### Why is this bad?
/// Because the function call is shorter and easier to read.
///
/// ### Known issues
/// Currently the lint only detects cases involving `bool`s.
///
/// ### Example
/// ```no_run
/// let mut x = true;
/// let _ = if x {
/// x = false;
/// true
/// } else {
/// false
/// };
/// ```
/// Use instead:
/// ```no_run
/// let mut x = true;
/// let _ = std::mem::take(&mut x);
/// ```
#[clippy::version = "1.94.0"]
pub MANUAL_TAKE,
complexity,
"manual `mem::take` implementation"
}
impl_lint_pass!(ManualTake => [MANUAL_TAKE]);
pub struct ManualTake {
msrv: Msrv,
}
impl ManualTake {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl LateLintPass<'_> for ManualTake {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::If(cond, then, Some(otherwise)) = expr.kind
&& let ExprKind::Path(_) = cond.kind
&& let ExprKind::Block(
Block {
stmts: [stmt],
expr: Some(then_expr),
..
},
..,
) = then.kind
&& let ExprKind::Block(
Block {
stmts: [],
expr: Some(else_expr),
..
},
..,
) = otherwise.kind
&& let StmtKind::Semi(assignment) = stmt.kind
&& let ExprKind::Assign(mut_c, possible_false, _) = assignment.kind
&& let ExprKind::Path(_) = mut_c.kind
&& !expr.span.in_external_macro(cx.sess().source_map())
&& let Some(std_or_core) = clippy_utils::std_or_core(cx)
&& self.msrv.meets(cx, MEM_TAKE)
&& clippy_utils::SpanlessEq::new(cx).eq_expr(cond, mut_c)
&& Some(false) == as_const_bool(possible_false)
&& let Some(then_bool) = as_const_bool(then_expr)
&& let Some(else_bool) = as_const_bool(else_expr)
&& then_bool != else_bool
{
span_lint_and_then(
cx,
MANUAL_TAKE,
expr.span,
"manual implementation of `mem::take`",
|diag| {
let mut app = Applicability::MachineApplicable;
let negate = if then_bool { "" } else { "!" };
let taken = snippet_with_context(cx, cond.span, expr.span.ctxt(), "_", &mut app).0;
diag.span_suggestion_verbose(
expr.span,
"use",
format!("{negate}{std_or_core}::mem::take(&mut {taken})"),
app,
);
},
);
}
}
}
fn as_const_bool(e: &Expr<'_>) -> Option<bool> {
if let ExprKind::Lit(lit) = e.kind
&& let LitKind::Bool(b) = lit.node
{
Some(b)
} else {
None
}
}