Repository: MartinThoma/flake8-simplify (2022-03-28 ~ 2022-03-28)

problem_statement: [Adjust Rule] SIM111 - Detect if another statement comes after the loop
## Desired change

* **Rule(s)**: SIM111
* **Adjustment**: Detect if another statement comes after the loop

## Explanation

The below code is flagged with SIM111, but if I would would use the suggested code, the `call_method` call would be lost.

## Example

```python
for a in my_list:
  if a == 2:
    return False
call_method()
return True
```

date: 2022-03-28
patch: diff --git a/flake8_simplify/rules/ast_for.py b/flake8_simplify/rules/ast_for.py
index fcbac36..8d4d984 100644
--- a/flake8_simplify/rules/ast_for.py
+++ b/flake8_simplify/rules/ast_for.py
@@ -97,7 +97,7 @@ def get_sim110_sim111(node: ast.For) -> List[Tuple[int, int, str]]:
         return errors
     if not hasattr(node.body[0].body[0].value, "value"):
         return errors
-    if isinstance(node.next_sibling, ast.Raise):  # type: ignore
+    if not isinstance(node.next_sibling, ast.Return):  # type: ignore
         return errors
     check = to_source(node.body[0].test)
     target = to_source(node.target)

test_patch: diff --git a/tests/test_100_rules.py b/tests/test_100_rules.py
index 2164e31..fba08fe 100644
--- a/tests/test_100_rules.py
+++ b/tests/test_100_rules.py
@@ -498,3 +498,15 @@ def test_sim120():
     assert results == {
         "1:0 SIM120 Use 'class FooBar:' instead of 'class FooBar(object):'"
     }
+
+
+def test_sim111_false_positive():
+    results = _results(
+        """for a in my_list:
+  if a == 2:
+    return False
+call_method()
+return True"""
+    )
+    for el in results:
+        assert "SIM111" not in el


----

problem_statement: [Adjust Rule] Remove SIM116 due to inefficiency/code evaluation problem
## Desired change

* **Rule(s)**: SIM116
* **Adjustment**: Remove the enforcement of the rule #31 

## Explanation

This style guideline may result in better looking code, but it is inefficient and can even create errors. Each dict value is interpreted whenever the dict is evaluated.

## Example

```python
some_array = [0,1] if x else [0,1,2]
mapping = {
  "foo": "bar",
  "foo2": long_expensive_function()
  "foo3": three_arg_func(some_array[0], some_array[1], some_array[2])
}
return mapping.get("foo",42) # This will still evaluate "foo2", and if x is True, completely crash when evaluating "foo3"
```


date: 2022-03-28
patch: diff --git a/flake8_simplify/rules/ast_if.py b/flake8_simplify/rules/ast_if.py
index 6f2b74e..eacf557 100644
--- a/flake8_simplify/rules/ast_if.py
+++ b/flake8_simplify/rules/ast_if.py
@@ -275,6 +275,11 @@ def get_sim116(node: ast.If) -> List[Tuple[int, int, str]]:
             and len(child.orelse) <= 1
         ):
             return errors
+        return_call = child.body[0]
+        assert isinstance(return_call, ast.Return), "hint for mypy"
+        if isinstance(return_call.value, ast.Call):
+            # See https://github.com/MartinThoma/flake8-simplify/issues/113
+            return errors
         key: Any
         if isinstance(child.test.comparators[0], ast.Str):
             key = child.test.comparators[0].s

test_patch: diff --git a/tests/test_100_rules.py b/tests/test_100_rules.py
index 49d9c69..715263e 100644
--- a/tests/test_100_rules.py
+++ b/tests/test_100_rules.py
@@ -430,6 +430,21 @@ else:
     }
 
 
+def test_sim116_false_positive():
+    ret = _results(
+        """if a == "foo":
+    return "bar"
+elif a == "bar":
+    return baz()
+elif a == "boo":
+    return "ooh"
+else:
+    return 42"""
+    )
+    for el in ret:
+        assert "SIM116" not in el
+
+
 def test_sim117():
     ret = _results(
         """with A() as a:


----

problem_statement: [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
```
date: 2022-03-28
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