Repository: pylint-dev/pylint (Python)

Issue:
`used-before-assignment` false positive with list comprehension and try/except
### Bug description

Somewhat similarly to #5965,

```python
def github_upload(filename, all_assets):
    assets = [asset for asset in all_assets if asset.name == filename]

    try:
        pass
    except ValueError:
        asset = assets[0]
        asset.delete()
```

leads to an `used-before-assignment` false-positive for the second line, yet `asset` is always defined there.

I bisected this to bd55b27d41542e3ca1f031f986b6151f6cac457f, part of #5402 ("Fix #4761: Emit used-before-assignment where single assignment only made in except blocks")

### Configuration

_No response_

### Command used

```shell
pylint -E repro1.py
```


### Pylint output

```shell
************* Module repro1
repro1.py:2:47: E0601: Using variable 'asset' before assignment (used-before-assignment)
```


### Expected behavior

No error.

### Pylint version

```shell
pylint 2.13.2
astroid 2.11.2
Python 3.8.12 (default, Mar  3 2022, 18:19:28) 
[GCC 11.2.0]
```

(environment in which I originally encountered the bug - output above is from a git install of both astroid and pylint)

Also happens with latest git main (astroid b18c7828, pylint 0bc45e90). Does *not* happen on pylint 2.12.2 and astroid 2.9.3 which I used previously.

### OS / Environment

Archlinux

### Additional dependencies

_No response_


Discussion:
Thanks for the well-written report! Extremely similar to #5586 and #5817, but still missed it before the release.
in the spirit of possibly being helpful:

somewhat similar list comprehension statement, but not in a try-except block

```python
if isinstance(d, dict):
    dict_keys =  [c for c in d if isinstance(d[c], dict)]
```
E:191,59: Using variable 'c' before assignment (used-before-assignment)

pylint 2.13.3,  pytest-pylint 0.18.0, python 3.8.13, pytest-7.1.1
I can't reproduce with that example:
```python
$ cat a.py
if isinstance(d, dict):
    dict_keys =  [c for c in d if isinstance(d[c], dict)]


$ python3 -m pylint a.py
************* Module a
a.py:1:14: E0602: Undefined variable 'd' (undefined-variable)
a.py:2:29: E0602: Undefined variable 'd' (undefined-variable)
a.py:2:45: E0602: Undefined variable 'd' (undefined-variable)
```
my apologies @jacobtylerwalls - my example wasn't meant to be complete, just the minimal snippet showing the statement. 
Extremely interesting:  the error was reported in a CI pipeline using Python 3.8.13, the other versions I reported earlier.
Not using pytest-pylint, I get the following.
Not being a Python expert/guru, I'm concerned over the difference between the full codebase scan via pytest-pylint and pylint standalone on this snippet. I need to do some more digging.  

My immediate take-away:  any pytest-pylint reported errors, rerun with standalone pylint to see if any differences.  
Interesting...

/tmp/a.py:
```python
d = { 'a': {}, 'b':1 }
dict_keys = []
if isinstance(d, dict):
    dict_keys =  [c for c in d if isinstance(d[c], dict)]
print("dict_keys: " + str(dict_keys))
```
```base
python3.8 /tmp/a.py
```
dict_keys: ['a']

```bash
python3.7 -m pylint /tmp/a.py
```
************* Module a
/tmp/a.py:1:0: C0114: Missing module docstring (missing-module-docstring)
/tmp/a.py:7:0: C0206: Consider iterating with .items() (consider-using-dict-items)

With python3.8:

```bash
python3.8 -m pylint --version
```
> pylint 2.13.3
> astroid 2.11.2
> Python 3.8.13 (default, Mar 29 2022, 19:52:48) 
> [GCC 8.3.0]
```bash
root@abb086d6fbf6:/usr/src/app# python3.8 -m pylint /tmp/a.py
```

> ************* Module a
> /tmp/a.py:1:0: C0114: Missing module docstring (missing-module-docstring)
> /tmp/a.py:4:0: C0206: Consider iterating with .items() (consider-using-dict-items)
> 


Thanks, I misunderstood -- thought you were saying you found a case reproducible without a try/except block. I think it's worth adding this case to the tests.
I can still reproduce this in 2.13.4:

```python
# test.py
def select_dict(data, keys):
    try:
        d = {key: data[key] for key in keys}
    except KeyError as e:
        key, *_ = e.args
        raise Exception(f"{key} not found")

    return d
```

```console
$ pip install -U pylint==2.13.4 -q && pylint test.py
************* Module test
test.py:3:23: E0601: Using variable 'key' before assignment (used-before-assignment)

------------------------------------------------------------------
Your code has been rated at 2.86/10 (previous run: 2.86/10, +0.00)
```


Thanks for the report: this one does not involve an if test in the comprehension, so I'm going to open a new issue for it. I have a patch ready.



Patch:
diff --git a/ChangeLog b/ChangeLog
index 817d82d220..94f531f79f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -41,6 +41,12 @@ What's New in Pylint 2.13.4?
 ============================
 Release date: TBA
 
+* Fix false positive regression in 2.13.0 for ``used-before-assignment`` for
+  homonyms between variable assignments in try/except blocks and variables in
+  a comprehension's filter.
+
+  Closes #6035
+
 * Include ``testing_pylintrc`` in source and wheel distributions.
 
   Closes #6028
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 58d2895baa..ef6a5369ff 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -629,13 +629,12 @@ def get_next_to_consume(self, node: nodes.Name) -> Optional[List[nodes.NodeNG]]:
         ):
             return found_nodes
 
+        # And is not part of a test in a filtered comprehension
+        if VariablesChecker._has_homonym_in_comprehension_test(node):
+            return found_nodes
+
         # Filter out assignments in ExceptHandlers that node is not contained in
-        # unless this is a test in a filtered comprehension
-        # Example: [e for e in range(3) if e] <--- followed by except e:
-        if found_nodes and (
-            not isinstance(parent_node, nodes.Comprehension)
-            or node not in parent_node.ifs
-        ):
+        if found_nodes:
             found_nodes = [
                 n
                 for n in found_nodes
@@ -1506,10 +1505,7 @@ def _check_consumer(
                 # (like "if x" in "[x for x in expr() if x]")
                 # https://github.com/PyCQA/pylint/issues/5586
                 and not (
-                    (
-                        isinstance(node.parent.parent, nodes.Comprehension)
-                        and node.parent in node.parent.parent.ifs
-                    )
+                    self._has_homonym_in_comprehension_test(node)
                     # Or homonyms against values to keyword arguments
                     # (like "var" in "[func(arg=var) for var in expr()]")
                     or (
@@ -2482,6 +2478,30 @@ def _has_homonym_in_upper_function_scope(
             for _consumer in self._to_consume[index - 1 :: -1]
         )
 
+    @staticmethod
+    def _has_homonym_in_comprehension_test(node: nodes.Name) -> bool:
+        """Return True if `node`'s frame contains a comprehension employing an
+        identical name in a test.
+
+        The name in the test could appear at varying depths:
+
+        Examples:
+            [x for x in range(3) if name]
+            [x for x in range(3) if name.num == 1]
+            [x for x in range(3)] if call(name.num)]
+        """
+        closest_comprehension = utils.get_node_first_ancestor_of_type(
+            node, nodes.Comprehension
+        )
+        return (
+            closest_comprehension is not None
+            and node.frame(future=True).parent_of(closest_comprehension)
+            and any(
+                test is node or test.parent_of(node)
+                for test in closest_comprehension.ifs
+            )
+        )
+
     def _store_type_annotation_node(self, type_annotation):
         """Given a type annotation, store all the name nodes it refers to."""
         if isinstance(type_annotation, nodes.Name):


Test patch:
diff --git a/tests/functional/u/used/used_before_assignment_filtered_comprehension.py b/tests/functional/u/used/used_before_assignment_filtered_comprehension.py
index f638b0be9b..f0c569aad3 100644
--- a/tests/functional/u/used/used_before_assignment_filtered_comprehension.py
+++ b/tests/functional/u/used/used_before_assignment_filtered_comprehension.py
@@ -7,3 +7,45 @@ def func():
     except ZeroDivisionError:
         value = 1
         print(value)
+
+
+def func2():
+    """Same, but with attribute access."""
+    try:
+        print(value for value in range(1 / 0) if isinstance(value.num, int))
+    except ZeroDivisionError:
+        value = 1
+        print(value)
+
+
+def func3():
+    """Same, but with no call."""
+    try:
+        print(value for value in range(1 / 0) if value)
+    except ZeroDivisionError:
+        value = 1
+        print(value)
+
+
+def func4():
+    """https://github.com/PyCQA/pylint/issues/6035"""
+    assets = [asset for asset in range(3) if asset.name == "filename"]
+
+    try:
+        raise ValueError
+    except ValueError:
+        asset = assets[0]
+        print(asset)
+
+
+def func5():
+    """Similar, but with subscript notation"""
+    results = {}
+    # pylint: disable-next=consider-using-dict-items
+    filtered =  [k for k in results if isinstance(results[k], dict)]
+
+    try:
+        1 / 0
+    except ZeroDivisionError:
+        k = None
+        print(k, filtered)