Skip to content

Fix false positive no-member in except * handler #2786

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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``.

Expand Down
30 changes: 27 additions & 3 deletions astroid/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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,
Expand Down
31 changes: 31 additions & 0 deletions tests/test_group_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
AssignName,
ExceptHandler,
For,
List,
Name,
Try,
Uninferable,
Expand Down Expand Up @@ -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"