Skip to content

Commit 488f3c7

Browse files
[mypyc] fix generator regression with empty tuple (#20371)
This PR fixes #20341
1 parent f2db975 commit 488f3c7

File tree

4 files changed

+49
-14
lines changed

4 files changed

+49
-14
lines changed

mypyc/irbuild/builder.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,8 +990,10 @@ def get_sequence_type_from_type(self, target_type: Type) -> RType:
990990
elif isinstance(target_type, TypeVarLikeType):
991991
return self.get_sequence_type_from_type(target_type.upper_bound)
992992
elif isinstance(target_type, TupleType):
993+
items = target_type.items
994+
assert items, "This function does not support empty tuples"
993995
# Tuple might have elements of different types.
994-
rtypes = {self.mapper.type_to_rtype(item) for item in target_type.items}
996+
rtypes = set(map(self.mapper.type_to_rtype, items))
995997
if len(rtypes) == 1:
996998
return rtypes.pop()
997999
else:

mypyc/irbuild/for_helpers.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from __future__ import annotations
99

1010
from collections.abc import Callable
11-
from typing import ClassVar
11+
from typing import ClassVar, cast
1212

1313
from mypy.nodes import (
1414
ARG_POS,
@@ -242,25 +242,45 @@ def sequence_from_generator_preallocate_helper(
242242
rtype = builder.node_type(sequence_expr)
243243
if not (is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple)):
244244
return None
245-
sequence = builder.accept(sequence_expr)
246-
length = get_expr_length_value(builder, sequence_expr, sequence, line, use_pyssize_t=True)
245+
247246
if isinstance(rtype, RTuple):
248247
# If input is RTuple, box it to tuple_rprimitive for generic iteration
249248
# TODO: this can be optimized a bit better with an unrolled ForRTuple helper
250249
proper_type = get_proper_type(builder.types[sequence_expr])
251250
assert isinstance(proper_type, TupleType), proper_type
252251

253-
get_item_ops = [
254-
(
255-
LoadLiteral(typ.value, object_rprimitive)
256-
if isinstance(typ, LiteralType)
257-
else TupleGet(sequence, i, line)
258-
)
259-
for i, typ in enumerate(get_proper_types(proper_type.items))
260-
]
252+
# the for_loop_helper_with_index crashes for empty tuples, bail out
253+
if not proper_type.items:
254+
return None
255+
256+
proper_types = get_proper_types(proper_type.items)
257+
258+
get_item_ops: list[LoadLiteral | TupleGet]
259+
if all(isinstance(typ, LiteralType) for typ in proper_types):
260+
get_item_ops = [
261+
LoadLiteral(cast(LiteralType, typ).value, object_rprimitive)
262+
for typ in proper_types
263+
]
264+
265+
else:
266+
sequence = builder.accept(sequence_expr)
267+
get_item_ops = [
268+
(
269+
LoadLiteral(typ.value, object_rprimitive)
270+
if isinstance(typ, LiteralType)
271+
else TupleGet(sequence, i, line)
272+
)
273+
for i, typ in enumerate(proper_types)
274+
]
275+
261276
items = list(map(builder.add, get_item_ops))
262277
sequence = builder.new_tuple(items, line)
263278

279+
else:
280+
sequence = builder.accept(sequence_expr)
281+
282+
length = get_expr_length_value(builder, sequence_expr, sequence, line, use_pyssize_t=True)
283+
264284
target_op = empty_op_llbuilder(length, line)
265285

266286
def set_item(item_index: Value) -> None:

mypyc/test-data/run-generators.test

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,3 +936,11 @@ def test_generator_override() -> None:
936936
assert base1_foo(Base1()) == [1]
937937
assert base1_foo(Derived1()) == [2, 3]
938938
assert derived1_foo(Derived1()) == [2, 3]
939+
940+
[case testGeneratorEmptyTuple]
941+
from collections.abc import Generator
942+
from typing import Optional, Union
943+
944+
def test_compiledGeneratorEmptyTuple() -> None:
945+
jobs: Generator[Optional[str], None, None] = (_ for _ in ())
946+
assert list(jobs) == []

mypyc/test-data/run-loops.test

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Test cases for "range" objects, "for" and "while" loops (compile and run)
22

33
[case testFor]
4-
from typing import List, Tuple
4+
from typing import Any, List, Tuple
55
def count(n: int) -> None:
66
for i in range(n):
77
print(i)
@@ -21,6 +21,10 @@ def list_iter(l: List[int]) -> None:
2121
def tuple_iter(l: Tuple[int, ...]) -> None:
2222
for i in l:
2323
print(i)
24+
def empty_tuple_iter(l: Tuple[()]) -> None:
25+
i: Any
26+
for i in l:
27+
print(i)
2428
def str_iter(l: str) -> None:
2529
for i in l:
2630
print(i)
@@ -39,7 +43,7 @@ def count_down_short() -> None:
3943
[file driver.py]
4044
from native import (
4145
count, list_iter, list_rev_iter, list_rev_iter_lol, count_between, count_down, count_double,
42-
count_down_short, tuple_iter, str_iter,
46+
count_down_short, tuple_iter, empty_tuple_iter, str_iter,
4347
)
4448
count(5)
4549
list_iter(list(reversed(range(5))))
@@ -52,6 +56,7 @@ count_down_short()
5256
print('==')
5357
list_rev_iter_lol(list(reversed(range(5))))
5458
tuple_iter((1, 2, 3))
59+
empty_tuple_iter(())
5560
str_iter("abc")
5661
[out]
5762
0

0 commit comments

Comments
 (0)