Repository: wemake-services/wemake-python-styleguide

Issue:
False positive 434: toggling a boolean
### What's wrong

The following code triggers a WPS434 violation warning.

```python
in_code_block = not in_code_block
```

### How it should be

No warning. There's no better way to toggle a boolean.

### Flake8 version and plugins

```json
{
  "dependencies": [],
  "platform": {
    "python_implementation": "CPython",
    "python_version": "3.8.12",
    "system": "Linux"
  },
  "plugins": [
    {
      "is_local": false,
      "plugin": "black",
      "version": "0.2.3"
    },
    {
      "is_local": false,
      "plugin": "flake8-bandit",
      "version": "2.1.2"
    },
    {
      "is_local": false,
      "plugin": "flake8-bugbear",
      "version": "21.9.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-comprehensions",
      "version": "3.6.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-darglint",
      "version": "1.8.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-docstrings",
      "version": "1.6.0, pydocstyle: 6.1.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-pytest-style",
      "version": "1.5.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-string-format",
      "version": "0.3.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-tidy-imports",
      "version": "4.4.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-variables-names",
      "version": "0.0.4"
    },
    {
      "is_local": false,
      "plugin": "flake8_builtins",
      "version": "1.5.2"
    },
    {
      "is_local": false,
      "plugin": "mccabe",
      "version": "0.6.1"
    },
    {
      "is_local": false,
      "plugin": "naming",
      "version": "0.12.1"
    },
    {
      "is_local": false,
      "plugin": "pycodestyle",
      "version": "2.7.0"
    },
    {
      "is_local": false,
      "plugin": "pyflakes",
      "version": "2.3.1"
    },
    {
      "is_local": false,
      "plugin": "wps-light",
      "version": "0.15.3"
    }
  ],
  "version": "3.9.2"
}
```

### pip information

pip 21.2.4 from /home/pawamoy/.local/lib/python3.8/site-packages/pip (python 3.8)

Not using pip to manage packages.

### OS information

GNU/Linux 5.14.6


Discussion:
Clear bug! Thanks a lot for the report.

Do you want to work on it?
I guess it should be fixed somewhere here: https://github.com/wemake-services/wemake-python-styleguide/blob/de7aedbdd8f72f998033c40b8a9fc1f894f7179b/wemake_python_styleguide/visitors/ast/naming/variables.py#L87-L93

I'll try to do that in the next few days :slightly_smiling_face: 
Any additional hint appreciated!
Yes, somewhere there! 👍 
If this is not taken already, I would like to work on it.
Not taken :slightly_smiling_face: 
@lensvol awesome! I was hoping you will return for another hactoberest season! This is a good tradition! 😊 
@pawamoy Can you please tell me what is type(in_code_block) . Is it <class 'bool'>?
It is a boolean indeed!

Resolving patch:
diff --git a/wemake_python_styleguide/violations/best_practices.py b/wemake_python_styleguide/violations/best_practices.py
--- a/wemake_python_styleguide/violations/best_practices.py
+++ b/wemake_python_styleguide/violations/best_practices.py
@@ -1424,13 +1424,14 @@ class ReassigningVariableToItselfViolation(ASTViolation):
         # Correct:
         some = some + 1
         x_coord, y_coord = y_coord, x_coord
+        flag = not flag
 
         # Wrong:
         some = some
         x_coord, y_coord = x_coord, y_coord
 
     .. versionadded:: 0.3.0
-    .. versionchanged:: 0.11.0
+    .. versionchanged:: 0.16.0
 
     """
 
diff --git a/wemake_python_styleguide/visitors/ast/naming/variables.py b/wemake_python_styleguide/visitors/ast/naming/variables.py
--- a/wemake_python_styleguide/visitors/ast/naming/variables.py
+++ b/wemake_python_styleguide/visitors/ast/naming/variables.py
@@ -67,6 +67,16 @@ def visit_any_assign(self, node: AnyAssign) -> None:
         self._check_unique_assignment(node, names)
         self.generic_visit(node)
 
+    def _is_reassignment_edge_case(self, node: AnyAssign) -> bool:
+        # This is not a variable, but a class property
+        if isinstance(nodes.get_context(node), ast.ClassDef):
+            return True
+
+        # It means that someone probably modifies original value
+        # of the variable using some unary operation, e.g. `a = not a`
+        # See #2189
+        return isinstance(node.value, ast.UnaryOp)
+
     def _check_reassignment(
         self,
         node: AnyAssign,
@@ -75,8 +85,8 @@ def _check_reassignment(
         if not node.value:
             return
 
-        if isinstance(nodes.get_context(node), ast.ClassDef):
-            return  # This is not a variable, but a class property
+        if self._is_reassignment_edge_case(node):
+            return
 
         var_values = name_nodes.get_variables_from_node(node.value)
         if len(names) <= 1 < len(var_values):


Test patch:
diff --git a/tests/test_visitors/test_ast/test_naming/test_variable_self_reassignment.py b/tests/test_visitors/test_ast/test_naming/test_variable_self_reassignment.py
--- a/tests/test_visitors/test_ast/test_naming/test_variable_self_reassignment.py
+++ b/tests/test_visitors/test_ast/test_naming/test_variable_self_reassignment.py
@@ -86,6 +86,8 @@ class MyClass(object):
 right_swap5 = 'dx, dy = dy, dx'
 right_swap6 = 'dy, dx = dx, dy'
 
+right_unary1 = 'a = not a'
+right_unary2 = 'a = -a'
 
 # Wrong:
 
@@ -267,6 +269,8 @@ def test_self_variable_reassignment_triple(
     right_swap4,
     right_swap5,
     right_swap6,
+    right_unary1,
+    right_unary2,
 ])
 def test_correct_variable_reassignment(
     assert_errors,