Skip to content

Commit

Permalink
Add new restriction lint on assert!(!..)
Browse files Browse the repository at this point in the history
  • Loading branch information
couchand committed Feb 16, 2022
1 parent 4931cab commit fcde7de
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3057,6 +3057,7 @@ Released 2018-09-13
[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions
[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
[`bool_assert_inverted`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_inverted
[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
[`borrow_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr
[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/bool_assert_comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.53.0"]
pub BOOL_ASSERT_COMPARISON,
style,
pedantic,
"Using a boolean as comparison value in an assert_* macro when there is no need"
}

Expand Down
67 changes: 67 additions & 0 deletions clippy_lints/src/bool_assert_inverted.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use clippy_utils::{diagnostics::span_lint_and_sugg, higher, is_direct_expn_of};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
/// ### What it does
/// This lint warns about the use of inverted conditions in assert-like macros.
///
/// ### Why is this bad?
/// It is all too easy to misread the semantics of an assertion when the
/// logic of the condition is reversed. Explicitly comparing to a boolean
/// value is preferable.
///
/// ### Example
/// ```rust
/// // Bad
/// assert!(!"a".is_empty());
///
/// // Good
/// assert_eq!("a".is_empty(), false);
///
/// // Okay
/// assert_ne!("a".is_empty(), true);
/// ```
#[clippy::version = "1.58.0"]
pub BOOL_ASSERT_INVERTED,
restriction,
"Asserting on an inverted condition"
}

declare_lint_pass!(BoolAssertInverted => [BOOL_ASSERT_INVERTED]);

fn is_inverted(e: &Expr<'_>) -> bool {
matches!(e.kind, ExprKind::Unary(UnOp::Not, _),) && !e.span.from_expansion()
}

impl<'tcx> LateLintPass<'tcx> for BoolAssertInverted {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let macros = ["assert", "debug_assert"];

for mac in &macros {
if let Some(span) = is_direct_expn_of(expr.span, mac) {
if let Some(args) = higher::extract_assert_macro_args(expr) {
if let [a, ..] = args[..] {
if !is_inverted(a) {
return;
}

let eq_mac = format!("{}_eq", mac);
span_lint_and_sugg(
cx,
BOOL_ASSERT_INVERTED,
span,
&format!("used `{}!` with an inverted condition", mac),
"replace it with",
format!("{}!(.., false, ..)", eq_mac),
Applicability::MaybeIncorrect,
);
return;
}
}
}
}
}
}
1 change: 0 additions & 1 deletion clippy_lints/src/lib.register_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
LintId::of(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(booleans::LOGIC_BUG),
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(casts::CAST_REF_TO_MUT),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ store.register_lints(&[
blacklisted_name::BLACKLISTED_NAME,
blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
bool_assert_comparison::BOOL_ASSERT_COMPARISON,
bool_assert_inverted::BOOL_ASSERT_INVERTED,
booleans::LOGIC_BUG,
booleans::NONMINIMAL_BOOL,
borrow_as_ptr::BORROW_AS_PTR,
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_pedantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
LintId::of(bit_mask::VERBOSE_BIT_MASK),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(borrow_as_ptr::BORROW_AS_PTR),
LintId::of(bytecount::NAIVE_BYTECOUNT),
LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_restriction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(as_conversions::AS_CONVERSIONS),
LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
LintId::of(bool_assert_inverted::BOOL_ASSERT_INVERTED),
LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
LintId::of(create_dir::CREATE_DIR),
LintId::of(dbg_macro::DBG_MACRO),
Expand Down
1 change: 0 additions & 1 deletion clippy_lints/src/lib.register_style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(assign_ops::ASSIGN_OP_PATTERN),
LintId::of(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(casts::FN_TO_NUMERIC_CAST),
LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ mod bit_mask;
mod blacklisted_name;
mod blocks_in_if_conditions;
mod bool_assert_comparison;
mod bool_assert_inverted;
mod booleans;
mod borrow_as_ptr;
mod bytecount;
Expand Down Expand Up @@ -821,6 +822,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(manual_map::ManualMap));
store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
store.register_late_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
store.register_late_pass(|| Box::new(bool_assert_inverted::BoolAssertInverted));
store.register_early_pass(move || Box::new(module_style::ModStyle));
store.register_late_pass(|| Box::new(unused_async::UnusedAsync));
let disallowed_types = conf.disallowed_types.clone();
Expand Down
55 changes: 55 additions & 0 deletions tests/ui/bool_assert_inverted.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#![warn(clippy::bool_assert_inverted)]

use std::ops::Not;

macro_rules! a {
() => {
true
};
}
macro_rules! b {
() => {
true
};
}

#[derive(Debug, Clone, Copy)]
struct ImplNotTraitWithBool;

impl PartialEq<bool> for ImplNotTraitWithBool {
fn eq(&self, other: &bool) -> bool {
false
}
}

impl Not for ImplNotTraitWithBool {
type Output = bool;

fn not(self) -> Self::Output {
true
}
}

fn main() {
let a = ImplNotTraitWithBool;

assert!(!"a".is_empty());
assert!("".is_empty());
assert!(!a);
assert!(a);

debug_assert!(!"a".is_empty());
debug_assert!("".is_empty());
debug_assert!(!a);
debug_assert!(a);

assert!(!"a".is_empty(), "tadam {}", false);
assert!("".is_empty(), "tadam {}", false);
assert!(!a, "tadam {}", false);
assert!(a, "tadam {}", false);

debug_assert!(!"a".is_empty(), "tadam {}", false);
debug_assert!("".is_empty(), "tadam {}", false);
debug_assert!(!a, "tadam {}", false);
debug_assert!(a, "tadam {}", false);
}
28 changes: 28 additions & 0 deletions tests/ui/bool_assert_inverted.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
error: used `debug_assert!` with an inverted condition
--> $DIR/bool_assert_inverted.rs:41:5
|
LL | debug_assert!(!"a".is_empty());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert_eq!(.., false, ..)`
|
= note: `-D clippy::bool-assert-inverted` implied by `-D warnings`

error: used `debug_assert!` with an inverted condition
--> $DIR/bool_assert_inverted.rs:43:5
|
LL | debug_assert!(!a);
| ^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert_eq!(.., false, ..)`

error: used `debug_assert!` with an inverted condition
--> $DIR/bool_assert_inverted.rs:51:5
|
LL | debug_assert!(!"a".is_empty(), "tadam {}", false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert_eq!(.., false, ..)`

error: used `debug_assert!` with an inverted condition
--> $DIR/bool_assert_inverted.rs:53:5
|
LL | debug_assert!(!a, "tadam {}", false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert_eq!(.., false, ..)`

error: aborting due to 4 previous errors

0 comments on commit fcde7de

Please sign in to comment.