From db4de99c85b45b5c4dcbc3551b8d1725778b0d91 Mon Sep 17 00:00:00 2001 From: Matus Valo Date: Tue, 22 Jul 2025 20:03:19 +0000 Subject: [PATCH 1/7] Fix false positive no-member in except * handler --- astroid/protocols.py | 16 +++++++++++++--- tests/test_group_exceptions.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index 387f9f8ac0..ed71363d4f 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -527,11 +527,21 @@ def excepthandler_assigned_stmts( ) -> Any: from astroid import objects # pylint: disable=import-outside-toplevel - for assigned in node_classes.unpack_infer(self.type): - if isinstance(assigned, nodes.ClassDef): - assigned = objects.ExceptionInstance(assigned) + def _generate_assigned(): + for assigned in node_classes.unpack_infer(self.type): + if isinstance(assigned, nodes.ClassDef): + assigned = objects.ExceptionInstance(assigned) + yield assigned + + if isinstance(self.parent, node_classes.TryStar): + # except * handler has assigned ExceptionGroup + eg = nodes.ClassDef('ExceptionGroup', self.lineno, self.col_offset, self, end_lineno=self.end_lineno, end_col_offset=self.end_col_offset) + assigned = objects.ExceptionInstance(eg) + assigned.instance_attrs['exceptions'] = [nodes.List.from_elements(_generate_assigned())] yield assigned + else: + yield from _generate_assigned() return { "node": self, "unknown": node, diff --git a/tests/test_group_exceptions.py b/tests/test_group_exceptions.py index 2ee4143fc7..b498b6a9ca 100644 --- a/tests/test_group_exceptions.py +++ b/tests/test_group_exceptions.py @@ -11,6 +11,7 @@ For, Name, Try, + List, Uninferable, bases, extract_node, @@ -93,6 +94,7 @@ def test_star_exceptions() -> None: assert final.value.args[0].value == 0 + @pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher") def test_star_exceptions_infer_name() -> None: trystar = extract_node( @@ -108,3 +110,31 @@ def test_star_exceptions_infer_name() -> None: stmts = bases._infer_stmts([trystar], context) assert list(stmts) == [Uninferable] assert context.lookupname == name + + +@pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher") +def test_star_exceptions_infer_exceptions() -> None: + code = textwrap.dedent( + """ + try: + raise ExceptionGroup("group", [ValueError(654), TypeError(10)]) + except* ValueError as ve: + print(e.exceptions) + except* TypeError as te: + print(e.exceptions) + else: + sys.exit(127) + finally: + sys.exit(0)""" + ) + node = extract_node(code) + assert isinstance(node, TryStar) + inferred_ve = next(node.handlers[0].statement().name.infer()) + assert inferred_ve.name =='ExceptionGroup' + assert isinstance(inferred_ve.getattr('exceptions')[0], List) + assert inferred_ve.getattr('exceptions')[0].elts[0].pytype() == 'builtins.ValueError' + + inferred_te = next(node.handlers[1].statement().name.infer()) + assert inferred_te.name =='ExceptionGroup' + assert isinstance(inferred_te.getattr('exceptions')[0], List) + assert inferred_te.getattr('exceptions')[0].elts[0].pytype() == 'builtins.TypeError' From 2ff716c376a1fe66f4d51e014fd3c92642d30f30 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:41:28 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- astroid/protocols.py | 13 +++++++++++-- tests/test_group_exceptions.py | 17 +++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index ed71363d4f..e3baa87e37 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -536,9 +536,18 @@ def _generate_assigned(): if isinstance(self.parent, node_classes.TryStar): # except * handler has assigned ExceptionGroup - eg = nodes.ClassDef('ExceptionGroup', self.lineno, self.col_offset, self, end_lineno=self.end_lineno, end_col_offset=self.end_col_offset) + eg = nodes.ClassDef( + "ExceptionGroup", + self.lineno, + self.col_offset, + self, + end_lineno=self.end_lineno, + end_col_offset=self.end_col_offset, + ) assigned = objects.ExceptionInstance(eg) - assigned.instance_attrs['exceptions'] = [nodes.List.from_elements(_generate_assigned())] + assigned.instance_attrs["exceptions"] = [ + nodes.List.from_elements(_generate_assigned()) + ] yield assigned else: yield from _generate_assigned() diff --git a/tests/test_group_exceptions.py b/tests/test_group_exceptions.py index b498b6a9ca..b3808f3a7e 100644 --- a/tests/test_group_exceptions.py +++ b/tests/test_group_exceptions.py @@ -9,9 +9,9 @@ AssignName, ExceptHandler, For, + List, Name, Try, - List, Uninferable, bases, extract_node, @@ -94,7 +94,6 @@ def test_star_exceptions() -> None: assert final.value.args[0].value == 0 - @pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher") def test_star_exceptions_infer_name() -> None: trystar = extract_node( @@ -130,11 +129,13 @@ def test_star_exceptions_infer_exceptions() -> None: node = extract_node(code) assert isinstance(node, TryStar) inferred_ve = next(node.handlers[0].statement().name.infer()) - assert inferred_ve.name =='ExceptionGroup' - assert isinstance(inferred_ve.getattr('exceptions')[0], List) - assert inferred_ve.getattr('exceptions')[0].elts[0].pytype() == 'builtins.ValueError' + assert inferred_ve.name == "ExceptionGroup" + assert isinstance(inferred_ve.getattr("exceptions")[0], List) + assert ( + inferred_ve.getattr("exceptions")[0].elts[0].pytype() == "builtins.ValueError" + ) inferred_te = next(node.handlers[1].statement().name.infer()) - assert inferred_te.name =='ExceptionGroup' - assert isinstance(inferred_te.getattr('exceptions')[0], List) - assert inferred_te.getattr('exceptions')[0].elts[0].pytype() == 'builtins.TypeError' + assert inferred_te.name == "ExceptionGroup" + assert isinstance(inferred_te.getattr("exceptions")[0], List) + assert inferred_te.getattr("exceptions")[0].elts[0].pytype() == "builtins.TypeError" From 863177d97a84092ec511c87ad45b196720116800 Mon Sep 17 00:00:00 2001 From: Matus Valo Date: Tue, 22 Jul 2025 20:43:23 +0000 Subject: [PATCH 3/7] Update changelog --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 9f62d499e7..78528305bb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,10 @@ What's New in astroid 4.0.0? ============================ Release date: TBA +* Fix false positive no-member in except * handler. + + Closes pylint-dev/pylint#9056 + * Removed internal functions ``infer_numpy_member``, ``name_looks_like_numpy_member``, and ``attribute_looks_like_numpy_member`` from ``astroid.brain.brain_numpy_utils``. From 69619cefd680c1c8806057abd248808edc08e131 Mon Sep 17 00:00:00 2001 From: Matus Valo Date: Fri, 25 Jul 2025 20:55:37 +0000 Subject: [PATCH 4/7] Use extract_node instead of direct class construction --- astroid/protocols.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index e3baa87e37..05cfa2d047 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -15,6 +15,7 @@ from typing import TYPE_CHECKING, Any, TypeVar from astroid import bases, decorators, nodes, util +from astroid.builder import extract_node from astroid.const import Context from astroid.context import InferenceContext, copy_context from astroid.exceptions import ( @@ -535,15 +536,13 @@ def _generate_assigned(): yield assigned if isinstance(self.parent, node_classes.TryStar): - # except * handler has assigned ExceptionGroup - eg = nodes.ClassDef( - "ExceptionGroup", - self.lineno, - self.col_offset, - self, - end_lineno=self.end_lineno, - end_col_offset=self.end_col_offset, - ) + # except * handler has assigned ExceptionGroup with caught + # exceptions under exceptions attribute + eg = next(node_classes.unpack_infer( + extract_node(''' +from builtins import ExceptionGroup +ExceptionGroup +'''))) assigned = objects.ExceptionInstance(eg) assigned.instance_attrs["exceptions"] = [ nodes.List.from_elements(_generate_assigned()) From 13ad3db804d244bec9c9752aed727d4222e6912f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 20:56:06 +0000 Subject: [PATCH 5/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- astroid/protocols.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index 05cfa2d047..7ed0fdc24d 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -538,11 +538,16 @@ def _generate_assigned(): if isinstance(self.parent, node_classes.TryStar): # except * handler has assigned ExceptionGroup with caught # exceptions under exceptions attribute - eg = next(node_classes.unpack_infer( - extract_node(''' + eg = next( + node_classes.unpack_infer( + extract_node( + """ from builtins import ExceptionGroup ExceptionGroup -'''))) +""" + ) + ) + ) assigned = objects.ExceptionInstance(eg) assigned.instance_attrs["exceptions"] = [ nodes.List.from_elements(_generate_assigned()) From c606d03660028db9a506414730d372a296b42974 Mon Sep 17 00:00:00 2001 From: Matus Valo Date: Fri, 25 Jul 2025 21:04:56 +0000 Subject: [PATCH 6/7] Make pylint happy --- astroid/protocols.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index 7ed0fdc24d..8fb042f912 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -538,7 +538,7 @@ def _generate_assigned(): if isinstance(self.parent, node_classes.TryStar): # except * handler has assigned ExceptionGroup with caught # exceptions under exceptions attribute - eg = next( + eg = list( node_classes.unpack_infer( extract_node( """ @@ -547,7 +547,7 @@ def _generate_assigned(): """ ) ) - ) + )[0] assigned = objects.ExceptionInstance(eg) assigned.instance_attrs["exceptions"] = [ nodes.List.from_elements(_generate_assigned()) From 0f362f57255ba4adf7973053337c9741c3ac2595 Mon Sep 17 00:00:00 2001 From: Matus Valo Date: Sun, 27 Jul 2025 15:45:38 +0000 Subject: [PATCH 7/7] Make both ruff and pylint happy --- astroid/protocols.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index 8fb042f912..41f9c5b2af 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -538,7 +538,8 @@ def _generate_assigned(): if isinstance(self.parent, node_classes.TryStar): # except * handler has assigned ExceptionGroup with caught # exceptions under exceptions attribute - eg = list( + # pylint: disable-next=stop-iteration-return + eg = next( node_classes.unpack_infer( extract_node( """ @@ -547,7 +548,7 @@ def _generate_assigned(): """ ) ) - )[0] + ) assigned = objects.ExceptionInstance(eg) assigned.instance_attrs["exceptions"] = [ nodes.List.from_elements(_generate_assigned())