Skip to content
Merged
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
30 changes: 12 additions & 18 deletions mypyc/irbuild/specialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
string_writer_range_check_op,
)
from mypyc.primitives.librt_vecs_ops import isinstance_vec
from mypyc.primitives.list_ops import isinstance_list, new_list_set_item_op
from mypyc.primitives.list_ops import isinstance_list
from mypyc.primitives.misc_ops import (
isinstance_bool,
isinstance_complex,
Expand Down Expand Up @@ -208,6 +208,14 @@ def _apply_specialization(
# TODO: Allow special cases to have default args or named args. Currently they don't since
# they check that everything in arg_kinds is ARG_POS.

# Specializers translate generator expression arguments inline, which doesn't support
# unpacking (Python 3.15+). Use the generic path, where transform_generator_expr
# reports an error.
if any(
isinstance(arg, GeneratorExpr) and isinstance(arg.left_expr, StarExpr) for arg in expr.args
):
return None

# If there is a specializer for this function, try calling it.
# Return the first successful one.
if name and (name, typ) in specializers:
Expand Down Expand Up @@ -431,31 +439,17 @@ def dict_methods_fast_path(builder: IRBuilder, expr: CallExpr, callee: RefExpr)
def translate_list_from_generator_call(
builder: IRBuilder, expr: CallExpr, callee: RefExpr
) -> Value | None:
"""Special case for simplest list comprehension.
"""Special case for list(<generator expression>), which acts like a list comprehension.

For example:
list(f(x) for x in some_list/some_tuple/some_str)
'translate_list_comprehension()' would take care of other cases
if this fails.
list(f(x) for x in some_iterable)
"""
if (
len(expr.args) == 1
and expr.arg_kinds[0] == ARG_POS
and isinstance(expr.args[0], GeneratorExpr)
and can_preallocate_from_generator(builder, expr.args[0])
):
gen = expr.args[0]

def set_item(x: Value, y: Value, z: Value, line: int) -> None:
builder.call_c(new_list_set_item_op, [x, y, z], line)

with comprehension_scope(builder, gen):
return sequence_from_generator_preallocate_helper(
builder,
gen,
empty_op_llbuilder=builder.builder.new_list_op_with_length,
set_item_op=set_item,
)
return translate_generator_to_list(builder, expr.args[0])
return None


Expand Down
40 changes: 40 additions & 0 deletions mypyc/test-data/irbuild-lists.test
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,46 @@ L8:
b = r11
return 1

[case testListFromGeneratorExpr]
def f(n: int) -> list[int]:
return list(x + 1 for x in range(n) if x != 2)
[out]
def f(n):
n :: int
r0 :: list
r1 :: int
r2 :: bit
x :: int
r3 :: bit
r4 :: int
r5 :: object
r6 :: i32
r7 :: bit
r8 :: int
L0:
r0 = PyList_New(0)
r1 = 0
L1:
r2 = int_lt r1, n
if r2 goto L2 else goto L6 :: bool
L2:
x = r1
r3 = int_ne x, 4
if r3 goto L4 else goto L3 :: bool
L3:
goto L5
L4:
r4 = CPyTagged_Add(x, 2)
r5 = box(int, r4)
r6 = PyList_Append(r0, r5)
r7 = r6 >= 0 :: signed
L5:
r8 = CPyTagged_Add(r1, 2)
r1 = r8
goto L1
L6:
return r0

[case testGeneratorNext_withgil]
from typing import List, Optional

Expand Down
11 changes: 11 additions & 0 deletions mypyc/test-data/irbuild-python315.test
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
[case testPEP798FailGracefully_python3_15]
from typing import Iterator

x = [[1]]
y = [*l for l in x] # E: Unpacking in comprehensions is not supported yet

dx = [{1: 1}]
dy = {**d for d in dx} # E: Unpacking in comprehensions is not supported yet

def f(it: Iterator[list[int]], x: list[list[int]], xs: list[list[str]]) -> None:
list(*l for l in it) # E: Unpacking in comprehensions is not supported yet
list(*l for l in x) # E: Unpacking in comprehensions is not supported yet
tuple(*l for l in x) # E: Unpacking in comprehensions is not supported yet
sorted(*l for l in x) # E: Unpacking in comprehensions is not supported yet
any(*l for l in x) # E: Unpacking in comprehensions is not supported yet
",".join(*l for l in xs) # E: Unpacking in comprehensions is not supported yet
sorted((*l for l in x), key=abs) # E: Unpacking in comprehensions is not supported yet
17 changes: 16 additions & 1 deletion mypyc/test-data/run-lists.test
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ def test_in_operator_various_cases() -> None:
assert list_in_mixed(type)

[case testListBuiltFromGenerator]
from typing import Final
from typing import Final, Iterator
abc: Final = "abc"
def test_from_gen() -> None:
source_a = ["a", "b", "c"]
Expand All @@ -583,6 +583,21 @@ def test_known_length() -> None:
built = [str(x) for x in [*abc, *"def", *b"ghi", ("j", "k"), *("l", "m", "n")]]
assert built == ['a', 'b', 'c', 'd', 'e', 'f', '103', '104', '105', "('j', 'k')", 'l', 'm', 'n']

def gen(n: int) -> Iterator[int]:
for i in range(n):
yield i

def test_from_gen_unknown_length() -> None:
n = 4
assert list(x + 1 for x in range(n)) == [1, 2, 3, 4]
assert list(x for x in range(n) if x % 2) == [1, 3]
assert list(x * y for x in range(3) for y in range(2)) == [0, 0, 0, 1, 0, 2]
assert list(k for k in {"a": 1, "b": 2}) == ["a", "b"]
assert list(x for x in gen(3)) == [0, 1, 2]
assert list(x for x in iter([5, 6])) == [5, 6]
empty: list[int] = []
assert list(x for x in empty) == []

[case testNext]
from typing import List

Expand Down
Loading