diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 520582f0a996..6fd60323852d 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -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, @@ -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: @@ -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(), 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 diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index ed89db8f702f..3e23c7e6f8a0 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -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 diff --git a/mypyc/test-data/irbuild-python315.test b/mypyc/test-data/irbuild-python315.test index 297484aa2952..cff60abcbe42 100644 --- a/mypyc/test-data/irbuild-python315.test +++ b/mypyc/test-data/irbuild-python315.test @@ -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 diff --git a/mypyc/test-data/run-lists.test b/mypyc/test-data/run-lists.test index 8b9934beab39..1f9be54108f0 100644 --- a/mypyc/test-data/run-lists.test +++ b/mypyc/test-data/run-lists.test @@ -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"] @@ -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