use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::If; use clippy_utils::is_from_proc_macro; use clippy_utils::source::snippet_opt; use rustc_errors::Applicability; use rustc_hir::{ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks for empty `if` branches with no else branch. /// /// ### Why is this bad? /// It can be entirely omitted, and often the condition too. /// /// ### Known issues /// This will usually only suggest to remove the `if` statement, not the condition. Other lints /// such as `no_effect` will take care of removing the condition if it's unnecessary. /// /// ### Example /// ```rust,ignore /// if really_expensive_condition(&i) {} /// if really_expensive_condition_with_side_effects(&mut i) {} /// ``` /// Use instead: /// ```rust,ignore /// // /// really_expensive_condition_with_side_effects(&mut i); /// ``` #[clippy::version = "1.72.0"] pub NEEDLESS_IF, complexity, "checks for empty if branches" } declare_lint_pass!(NeedlessIf => [NEEDLESS_IF]); impl LateLintPass<'_> for NeedlessIf { fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) { if let StmtKind::Expr(expr) = stmt.kind && let Some(If {cond, then, r#else: None }) = If::hir(expr) && let ExprKind::Block(block, ..) = then.kind && block.stmts.is_empty() && block.expr.is_none() && !in_external_macro(cx.sess(), expr.span) && let Some(then_snippet) = snippet_opt(cx, then.span) // Ignore // - empty macro expansions // - empty reptitions in macro expansions // - comments // - #[cfg]'d out code && then_snippet.chars().all(|ch| matches!(ch, '{' | '}') || ch.is_ascii_whitespace()) && let Some(cond_snippet) = snippet_opt(cx, cond.span) && !is_from_proc_macro(cx, expr) { span_lint_and_sugg( cx, NEEDLESS_IF, stmt.span, "this `if` branch is empty", "you can remove it", if cond.can_have_side_effects() || !cx.tcx.hir().attrs(stmt.hir_id).is_empty() { // `{ foo }` or `{ foo } && bar` placed into a statement position would be // interpreted as a block statement, force it to be an expression if cond_snippet.starts_with('{') { format!("({cond_snippet});") } else { format!("{cond_snippet};") } } else { String::new() }, Applicability::MachineApplicable, ); } } }