Repository: MartinThoma/flake8-simplify (python)

Issue:
yield from is not allowed in async function
## Desired change
Do not warning if a SIM104 is encountered inside an async function or method.

* **Rule(s)**: SIM104
* **Adjustment**: In which way should it/they be adjusted?
Do not warning if a SIM104 is encountered inside an async function or method.

## Explanation

Why is the adjustment necessary / better?

The suggested fix to replace a for yield loop with a `yield from` statement is [invalid](https://peps.python.org/pep-0525/#asynchronous-yield-from) inside an async function.

## Example

This is an example where the mentioned rule(s) would currently be suboptimal:

```python
async fib(limit=10):
    if limit == 1:
        yield 0
    if limit == 2:
        yield 0
        yield 1
    i, j = 0, 1
    for _ in range(2, limit):
        n = i + j
        yield n
        i, j = j, n
```


Pull request description:
SIM104: Remove false-positives in case the loop is not a direct child of an async function
Not all for loops within a function body is a direct child of the function body

Closes #146 

Patch:
diff --git a/README.md b/README.md
index f73d258..4aeac9c 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ Python-specific rules:
 * `SIM126`: Reserved for [SIM906](#sim906) once it's stable
 * `SIM127`: Reserved for [SIM907](#sim907) once it's stable
 
-Simplifying Comparations:
+Simplifying Comparisons:
 
 * `SIM201`: Use 'a != b' instead of 'not a == b' ([example](#SIM201))
 * `SIM202`: Use 'a == b' instead of 'not a != b' ([example](#SIM202))
diff --git a/flake8_simplify/rules/ast_for.py b/flake8_simplify/rules/ast_for.py
index 8d4d984..e19f64c 100644
--- a/flake8_simplify/rules/ast_for.py
+++ b/flake8_simplify/rules/ast_for.py
@@ -48,7 +48,15 @@ def get_sim104(node: ast.For) -> List[Tuple[int, int, str]]:
         or node.orelse != []
     ):
         return errors
-    if isinstance(node.parent, ast.AsyncFunctionDef):  # type: ignore
+
+    parent = getattr(node, "parent", None)
+    while (parent
+           and hasattr(parent, "parent")
+           and parent.parent is not parent
+           and not isinstance(parent, ast.AsyncFunctionDef)):
+        parent = getattr(parent, "parent", None)
+
+    if isinstance(parent, ast.AsyncFunctionDef):  # type: ignore
         return errors
     iterable = to_source(node.iter)
     errors.append(


Test patch:
diff --git a/tests/test_100_rules.py b/tests/test_100_rules.py
index fba08fe..955d71d 100644
--- a/tests/test_100_rules.py
+++ b/tests/test_100_rules.py
@@ -99,6 +99,17 @@ def test_s104_async_generator_false_positive():
         assert "SIM104" not in el
 
 
+def test_s104_async_generator_false_positive_2():
+    ret = _results(
+        """async def items():
+    with open('/etc/passwd') as f:
+        for line in f:
+            yield line"""
+    )
+    for el in ret:
+        assert "SIM104" not in el
+
+
 def test_sim105():
     ret = _results(
         """try: