Skip to content

Commit ec3c227

Browse files
committed
Add new restriction lint on assert!(!..)
1 parent 4931cab commit ec3c227

11 files changed

+154
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3057,6 +3057,7 @@ Released 2018-09-13
30573057
[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
30583058
[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions
30593059
[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
3060+
[`bool_assert_inverted`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_inverted
30603061
[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
30613062
[`borrow_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr
30623063
[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const

clippy_lints/src/bool_assert_comparison.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ declare_clippy_lint! {
2626
/// ```
2727
#[clippy::version = "1.53.0"]
2828
pub BOOL_ASSERT_COMPARISON,
29-
style,
29+
pedantic,
3030
"Using a boolean as comparison value in an assert_* macro when there is no need"
3131
}
3232

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node};
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{Expr, ExprKind, UnOp};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
8+
declare_clippy_lint! {
9+
/// ### What it does
10+
/// This lint warns about the use of inverted conditions in assert-like macros.
11+
///
12+
/// ### Why is this bad?
13+
/// It is all too easy to misread the semantics of an assertion when the
14+
/// logic of the condition is reversed. Explicitly comparing to a boolean
15+
/// value is preferable.
16+
///
17+
/// ### Example
18+
/// ```rust
19+
/// // Bad
20+
/// assert!(!"a".is_empty());
21+
///
22+
/// // Good
23+
/// assert_eq!("a".is_empty(), false);
24+
///
25+
/// // Okay
26+
/// assert_ne!("a".is_empty(), true);
27+
/// ```
28+
#[clippy::version = "1.58.0"]
29+
pub BOOL_ASSERT_INVERTED,
30+
restriction,
31+
"Asserting on an inverted condition"
32+
}
33+
34+
declare_lint_pass!(BoolAssertInverted => [BOOL_ASSERT_INVERTED]);
35+
36+
fn is_inverted(e: &Expr<'_>) -> bool {
37+
matches!(e.kind, ExprKind::Unary(UnOp::Not, _),) && !e.span.from_expansion()
38+
}
39+
40+
impl<'tcx> LateLintPass<'tcx> for BoolAssertInverted {
41+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
42+
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
43+
let macro_name = cx.tcx.item_name(macro_call.def_id);
44+
if !matches!(macro_name.as_str(), "assert" | "debug_assert") {
45+
return;
46+
}
47+
let Some ((a, _)) = find_assert_args(cx, expr, macro_call.expn) else { return };
48+
if !is_inverted(a) {
49+
return;
50+
}
51+
52+
let macro_name = macro_name.as_str();
53+
let eq_mac = format!("{}_eq", macro_name);
54+
span_lint_and_sugg(
55+
cx,
56+
BOOL_ASSERT_INVERTED,
57+
macro_call.span,
58+
&format!("used `{}!` with an inverted condition", macro_name),
59+
"replace it with",
60+
format!("{}!(.., false, ..)", eq_mac),
61+
Applicability::MaybeIncorrect,
62+
);
63+
}
64+
}

clippy_lints/src/lib.register_all.rs

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
1818
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
1919
LintId::of(blacklisted_name::BLACKLISTED_NAME),
2020
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
21-
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
2221
LintId::of(booleans::LOGIC_BUG),
2322
LintId::of(booleans::NONMINIMAL_BOOL),
2423
LintId::of(casts::CAST_REF_TO_MUT),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ store.register_lints(&[
5757
blacklisted_name::BLACKLISTED_NAME,
5858
blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
5959
bool_assert_comparison::BOOL_ASSERT_COMPARISON,
60+
bool_assert_inverted::BOOL_ASSERT_INVERTED,
6061
booleans::LOGIC_BUG,
6162
booleans::NONMINIMAL_BOOL,
6263
borrow_as_ptr::BORROW_AS_PTR,

clippy_lints/src/lib.register_pedantic.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
77
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
88
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
99
LintId::of(bit_mask::VERBOSE_BIT_MASK),
10+
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
1011
LintId::of(borrow_as_ptr::BORROW_AS_PTR),
1112
LintId::of(bytecount::NAIVE_BYTECOUNT),
1213
LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS),

clippy_lints/src/lib.register_restriction.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
88
LintId::of(as_conversions::AS_CONVERSIONS),
99
LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
1010
LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
11+
LintId::of(bool_assert_inverted::BOOL_ASSERT_INVERTED),
1112
LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
1213
LintId::of(create_dir::CREATE_DIR),
1314
LintId::of(dbg_macro::DBG_MACRO),

clippy_lints/src/lib.register_style.rs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
77
LintId::of(assign_ops::ASSIGN_OP_PATTERN),
88
LintId::of(blacklisted_name::BLACKLISTED_NAME),
99
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
10-
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
1110
LintId::of(casts::FN_TO_NUMERIC_CAST),
1211
LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
1312
LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ mod bit_mask;
175175
mod blacklisted_name;
176176
mod blocks_in_if_conditions;
177177
mod bool_assert_comparison;
178+
mod bool_assert_inverted;
178179
mod booleans;
179180
mod borrow_as_ptr;
180181
mod bytecount;
@@ -821,6 +822,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
821822
store.register_late_pass(|| Box::new(manual_map::ManualMap));
822823
store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
823824
store.register_late_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
825+
store.register_late_pass(|| Box::new(bool_assert_inverted::BoolAssertInverted));
824826
store.register_early_pass(move || Box::new(module_style::ModStyle));
825827
store.register_late_pass(|| Box::new(unused_async::UnusedAsync));
826828
let disallowed_types = conf.disallowed_types.clone();

tests/ui/bool_assert_inverted.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#![warn(clippy::bool_assert_inverted)]
2+
3+
use std::ops::Not;
4+
5+
macro_rules! a {
6+
() => {
7+
true
8+
};
9+
}
10+
macro_rules! b {
11+
() => {
12+
true
13+
};
14+
}
15+
16+
#[derive(Debug, Clone, Copy)]
17+
struct ImplNotTraitWithBool;
18+
19+
impl PartialEq<bool> for ImplNotTraitWithBool {
20+
fn eq(&self, other: &bool) -> bool {
21+
false
22+
}
23+
}
24+
25+
impl Not for ImplNotTraitWithBool {
26+
type Output = bool;
27+
28+
fn not(self) -> Self::Output {
29+
true
30+
}
31+
}
32+
33+
fn main() {
34+
let a = ImplNotTraitWithBool;
35+
36+
assert!(!"a".is_empty());
37+
assert!("".is_empty());
38+
assert!(!a);
39+
assert!(a);
40+
41+
debug_assert!(!"a".is_empty());
42+
debug_assert!("".is_empty());
43+
debug_assert!(!a);
44+
debug_assert!(a);
45+
46+
assert!(!"a".is_empty(), "tadam {}", false);
47+
assert!("".is_empty(), "tadam {}", false);
48+
assert!(!a, "tadam {}", false);
49+
assert!(a, "tadam {}", false);
50+
51+
debug_assert!(!"a".is_empty(), "tadam {}", false);
52+
debug_assert!("".is_empty(), "tadam {}", false);
53+
debug_assert!(!a, "tadam {}", false);
54+
debug_assert!(a, "tadam {}", false);
55+
}

tests/ui/bool_assert_inverted.stderr

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: used `debug_assert!` with an inverted condition
2+
--> $DIR/bool_assert_inverted.rs:41:5
3+
|
4+
LL | debug_assert!(!"a".is_empty());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert_eq!(.., false, ..)`
6+
|
7+
= note: `-D clippy::bool-assert-inverted` implied by `-D warnings`
8+
9+
error: used `debug_assert!` with an inverted condition
10+
--> $DIR/bool_assert_inverted.rs:43:5
11+
|
12+
LL | debug_assert!(!a);
13+
| ^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert_eq!(.., false, ..)`
14+
15+
error: used `debug_assert!` with an inverted condition
16+
--> $DIR/bool_assert_inverted.rs:51:5
17+
|
18+
LL | debug_assert!(!"a".is_empty(), "tadam {}", false);
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert_eq!(.., false, ..)`
20+
21+
error: used `debug_assert!` with an inverted condition
22+
--> $DIR/bool_assert_inverted.rs:53:5
23+
|
24+
LL | debug_assert!(!a, "tadam {}", false);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert_eq!(.., false, ..)`
26+
27+
error: aborting due to 4 previous errors
28+

0 commit comments

Comments
 (0)