Skip to content

Commit ea08daf

Browse files
barun511ada4a
andcommitted
Add ifs_as_logical_ops lint
Co-authored-by: Ada Alakbarova <ada.alakbarova@proton.me>
1 parent 5464039 commit ea08daf

9 files changed

Lines changed: 548 additions & 0 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6977,6 +6977,7 @@ Released 2018-09-13
69776977
[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else
69786978
[`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else
69796979
[`if_then_some_else_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none
6980+
[`ifs_as_logical_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_as_logical_ops
69806981
[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
69816982
[`ignore_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#ignore_without_reason
69826983
[`ignored_unit_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ignored_unit_patterns

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
214214
crate::ifs::IF_SAME_THEN_ELSE_INFO,
215215
crate::ifs::IFS_SAME_COND_INFO,
216216
crate::ifs::SAME_FUNCTIONS_IN_IF_CONDITION_INFO,
217+
crate::ifs_as_logical_ops::IFS_AS_LOGICAL_OPS_INFO,
217218
crate::ignored_unit_patterns::IGNORED_UNIT_PATTERNS_INFO,
218219
crate::impl_hash_with_borrow_str_and_bytes::IMPL_HASH_BORROW_WITH_STR_AND_BYTES_INFO,
219220
crate::implicit_hasher::IMPLICIT_HASHER_INFO,
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::higher::If;
3+
use clippy_utils::source::{SpanExt, walk_span_to_context};
4+
use clippy_utils::sugg::Sugg;
5+
use clippy_utils::{is_else_clause, is_from_proc_macro, peel_blocks};
6+
use rustc_ast::LitKind;
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{Expr, ExprKind};
9+
use rustc_lint::{LateContext, LateLintPass, LintContext};
10+
use rustc_session::declare_lint_pass;
11+
use rustc_span::Span;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
///
16+
/// Warn about cases where `x && y` could be used in place of an if condition.
17+
///
18+
/// ### Why is this bad?
19+
///
20+
/// `x && y` is more standard as a construction, and makes it clearer that this is just an and.
21+
/// It is also less verbose.
22+
///
23+
/// ### Example
24+
/// ```no_run
25+
/// # fn a() -> bool { false }
26+
/// fn b(b1: bool) -> bool {
27+
/// if b1 { a() } else { false}
28+
/// }
29+
///
30+
/// ```
31+
///
32+
/// Could be written
33+
///
34+
/// ```no_run
35+
/// # fn a() -> bool { false }
36+
/// fn b(b1: bool) -> bool {
37+
/// b1 && a()
38+
/// }
39+
/// ```
40+
#[clippy::version = "1.99.0"]
41+
pub IFS_AS_LOGICAL_OPS,
42+
pedantic,
43+
"`if` conditions that can be rewritten as logical operators"
44+
}
45+
46+
declare_lint_pass!(IfsAsLogicalOps => [IFS_AS_LOGICAL_OPS]);
47+
48+
impl<'tcx> LateLintPass<'tcx> for IfsAsLogicalOps {
49+
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) {
50+
// Make sure the if block is not an if-let block.
51+
if let Some(If {cond: if_cond, then, r#else: Some(els)}) = If::hir(e)
52+
&& let ExprKind::Block(then_block, _label) = then.kind
53+
// Check if the if-block has only a trailing expression
54+
&& then_block.stmts.is_empty()
55+
&& let Some(then_block_inner_expr) = then_block.expr
56+
// And that the else block consists of only the boolean 'false'.
57+
&& let ExprKind::Block(else_block, _label) = els.kind
58+
&& else_block.stmts.is_empty()
59+
&& let Some(else_block_inner_expr) = else_block.expr
60+
&& let ExprKind::Lit(lit) = else_block_inner_expr.kind
61+
&& matches!(lit.node, LitKind::Bool(false))
62+
// We do not emit this lint if the expression diverges.
63+
&& !cx.typeck_results().expr_ty(then_block_inner_expr).is_never()
64+
// Make sure that the expression is only in a single macro context
65+
&& let ctxt = e.span.ctxt()
66+
&& ctxt == then_block.span.ctxt()
67+
&& ctxt == else_block.span.ctxt()
68+
&& ctxt == else_block_inner_expr.span.ctxt()
69+
&& ctxt == lit.span.ctxt()
70+
&& !ctxt.in_external_macro(cx.sess().source_map())
71+
&& !is_from_proc_macro(cx,e)
72+
{
73+
// Do not lint if the statement is trivially a boolean.
74+
if let ExprKind::Lit(lit_ptr) = peel_blocks(then_block_inner_expr).kind
75+
&& let LitKind::Bool(_) = lit_ptr.node
76+
{
77+
return;
78+
}
79+
80+
if let Some(walked_then_block_inner) = walk_span_to_context(then_block_inner_expr.span, ctxt)
81+
&& check_that_nested_spans_have_no_comments(then_block.span, walked_then_block_inner, cx)
82+
&& check_that_nested_spans_have_no_comments(else_block.span, else_block_inner_expr.span, cx)
83+
{
84+
let mut applicability = if ctxt.is_root() {
85+
Applicability::MachineApplicable
86+
} else {
87+
Applicability::MaybeIncorrect
88+
};
89+
90+
let mut sugg = Sugg::hir_with_context(cx, if_cond, ctxt, "_", &mut applicability);
91+
let rhs_sugg = Sugg::hir_with_context(cx, then_block_inner_expr, ctxt, "_", &mut applicability);
92+
93+
sugg = sugg.and(&rhs_sugg);
94+
95+
if is_else_clause(cx.tcx, e) {
96+
sugg = sugg.blockify();
97+
}
98+
99+
span_lint_and_sugg(
100+
cx,
101+
IFS_AS_LOGICAL_OPS,
102+
e.span,
103+
"if expression that could be written as a logical and expression",
104+
"try",
105+
sugg.to_string(),
106+
applicability,
107+
);
108+
}
109+
}
110+
}
111+
}
112+
113+
fn check_that_nested_spans_have_no_comments(outer_span: Span, inner_span: Span, cx: &LateContext<'_>) -> bool {
114+
(outer_span.lo()..inner_span.lo()).check_text(cx, |src| src.trim_end() == "{")
115+
&& (inner_span.hi()..outer_span.hi()).check_text(cx, |src| src.trim_start() == "}")
116+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ mod if_let_mutex;
156156
mod if_not_else;
157157
mod if_then_some_else_none;
158158
mod ifs;
159+
mod ifs_as_logical_ops;
159160
mod ignored_unit_patterns;
160161
mod impl_hash_with_borrow_str_and_bytes;
161162
mod implicit_hasher;
@@ -868,6 +869,7 @@ rustc_lint::late_lint_methods!(
868869
RedundantElse: redundant_else::RedundantElse = redundant_else::RedundantElse,
869870
RestWhenDestructuringStruct: rest_when_destructuring_struct::RestWhenDestructuringStruct = rest_when_destructuring_struct::RestWhenDestructuringStruct,
870871
BlockScrutinee: block_scrutinee::BlockScrutinee = block_scrutinee::BlockScrutinee,
872+
IfsAsLogicalOps: ifs_as_logical_ops::IfsAsLogicalOps = ifs_as_logical_ops::IfsAsLogicalOps,
871873
// add late passes here, used by `cargo dev new_lint`
872874
]]
873875
);

tests/ui/ifs_as_logical_ops.fixed

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//@aux-build:proc_macros.rs
2+
#![warn(clippy::ifs_as_logical_ops)]
3+
#![expect(clippy::needless_bool)]
4+
5+
extern crate proc_macros;
6+
use proc_macros::{external, with_span};
7+
8+
fn main() {
9+
// test code goes here
10+
}
11+
12+
fn basic_lint_test(b1: bool, b2: bool) -> bool {
13+
b1 && b2
14+
//~^ ifs_as_logical_ops
15+
}
16+
17+
fn lint_test_with_cfg_as_second_argument(b1: bool, b2: bool) -> bool {
18+
if b1 { b2 } else { cfg!(false) }
19+
}
20+
21+
fn nested_expressions_produces_lint(b1: bool, b2: bool) -> bool {
22+
b1 && { b2 }
23+
//~^ ifs_as_logical_ops
24+
}
25+
26+
fn diverging_does_not_produce_lint(b1: bool, b2: bool) -> bool {
27+
if b1 { panic!() } else { false }
28+
}
29+
30+
fn complex_expressions_do_not_produce_lint(b1: bool, b2: bool) -> bool {
31+
if b1 {
32+
let mut some_value = 100;
33+
if some_value < 50 {
34+
return true;
35+
}
36+
true
37+
} else {
38+
false
39+
}
40+
}
41+
42+
fn example_with_if_comment_before_expr_does_not_lint(chars: Vec<char>) -> bool {
43+
if !chars.is_empty() {
44+
// SAFETY: Always starts with ^[ and ends with m.
45+
chars.len() > 5
46+
} else {
47+
false
48+
}
49+
}
50+
51+
fn example_with_if_comment_after_expr_does_not_lint(chars: Vec<char>) -> bool {
52+
if !chars.is_empty() {
53+
chars.len() > 5
54+
// SAFETY: Always starts with ^[ and ends with m.
55+
} else {
56+
false
57+
}
58+
}
59+
60+
fn example_with_else_comment_before_expr_does_not_lint(chars: Vec<char>) -> bool {
61+
if !chars.is_empty() {
62+
chars.len() > 5
63+
} else {
64+
// Watch out! Comment!
65+
false
66+
}
67+
}
68+
69+
fn example_with_else_comment_after_expr_does_not_lint(chars: Vec<char>) -> bool {
70+
if !chars.is_empty() {
71+
chars.len() > 5
72+
} else {
73+
false
74+
// Watch out! Comment!
75+
}
76+
}
77+
78+
fn example_with_cfg_does_not_lint(chars: Vec<char>) -> bool {
79+
if !chars.is_empty() {
80+
#[cfg(false)]
81+
return 2 * 2 == 5;
82+
chars.len() > 5
83+
} else {
84+
false
85+
}
86+
}
87+
88+
fn example_with_debug_does_not_lint(b1: bool) -> bool {
89+
if b1 {
90+
dbg!("Something");
91+
true
92+
} else {
93+
false
94+
}
95+
}
96+
97+
fn basic_if_let_does_not_lint(b1: bool, b2: Option<i32>) -> bool {
98+
if let Some(30) = b2 { b1 } else { false }
99+
}
100+
101+
fn if_and_let_does_not_lint(b1: bool, b2: Option<i32>, b3: bool) -> bool {
102+
if b1 && let Some(b) = b2 { b3 } else { false }
103+
}
104+
105+
fn if_let_complex_does_not_lint(b1: bool, b2: Option<i32>, b3: bool) -> bool {
106+
if let Some(b) = b2
107+
&& b1
108+
{
109+
b3
110+
} else {
111+
false
112+
}
113+
}
114+
115+
fn else_if_does_lint(b1: bool, b2: bool, b3: bool) -> bool {
116+
if b1 {
117+
// There is some expansion here.
118+
let _ = 40;
119+
false
120+
} else { b2 && b3 }
121+
//~^^^^^ ifs_as_logical_ops
122+
}
123+
124+
fn needless_bool_clash_does_not_lint(x: bool) -> bool {
125+
if x { true } else { false }
126+
}
127+
128+
macro_rules! always_return_true {
129+
() => {
130+
true
131+
};
132+
}
133+
134+
fn needless_bool_macro_clash_does_not_lint(b1: bool) -> bool {
135+
if b1 { always_return_true!() } else { false }
136+
}
137+
138+
macro_rules! nothing {
139+
() => {};
140+
}
141+
142+
fn empty_expansion_does_not_lint(b1: bool, b2: bool) -> bool {
143+
if b1 {
144+
nothing!();
145+
b2
146+
} else {
147+
false
148+
}
149+
}
150+
151+
fn macro_with_comment_does_not_lint(b1: bool, num: i32) -> bool {
152+
macro_rules! b2 {
153+
() => {{ num > 33 }};
154+
}
155+
if b1 {
156+
// watch out! Comment!
157+
b2!()
158+
} else {
159+
false
160+
}
161+
}
162+
163+
fn macro_without_comment_does_lint(b1: bool, num: i32) -> bool {
164+
macro_rules! b2 {
165+
() => {{ num > 33 }};
166+
}
167+
b1 && b2!()
168+
//~^ ifs_as_logical_ops
169+
}
170+
171+
fn proc_macro_tests() {
172+
proc_macros::external! {
173+
fn would_lint_usually(b1: bool, num: i32) -> bool {
174+
if b1 { num > 30 } else { false }
175+
}
176+
}
177+
proc_macros::with_span! {
178+
span
179+
fn would_lint_usually_2(b1: bool, num: i32) -> bool {
180+
if b1 { num > 30 } else { false }
181+
}
182+
}
183+
}

0 commit comments

Comments
 (0)