Skip to content

Commit 505928d

Browse files
work in progress trystar refs #1516
1 parent 55afe5d commit 505928d

File tree

4 files changed

+158
-0
lines changed

4 files changed

+158
-0
lines changed

astroid/nodes/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
Subscript,
8585
TryExcept,
8686
TryFinally,
87+
TryStar,
8788
Tuple,
8889
UnaryOp,
8990
Unknown,

astroid/nodes/node_classes.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4210,6 +4210,107 @@ def get_children(self):
42104210
yield from self.finalbody
42114211

42124212

4213+
class TryStar(_base_nodes.MultiLineWithElseBlockNode, _base_nodes.Statement):
4214+
"""Class representing an :class:`ast.TryStar` node."""
4215+
4216+
_astroid_fields = ("body", "handlers", "orelse", "finalbody")
4217+
_multi_line_block_fields = ("body", "handlers", "orelse", "finalbody")
4218+
4219+
def __init__(
4220+
self,
4221+
*,
4222+
lineno: int | None = None,
4223+
col_offset: int | None = None,
4224+
end_lineno: int | None = None,
4225+
end_col_offset: int | None = None,
4226+
parent: NodeNG | None = None,
4227+
) -> None:
4228+
"""
4229+
:param lineno: The line that this node appears on in the source code.
4230+
:param col_offset: The column that this node appears on in the
4231+
source code.
4232+
:param parent: The parent node in the syntax tree.
4233+
:param end_lineno: The last line this node appears on in the source code.
4234+
:param end_col_offset: The end column this node appears on in the
4235+
source code. Note: This is after the last symbol.
4236+
"""
4237+
self.body: list[NodeNG] = []
4238+
"""The contents of the block to catch exceptions from."""
4239+
4240+
self.handlers: list[ExceptHandler] = []
4241+
"""The exception handlers."""
4242+
4243+
self.orelse: list[NodeNG] = []
4244+
"""The contents of the ``else`` block."""
4245+
4246+
self.finalbody: list[NodeNG] = []
4247+
"""The contents of the ``finally`` block."""
4248+
4249+
super().__init__(
4250+
lineno=lineno,
4251+
col_offset=col_offset,
4252+
end_lineno=end_lineno,
4253+
end_col_offset=end_col_offset,
4254+
parent=parent,
4255+
)
4256+
4257+
def postinit(
4258+
self,
4259+
*,
4260+
body: list[NodeNG] | None = None,
4261+
handlers: list[ExceptHandler] | None = None,
4262+
orelse: list[NodeNG] | None = None,
4263+
finalbody: list[NodeNG] | None = None,
4264+
) -> None:
4265+
"""Do some setup after initialisation.
4266+
:param body: The contents of the block to catch exceptions from.
4267+
:param handlers: The exception handlers.
4268+
:param orelse: The contents of the ``else`` block.
4269+
:param finalbody: The contents of the ``finally`` block.
4270+
"""
4271+
if body:
4272+
self.body = body
4273+
if handlers:
4274+
self.handlers = handlers
4275+
if orelse:
4276+
self.orelse = orelse
4277+
if finalbody:
4278+
self.finalbody = finalbody
4279+
4280+
def _infer_name(self, frame, name):
4281+
return name
4282+
4283+
def block_range(self, lineno: int) -> tuple[int, int]:
4284+
"""Get a range from a given line number to where this node ends."""
4285+
if lineno == self.fromlineno:
4286+
return lineno, lineno
4287+
if self.body and self.body[0].fromlineno <= lineno <= self.body[-1].tolineno:
4288+
# Inside try body - return from lineno till end of try body
4289+
return lineno, self.body[-1].tolineno
4290+
for exhandler in self.handlers:
4291+
if exhandler.type and lineno == exhandler.type.fromlineno:
4292+
return lineno, lineno
4293+
if exhandler.body[0].fromlineno <= lineno <= exhandler.body[-1].tolineno:
4294+
return lineno, exhandler.body[-1].tolineno
4295+
if self.orelse:
4296+
if self.orelse[0].fromlineno - 1 == lineno:
4297+
return lineno, lineno
4298+
if self.orelse[0].fromlineno <= lineno <= self.orelse[-1].tolineno:
4299+
return lineno, self.orelse[-1].tolineno
4300+
if self.finalbody:
4301+
if self.finalbody[0].fromlineno - 1 == lineno:
4302+
return lineno, lineno
4303+
if self.finalbody[0].fromlineno <= lineno <= self.finalbody[-1].tolineno:
4304+
return lineno, self.finalbody[-1].tolineno
4305+
return lineno, self.tolineno
4306+
4307+
def get_children(self):
4308+
yield from self.body
4309+
yield from self.handlers
4310+
yield from self.orelse
4311+
yield from self.finalbody
4312+
4313+
42134314
class Tuple(BaseContainer):
42144315
"""Class representing an :class:`ast.Tuple` node.
42154316

astroid/rebuilder.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,6 +1822,15 @@ def visit_try(
18221822
return self.visit_tryexcept(node, parent)
18231823
return None
18241824

1825+
def visit_trystar(self, node: ast.TryStar, parent: NodeNG) -> nodes.TryStar:
1826+
return nodes.TryStar(
1827+
lineno=node.lineno,
1828+
col_offset=node.col_offset,
1829+
end_lineno=getattr(node, "end_lineno", None),
1830+
end_col_offset=getattr(node, "end_col_offset", None),
1831+
parent=parent,
1832+
)
1833+
18251834
def visit_tuple(self, node: ast.Tuple, parent: NodeNG) -> nodes.Tuple:
18261835
"""Visit a Tuple node by returning a fresh instance of it."""
18271836
context = self._get_context(node)

tests/test_group_exceptions.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2+
# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
4+
import pytest
5+
6+
from astroid import ExceptHandler, TryExcept, extract_node
7+
from astroid.const import PY311_PLUS
8+
from astroid.nodes import TryStar
9+
10+
11+
@pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher")
12+
def test_group_exceptions() -> None:
13+
node = extract_node(
14+
"""
15+
try:
16+
raise ExceptionGroup("group", [ValueError(654)])
17+
except ExceptionGroup as eg:
18+
for err in eg.exceptions:
19+
if isinstance(err, ValueError):
20+
print("Handling ValueError")
21+
elif isinstance(err, TypeError):
22+
print("Handling TypeError")
23+
"""
24+
)
25+
assert isinstance(node, TryExcept)
26+
handler = node.handlers[0]
27+
assert isinstance(handler, ExceptHandler)
28+
assert handler.type.name == "ExceptionGroup"
29+
30+
31+
@pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher")
32+
def test_star_exceptions() -> None:
33+
node = extract_node(
34+
"""
35+
try:
36+
raise ExceptionGroup("group", [ValueError(654)])
37+
except* ValueError:
38+
print("Handling ValueError")
39+
except* TypeError:
40+
print("Handling TypeError")
41+
"""
42+
)
43+
assert isinstance(node, TryStar)
44+
assert node.handlers
45+
handler = node.handlers[0]
46+
assert isinstance(handler, ExceptHandler)
47+
assert handler.type.name == "ExceptionGroup"

0 commit comments

Comments
 (0)