Skip to content

Commit 2180f53

Browse files
authored
Fix sub_table ordering for nested inlined comprehensions (PEP 709) (#7480)
When an inlined comprehension's first iterator expression contains nested scopes (such as a lambda), those scopes' sub_tables appear at the current position in the parent's sub_table list. The previous code spliced the comprehension's own child sub_tables (e.g. inner inlined comprehensions) into that same position before compiling the iterator, which shifted the iterator's sub_tables to wrong indices. Move the splice after the first iterator is compiled so its sub_tables are consumed at their original positions. Fixes nested list comprehensions like: ```python [[x for _, x in g] for _, g in itertools.groupby(..., lambda x: ...)] ``` Disclosure: I used AI to develop the patch though I was heavily involved.
1 parent 3c62b56 commit 2180f53

File tree

2 files changed

+47
-5
lines changed

2 files changed

+47
-5
lines changed

crates/codegen/src/compile.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8013,23 +8013,31 @@ impl Compiler {
80138013
) -> CompileResult<()> {
80148014
// PEP 709: Consume the comprehension's sub_table.
80158015
// The symbols are already merged into parent scope by analyze_symbol_table.
8016-
// Splice the comprehension's children into the parent so nested scopes
8017-
// (e.g. inner comprehensions, lambdas) can be found by the compiler.
80188016
let current_table = self
80198017
.symbol_table_stack
80208018
.last_mut()
80218019
.expect("no current symbol table");
80228020
let comp_table = current_table.sub_tables[current_table.next_sub_table].clone();
80238021
current_table.next_sub_table += 1;
8022+
8023+
// Compile the outermost iterator first. Its expression may reference
8024+
// nested scopes (e.g. lambdas) whose sub_tables sit at the current
8025+
// position in the parent's list. Those must be consumed before we
8026+
// splice in the comprehension's own children.
8027+
self.compile_expression(&generators[0].iter)?;
8028+
8029+
// Splice the comprehension's children (e.g. nested inlined
8030+
// comprehensions) into the parent so the compiler can find them.
80248031
if !comp_table.sub_tables.is_empty() {
8032+
let current_table = self
8033+
.symbol_table_stack
8034+
.last_mut()
8035+
.expect("no current symbol table");
80258036
let insert_pos = current_table.next_sub_table;
80268037
for (i, st) in comp_table.sub_tables.iter().enumerate() {
80278038
current_table.sub_tables.insert(insert_pos + i, st.clone());
80288039
}
80298040
}
8030-
8031-
// Step 1: Compile the outermost iterator BEFORE tweaking scopes
8032-
self.compile_expression(&generators[0].iter)?;
80338041
if has_async && generators[0].is_async {
80348042
emit!(self, Instruction::GetAIter);
80358043
} else {

extra_tests/snippets/syntax_comprehension.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,37 @@
4545
def f():
4646
# Test no panic occurred.
4747
[[x := 1 for j in range(5)] for i in range(5)]
48+
49+
50+
# Nested inlined comprehensions with lambda in the first iterator expression.
51+
# The lambda's sub_table must be consumed before the inner comprehension's
52+
# sub_table is spliced in, otherwise scope ordering is wrong.
53+
def test_nested_comp_with_lambda():
54+
import itertools
55+
offsets = {0: [0], 1: [1], 3: [2]}
56+
grouped = [
57+
[x for _, x in group]
58+
for _, group in itertools.groupby(
59+
enumerate(sorted(offsets.keys())), lambda x: x[1] - x[0]
60+
)
61+
]
62+
assert grouped == [[0, 1], [3]], f"got {grouped}"
63+
64+
test_nested_comp_with_lambda()
65+
66+
67+
# Nested inlined comprehensions with throwaway `_` in both levels.
68+
def test_nested_comp_underscore():
69+
data = [(1, "a", "x"), (2, "b", "y")]
70+
result = [[v for _, v in zip(range(2), row)] for _, *row in data]
71+
assert result == [["a", "x"], ["b", "y"]], f"got {result}"
72+
73+
test_nested_comp_underscore()
74+
75+
76+
# Simple nested inlined comprehensions.
77+
def test_simple_nested_comp():
78+
result = [[j * i for j in range(3)] for i in range(3)]
79+
assert result == [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
80+
81+
test_simple_nested_comp()

0 commit comments

Comments
 (0)