Repository: MartinThoma/flake8-simplify (python)

Issue:
[SIM909] Avoid reflexive assignment
## Explanation

Assigning a name to itself is a no-op and should be eliminated.

## Example

```python
# Bad
foo = foo

# Good
# (nothing)

# Bad
foo = foo = 42

# Good
foo = 42
```

Pull request:
SIM909: Avoid reflexive assignments
Closes #114

Patch:
diff --git a/README.md b/README.md
index 8fb5541..79eb00a 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,7 @@ General Code Style:
 * [`SIM112`](https://github.com/MartinThoma/flake8-simplify/issues/19): Use CAPITAL environment variables ([example](#SIM112))
 * `SIM122`: Reserved for SIM902 once it's stable
 * `SIM123`: Reserved for SIM903 once it's stable
+* `SIM124`: Reserved for SIM909 once it's stable
 
 **Experimental rules:**
 
@@ -631,3 +632,26 @@ if "some_key" in some_dict:
 # Good
 name = some_dict.get("some_key", "some_default")
 ```
+
+### SIM909
+
+Thank you Ryan Delaney for the idea!
+
+The trial period starts on 28th of March and will end on 28th of September 2022.
+
+```python
+# Bad
+foo = foo
+
+# Good: Nothing. Reflexive assignments have no purpose.
+```
+
+or
+
+```python
+# Bad
+foo = foo = 42
+
+# Good
+foo = 42
+```
diff --git a/flake8_simplify/__init__.py b/flake8_simplify/__init__.py
index 1255e3d..e7f470a 100644
--- a/flake8_simplify/__init__.py
+++ b/flake8_simplify/__init__.py
@@ -5,7 +5,7 @@ import sys
 from typing import Any, Generator, List, Tuple, Type
 
 # First party
-from flake8_simplify.rules.ast_assign import get_sim904
+from flake8_simplify.rules.ast_assign import get_sim904, get_sim909
 from flake8_simplify.rules.ast_bool_op import (
     get_sim101,
     get_sim109,
@@ -68,6 +68,7 @@ class Visitor(ast.NodeVisitor):
 
     def visit_Assign(self, node: ast.Assign) -> Any:
         self.errors += get_sim904(node)
+        self.errors += get_sim909(node)
         self.generic_visit(node)
 
     def visit_Call(self, node: ast.Call) -> Any:
diff --git a/flake8_simplify/rules/ast_assign.py b/flake8_simplify/rules/ast_assign.py
index 82ba8f6..6dadb8f 100644
--- a/flake8_simplify/rules/ast_assign.py
+++ b/flake8_simplify/rules/ast_assign.py
@@ -67,3 +67,47 @@ def get_sim904(node: ast.Assign) -> List[Tuple[int, int, str]]:
         )
     )
     return errors
+
+
+def get_sim909(node: ast.Assign) -> List[Tuple[int, int, str]]:
+    """
+    Avoid reflexive assignments
+
+    Example
+    -------
+    Code:
+        # Bad
+        foo = foo
+
+        # Good: Just remove them
+    Bad AST:
+        [
+            Assign(
+                targets=[Name(id='foo', ctx=Store())],
+                value=Name(id='foo', ctx=Load()),
+                type_comment=None,
+            ),
+        ]
+    """
+    RULE = "SIM909 Remove reflexive assignment '{code}'"
+    errors: List[Tuple[int, int, str]] = []
+
+    names = []
+    if isinstance(node.value, (ast.Name, ast.Subscript, ast.Tuple)):
+        names.append(to_source(node.value))
+    for target in node.targets:
+        names.append(to_source(target))
+
+    if len(names) == len(set(names)):
+        return errors
+
+    code = to_source(node)
+
+    errors.append(
+        (
+            node.lineno,
+            node.col_offset,
+            RULE.format(code=code),
+        )
+    )
+    return errors
diff --git a/flake8_simplify/utils.py b/flake8_simplify/utils.py
index a51b7f8..89a38fe 100644
--- a/flake8_simplify/utils.py
+++ b/flake8_simplify/utils.py
@@ -69,7 +69,7 @@ class For(ast.For):
 
 
 def to_source(
-    node: Union[None, ast.expr, ast.Expr, ast.withitem, ast.slice]
+    node: Union[None, ast.expr, ast.Expr, ast.withitem, ast.slice, ast.Assign]
 ) -> str:
     if node is None:
         return "None"


Test patch:
diff --git a/tests/test_900_rules.py b/tests/test_900_rules.py
index 13cd68e..df174fe 100644
--- a/tests/test_900_rules.py
+++ b/tests/test_900_rules.py
@@ -193,3 +193,47 @@ if "some_key" in some_dict:
         "2:0 SIM908 Use 'some_dict.get(\"some_key\")' instead of "
         '\'if "some_key" in some_dict: some_dict["some_key"]\''
     }
+
+
+@pytest.mark.parametrize(
+    ("s", "msg"),
+    (
+        # Credits to Ryan Delaney for the following two example
+        # https://github.com/MartinThoma/flake8-simplify/issues/114
+        (
+            "foo = foo",
+            "1:0 SIM909 Remove reflexive assignment 'foo = foo'",
+        ),
+        (
+            "foo = foo = 42",
+            "1:0 SIM909 Remove reflexive assignment 'foo = foo = 42'",
+        ),
+        (
+            "foo = bar = foo = 42",
+            "1:0 SIM909 Remove reflexive assignment 'foo = bar = foo = 42'",
+        ),
+        (
+            "n, m = n, m",
+            "1:0 SIM909 Remove reflexive assignment 'n, m = n, m'",
+        ),
+        (
+            "a['foo'] = a['foo']",
+            "1:0 SIM909 Remove reflexive assignment 'a['foo'] = a['foo']'",
+        ),
+    ),
+    ids=["simple", "double", "multiple", "tuple", "dict"],
+)
+def test_sim909(s, msg):
+    results = _results(s)
+    assert results == {msg}
+
+
+@pytest.mark.parametrize(
+    ("s"),
+    ("n, m = m, n", "foo = 'foo'"),
+    ids=["tuple-switch", "variable"],
+)
+def test_sim909_false_positives(s):
+    results = _results(s)
+    for result in results:
+        assert "SIM909" not in result