mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-22 10:05:06 +03:00
233 lines
7.6 KiB
Rust
233 lines
7.6 KiB
Rust
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
|
use clippy_utils::source::snippet_with_context;
|
|
use clippy_utils::sugg::Sugg;
|
|
use clippy_utils::{
|
|
SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_parent_stmt, is_receiver_of_method_call,
|
|
peel_blocks, peel_blocks_with_stmt, span_contains_comment,
|
|
};
|
|
use rustc_ast::ast::LitKind;
|
|
use rustc_errors::Applicability;
|
|
use rustc_hir::{Expr, ExprKind};
|
|
use rustc_lint::{LateContext, LateLintPass};
|
|
use rustc_session::declare_lint_pass;
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for expressions of the form `if c { true } else {
|
|
/// false }` (or vice versa) and suggests using the condition directly.
|
|
///
|
|
/// ### Why is this bad?
|
|
/// Redundant code.
|
|
///
|
|
/// ### Known problems
|
|
/// Maybe false positives: Sometimes, the two branches are
|
|
/// painstakingly documented (which we, of course, do not detect), so they *may*
|
|
/// have some value. Even then, the documentation can be rewritten to match the
|
|
/// shorter code.
|
|
///
|
|
/// ### Example
|
|
/// ```no_run
|
|
/// # let x = true;
|
|
/// if x {
|
|
/// false
|
|
/// } else {
|
|
/// true
|
|
/// }
|
|
/// # ;
|
|
/// ```
|
|
///
|
|
/// Use instead:
|
|
/// ```no_run
|
|
/// # let x = true;
|
|
/// !x
|
|
/// # ;
|
|
/// ```
|
|
#[clippy::version = "pre 1.29.0"]
|
|
pub NEEDLESS_BOOL,
|
|
complexity,
|
|
"if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`"
|
|
}
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for expressions of the form `if c { x = true } else { x = false }`
|
|
/// (or vice versa) and suggest assigning the variable directly from the
|
|
/// condition.
|
|
///
|
|
/// ### Why is this bad?
|
|
/// Redundant code.
|
|
///
|
|
/// ### Example
|
|
/// ```rust,ignore
|
|
/// # fn must_keep(x: i32, y: i32) -> bool { x == y }
|
|
/// # let x = 32; let y = 10;
|
|
/// # let mut skip: bool;
|
|
/// if must_keep(x, y) {
|
|
/// skip = false;
|
|
/// } else {
|
|
/// skip = true;
|
|
/// }
|
|
/// ```
|
|
/// Use instead:
|
|
/// ```rust,ignore
|
|
/// # fn must_keep(x: i32, y: i32) -> bool { x == y }
|
|
/// # let x = 32; let y = 10;
|
|
/// # let mut skip: bool;
|
|
/// skip = !must_keep(x, y);
|
|
/// ```
|
|
#[clippy::version = "1.71.0"]
|
|
pub NEEDLESS_BOOL_ASSIGN,
|
|
complexity,
|
|
"setting the same boolean variable in both branches of an if-statement"
|
|
}
|
|
|
|
declare_lint_pass!(NeedlessBool => [NEEDLESS_BOOL, NEEDLESS_BOOL_ASSIGN]);
|
|
|
|
fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
|
|
let mut inner = e;
|
|
while let ExprKind::Binary(_, i, _)
|
|
| ExprKind::Call(i, _)
|
|
| ExprKind::Cast(i, _)
|
|
| ExprKind::Type(i, _)
|
|
| ExprKind::Index(i, _, _) = inner.kind
|
|
{
|
|
if is_block_like(i) {
|
|
return true;
|
|
}
|
|
inner = i;
|
|
}
|
|
false
|
|
}
|
|
|
|
impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
|
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
|
use self::Expression::{Bool, RetBool};
|
|
if !e.span.from_expansion()
|
|
&& let Some(higher::If {
|
|
cond,
|
|
then,
|
|
r#else: Some(else_expr),
|
|
}) = higher::If::hir(e)
|
|
&& !span_contains_comment(cx, e.span)
|
|
{
|
|
let reduce = |ret, not| {
|
|
let mut applicability = Applicability::MachineApplicable;
|
|
let snip = Sugg::hir_with_context(cx, cond, e.span.ctxt(), "<predicate>", &mut applicability);
|
|
let mut snip = if not { !snip } else { snip };
|
|
|
|
if ret {
|
|
snip = snip.make_return();
|
|
}
|
|
|
|
if is_else_clause(cx.tcx, e) {
|
|
snip = snip.blockify();
|
|
}
|
|
|
|
if (condition_needs_parentheses(cond) && is_parent_stmt(cx, e.hir_id))
|
|
|| is_receiver_of_method_call(cx, e)
|
|
|| is_as_argument(cx, e)
|
|
{
|
|
snip = snip.maybe_paren();
|
|
}
|
|
|
|
span_lint_and_sugg(
|
|
cx,
|
|
NEEDLESS_BOOL,
|
|
e.span,
|
|
"this if-then-else expression returns a bool literal",
|
|
"you can reduce it to",
|
|
snip.to_string(),
|
|
applicability,
|
|
);
|
|
};
|
|
if let Some(a) = fetch_bool_block(then)
|
|
&& let Some(b) = fetch_bool_block(else_expr)
|
|
{
|
|
match (a, b) {
|
|
(RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => {
|
|
span_lint(
|
|
cx,
|
|
NEEDLESS_BOOL,
|
|
e.span,
|
|
"this if-then-else expression will always return true",
|
|
);
|
|
},
|
|
(RetBool(false), RetBool(false)) | (Bool(false), Bool(false)) => {
|
|
span_lint(
|
|
cx,
|
|
NEEDLESS_BOOL,
|
|
e.span,
|
|
"this if-then-else expression will always return false",
|
|
);
|
|
},
|
|
(RetBool(true), RetBool(false)) => reduce(true, false),
|
|
(Bool(true), Bool(false)) => reduce(false, false),
|
|
(RetBool(false), RetBool(true)) => reduce(true, true),
|
|
(Bool(false), Bool(true)) => reduce(false, true),
|
|
_ => (),
|
|
}
|
|
}
|
|
if let Some((lhs_a, a)) = fetch_assign(then)
|
|
&& let Some((lhs_b, b)) = fetch_assign(else_expr)
|
|
&& SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b)
|
|
{
|
|
let mut applicability = Applicability::MachineApplicable;
|
|
let cond = Sugg::hir_with_context(cx, cond, e.span.ctxt(), "..", &mut applicability);
|
|
let (lhs, _) = snippet_with_context(cx, lhs_a.span, e.span.ctxt(), "..", &mut applicability);
|
|
let mut sugg = if a == b {
|
|
format!("{cond}; {lhs} = {a:?};")
|
|
} else {
|
|
format!("{lhs} = {};", if a { cond } else { !cond })
|
|
};
|
|
|
|
if is_else_clause(cx.tcx, e) {
|
|
sugg = format!("{{ {sugg} }}");
|
|
}
|
|
|
|
span_lint_and_sugg(
|
|
cx,
|
|
NEEDLESS_BOOL_ASSIGN,
|
|
e.span,
|
|
"this if-then-else expression assigns a bool literal",
|
|
"you can reduce it to",
|
|
sugg,
|
|
applicability,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
enum Expression {
|
|
Bool(bool),
|
|
RetBool(bool),
|
|
}
|
|
|
|
fn fetch_bool_block(expr: &Expr<'_>) -> Option<Expression> {
|
|
match peel_blocks_with_stmt(expr).kind {
|
|
ExprKind::Ret(Some(ret)) => Some(Expression::RetBool(fetch_bool_expr(ret)?)),
|
|
_ => Some(Expression::Bool(fetch_bool_expr(expr)?)),
|
|
}
|
|
}
|
|
|
|
fn fetch_bool_expr(expr: &Expr<'_>) -> Option<bool> {
|
|
if let ExprKind::Lit(lit_ptr) = peel_blocks(expr).kind
|
|
&& let LitKind::Bool(value) = lit_ptr.node
|
|
{
|
|
return Some(value);
|
|
}
|
|
None
|
|
}
|
|
|
|
fn fetch_assign<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, bool)> {
|
|
if let ExprKind::Assign(lhs, rhs, _) = peel_blocks_with_stmt(expr).kind {
|
|
fetch_bool_expr(rhs).map(|b| (lhs, b))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn is_as_argument(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
|
matches!(get_parent_expr(cx, e).map(|e| e.kind), Some(ExprKind::Cast(_, _)))
|
|
}
|