Skip to content

Commit 45878e3

Browse files
authored
Fix false positive no-member in except * handler
2 parents 3866741 + 66a4007 commit 45878e3

File tree

3 files changed

+62
-3
lines changed

3 files changed

+62
-3
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ What's New in astroid 4.0.0?
77
============================
88
Release date: TBA
99

10+
* Fix false positive no-member in except * handler.
11+
12+
Closes pylint-dev/pylint#9056
13+
1014
* Fix crash when comparing invalid dict literal
1115

1216
Closes #2522

astroid/protocols.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing import TYPE_CHECKING, Any, TypeVar
1616

1717
from astroid import bases, decorators, nodes, util
18+
from astroid.builder import extract_node
1819
from astroid.const import Context
1920
from astroid.context import InferenceContext, copy_context
2021
from astroid.exceptions import (
@@ -527,11 +528,34 @@ def excepthandler_assigned_stmts(
527528
) -> Any:
528529
from astroid import objects # pylint: disable=import-outside-toplevel
529530

530-
for assigned in node_classes.unpack_infer(self.type):
531-
if isinstance(assigned, nodes.ClassDef):
532-
assigned = objects.ExceptionInstance(assigned)
531+
def _generate_assigned():
532+
for assigned in node_classes.unpack_infer(self.type):
533+
if isinstance(assigned, nodes.ClassDef):
534+
assigned = objects.ExceptionInstance(assigned)
533535

536+
yield assigned
537+
538+
if isinstance(self.parent, node_classes.TryStar):
539+
# except * handler has assigned ExceptionGroup with caught
540+
# exceptions under exceptions attribute
541+
# pylint: disable-next=stop-iteration-return
542+
eg = next(
543+
node_classes.unpack_infer(
544+
extract_node(
545+
"""
546+
from builtins import ExceptionGroup
547+
ExceptionGroup
548+
"""
549+
)
550+
)
551+
)
552+
assigned = objects.ExceptionInstance(eg)
553+
assigned.instance_attrs["exceptions"] = [
554+
nodes.List.from_elements(_generate_assigned())
555+
]
534556
yield assigned
557+
else:
558+
yield from _generate_assigned()
535559
return {
536560
"node": self,
537561
"unknown": node,

tests/test_group_exceptions.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
AssignName,
1010
ExceptHandler,
1111
For,
12+
List,
1213
Name,
1314
Try,
1415
Uninferable,
@@ -108,3 +109,33 @@ def test_star_exceptions_infer_name() -> None:
108109
stmts = bases._infer_stmts([trystar], context)
109110
assert list(stmts) == [Uninferable]
110111
assert context.lookupname == name
112+
113+
114+
@pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher")
115+
def test_star_exceptions_infer_exceptions() -> None:
116+
code = textwrap.dedent(
117+
"""
118+
try:
119+
raise ExceptionGroup("group", [ValueError(654), TypeError(10)])
120+
except* ValueError as ve:
121+
print(e.exceptions)
122+
except* TypeError as te:
123+
print(e.exceptions)
124+
else:
125+
sys.exit(127)
126+
finally:
127+
sys.exit(0)"""
128+
)
129+
node = extract_node(code)
130+
assert isinstance(node, TryStar)
131+
inferred_ve = next(node.handlers[0].statement().name.infer())
132+
assert inferred_ve.name == "ExceptionGroup"
133+
assert isinstance(inferred_ve.getattr("exceptions")[0], List)
134+
assert (
135+
inferred_ve.getattr("exceptions")[0].elts[0].pytype() == "builtins.ValueError"
136+
)
137+
138+
inferred_te = next(node.handlers[1].statement().name.infer())
139+
assert inferred_te.name == "ExceptionGroup"
140+
assert isinstance(inferred_te.getattr("exceptions")[0], List)
141+
assert inferred_te.getattr("exceptions")[0].elts[0].pytype() == "builtins.TypeError"

0 commit comments

Comments
 (0)