Skip to content

Commit d0ff3bd

Browse files
Fix crash when a tuple is used as a ContextManager (#4646)
1 parent a41dc89 commit d0ff3bd

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

Diff for: CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Handle `# fmt: skip` followed by a comment at the end of file (#4635)
1616
- Fix crash when a tuple appears in the `as` clause of a `with` statement
1717
(#4634)
18+
- Fix crash when tuple is used as a context manager inside a `with` statement (#4646)
1819

1920
### Preview style
2021

Diff for: src/black/linegen.py

+6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
ensure_visible,
4141
fstring_to_string,
4242
get_annotation_type,
43+
has_sibling_with_type,
4344
is_arith_like,
4445
is_async_stmt_or_funcdef,
4546
is_atom_with_invisible_parens,
@@ -1628,6 +1629,11 @@ def maybe_make_parens_invisible_in_atom(
16281629
or is_empty_tuple(node)
16291630
or is_one_tuple(node)
16301631
or (is_tuple(node) and parent.type == syms.asexpr_test)
1632+
or (
1633+
is_tuple(node)
1634+
and parent.type == syms.with_stmt
1635+
and has_sibling_with_type(node, token.COMMA)
1636+
)
16311637
or (is_yield(node) and parent.type != syms.expr_stmt)
16321638
or (
16331639
# This condition tries to prevent removing non-optional brackets

Diff for: src/black/nodes.py

+18
Original file line numberDiff line numberDiff line change
@@ -1058,3 +1058,21 @@ def furthest_ancestor_with_last_leaf(leaf: Leaf) -> LN:
10581058
while node.parent and node.parent.children and node is node.parent.children[-1]:
10591059
node = node.parent
10601060
return node
1061+
1062+
1063+
def has_sibling_with_type(node: LN, type: int) -> bool:
1064+
# Check previous siblings
1065+
sibling = node.prev_sibling
1066+
while sibling is not None:
1067+
if sibling.type == type:
1068+
return True
1069+
sibling = sibling.prev_sibling
1070+
1071+
# Check next siblings
1072+
sibling = node.next_sibling
1073+
while sibling is not None:
1074+
if sibling.type == type:
1075+
return True
1076+
sibling = sibling.next_sibling
1077+
1078+
return False

Diff for: tests/data/cases/context_managers_39.py

+40
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,26 @@ async def func():
8989
with (x, y) as z:
9090
pass
9191

92+
93+
# don't remove the brackets here, it changes the meaning of the code.
94+
# even though the code will always trigger a runtime error
95+
with (name_5, name_4), name_5:
96+
pass
97+
98+
99+
def test_tuple_as_contextmanager():
100+
from contextlib import nullcontext
101+
102+
try:
103+
with (nullcontext(),nullcontext()),nullcontext():
104+
pass
105+
except TypeError:
106+
# test passed
107+
pass
108+
else:
109+
# this should be a type error
110+
assert False
111+
92112
# output
93113

94114

@@ -182,3 +202,23 @@ async def func():
182202
# don't remove the brackets here, it changes the meaning of the code.
183203
with (x, y) as z:
184204
pass
205+
206+
207+
# don't remove the brackets here, it changes the meaning of the code.
208+
# even though the code will always trigger a runtime error
209+
with (name_5, name_4), name_5:
210+
pass
211+
212+
213+
def test_tuple_as_contextmanager():
214+
from contextlib import nullcontext
215+
216+
try:
217+
with (nullcontext(), nullcontext()), nullcontext():
218+
pass
219+
except TypeError:
220+
# test passed
221+
pass
222+
else:
223+
# this should be a type error
224+
assert False

0 commit comments

Comments
 (0)