rust-lang / rust-lang/rust-clippy

Possibly unintended use of unary `!` of non-`bool` type on left side of boolean expression/comparison

Open
#12,916 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

A-lint
Dominant language
Rust
Stars
13.5k
Forks
2.2k
Avg merge
2d 10h
Merged PRs (30d)
32

Description

What it does

I believe this might be an addition to clippy::precedence, but I'm not sure.

The unary ! (std::ops::Not) has precedence over comparisons (like >, ==, >=, etc).

Rust's syntax allows conditional expressions without parentheses in if, match and other constructs:

let a = 1;
let b = 2;
let c = 3;

if a == b || a == c {
    a
} else {
    b
}

This language feature makes it more likely for people to incorrectly assume that to negate the comparison or entire conditional, they can simply add a ! at the front or left hand of it, just like they would do if it was wrapped in parentheses in other languages. The fact that boolean not and bitwise not use the same symbol could further add to this confusion.

Instead of negating the expression, this performs a bitwise not on a, and does not trigger compiler warnings or existing Clippy lints:

if !a == b || a == c {
    a
} else {
    b
}

When a bitwise not of a non-bool type is performed at the start of a boolean expression (maybe additionally: on the left-hand side of a boolean comparison within a larger expression), Clippy should warn the user that they are not negating a boolean expression, but performing a bitwise not on a variable.

To reduce false positives, I think exceptions can be made when:

  • Bitwise not on non-bool types additionally appears somewhere else in the expression (like !a == b || a == !c), meaning the user is more likely to be aware of the behavior of ! used on a non-bool variable.
  • The expression contains other bitwise operators (like <<, ^=) making the use of another bitwise operator (!) more likely, and also meaning the user is more likely to be aware of the bitwise behavior of ! used on a non-bool variable.
  • The expression in question is already negated (like !(!a == b || a == c), !a != c)

I am unsure whether this should only apply to entire expression, or also to its sub-expressions, especially when they're boolean comparisons (meaning a < b && !a > c would warn about the !a > c part), which would probably lead to more false positives but also catch more mistakes.

Advantage
  • Make the user aware of accidental use of bitwise not where they meant to perform the much more common boolean negation.
Drawbacks
  • The user could see a warning when they genuinely meant to perform a bitwise not on the value at the very start of a boolean expression, or the left-hand side of a boolean comparison.
Example
let a = 1;
let b = 2;
let c = 3;

if !a == b || a == c {
    println!("`a` is *not* equal to `b` or `c`");
} else {
    println!("`a` is equal to `b` or `c`");
}

Could be intended as:

let a = 1;
let b = 2;
let c = 3;

if !(a == b || a == c) {
    println!("`a` is *not* equal to `b` or `c`");
} else {
    println!("`a` is equal to `b` or `c`");
}

(optionally stricter, also works on sub-expressions:)

fn main() {
    let a = 1;
    let b = 2;
    let c = 3;

    if a < b && !a < c {
        println!("`a` is less than `b` but not less than `c`");
    } else {
        println!("`a` is *not* less than `b` but not less than `c`");
    }
}

Could be intended as:

fn main() {
    let a = 1;
    let b = 2;
    let c = 3;

    if a < b && !(a < c) {
        println!("`a` is less than `b` but not less than `c`");
    } else {
        println!("`a` is *not* less than `b` but not less than `c`");
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the existing clippy::precedence lint and the expression examples in this issue. Define the intended warning scope and false-positive exceptions for non-bool unary !; the work is done when the lint behavior and affected expression cases are specified and covered by appropriate Clippy tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.