diff --git a/ChangeLog b/ChangeLog index 9f62d499e..78528305b 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``. diff --git a/astroid/protocols.py b/astroid/protocols.py index 387f9f8ac..41f9c5b2a 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 ( @@ -527,11 +528,34 @@ 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 with caught + # exceptions under exceptions attribute + # pylint: disable-next=stop-iteration-return + 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()) + ] 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 2ee4143fc..b3808f3a7 100644 --- a/tests/test_group_exceptions.py +++ b/tests/test_group_exceptions.py @@ -9,6 +9,7 @@ AssignName, ExceptHandler, For, + List, Name, Try, Uninferable, @@ -108,3 +109,33 @@ 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"