From 1dade0fd983531bfe300f6e7c4e42d4290e148cc Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 15 Oct 2026 09:36:57 +0100 Subject: [PATCH 1/5] [mypyc] Don't copy items twice in list() The special case for list() only handled sources that allow preallocating the result (such as lists and tuples). For other sources, the generator expression was first compiled into a list, an iterator was created for it, and list() then copied the items into a new list. Compile it as a list comprehension in all cases instead. --- mypyc/irbuild/specialize.py | 22 +++------------- mypyc/test-data/irbuild-lists.test | 40 ++++++++++++++++++++++++++++++ mypyc/test-data/run-lists.test | 17 ++++++++++++- 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 520582f0a996..87db534a4352 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, @@ -431,31 +431,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/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 From 3ca21ef835afe9227673774326e599edc917d520 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 20 Oct 2026 12:03:05 +0100 Subject: [PATCH 2/5] [mypyc] Report error for unpacking in generator expression call arguments Specializers that compile a generator expression argument inline (such as list(), sorted() and str.join()) bypassed the check for unpacking in comprehensions (Python 3.15+), causing an internal compiler error. Skip specializers for such calls, so that the generic path reports the error. --- mypyc/irbuild/specialize.py | 7 +++++++ mypyc/test-data/irbuild-python315.test | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 87db534a4352..3c375dc10969 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -208,6 +208,13 @@ 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, which 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: diff --git a/mypyc/test-data/irbuild-python315.test b/mypyc/test-data/irbuild-python315.test index 297484aa2952..ae1e2f782552 100644 --- a/mypyc/test-data/irbuild-python315.test +++ b/mypyc/test-data/irbuild-python315.test @@ -4,3 +4,14 @@ 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 + +[case testPEP798GeneratorArgFailGracefully_python3_15] +from typing import Iterator + +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 From 575972fb479c55f0f84afc770076fbb8189d088c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 20 Oct 2026 12:03:12 +0100 Subject: [PATCH 3/5] [mypyc] Test unpacking in generator argument with a keyword argument --- mypyc/test-data/irbuild-python315.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/test-data/irbuild-python315.test b/mypyc/test-data/irbuild-python315.test index ae1e2f782552..6c466abd83fb 100644 --- a/mypyc/test-data/irbuild-python315.test +++ b/mypyc/test-data/irbuild-python315.test @@ -15,3 +15,4 @@ def f(it: Iterator[list[int]], x: list[list[int]], xs: list[list[str]]) -> None: 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 From 11cc06268f85a45f351bb64d38a75957f3f6fced Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 20 Oct 2026 12:03:27 +0100 Subject: [PATCH 4/5] [mypyc] Merge Python 3.15 unpacking test cases --- mypyc/test-data/irbuild-python315.test | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mypyc/test-data/irbuild-python315.test b/mypyc/test-data/irbuild-python315.test index 6c466abd83fb..cff60abcbe42 100644 --- a/mypyc/test-data/irbuild-python315.test +++ b/mypyc/test-data/irbuild-python315.test @@ -1,13 +1,12 @@ [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 -[case testPEP798GeneratorArgFailGracefully_python3_15] -from typing import Iterator - 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 From 1156e6b9b6cb4d02ddd4f506ebbe9ad220c19e47 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 20 Oct 2026 12:03:37 +0100 Subject: [PATCH 5/5] [mypyc] Clarify where unpacking in generator arguments is reported --- mypyc/irbuild/specialize.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 3c375dc10969..6fd60323852d 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -209,7 +209,8 @@ def _apply_specialization( # 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, which reports an error. + # 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 ):