Skip to content

Commit 1e7aa9a

Browse files
committed
Exclude async/await comprehensions from PEP 709 inlining in symboltable
Async comprehensions and comprehensions with await in the element expression need their own coroutine scope and cannot be inlined. The symboltable builder was not checking these conditions, causing incorrect symbol scope resolution when an async comprehension was nested inside an inlined comprehension (e.g. [[x async for x in g] for j in items]).
1 parent e3932b4 commit 1e7aa9a

File tree

1 file changed

+25
-1
lines changed

1 file changed

+25
-1
lines changed

crates/codegen/src/symboltable.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,26 @@ fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet<String
299299
}
300300
}
301301

302+
/// Check if an expression contains an `await` node (shallow, not into nested scopes).
303+
fn expr_contains_await(expr: &ast::Expr) -> bool {
304+
use ast::visitor::Visitor;
305+
struct AwaitFinder(bool);
306+
impl ast::visitor::Visitor<'_> for AwaitFinder {
307+
fn visit_expr(&mut self, expr: &ast::Expr) {
308+
if !self.0 {
309+
if matches!(expr, ast::Expr::Await(_)) {
310+
self.0 = true;
311+
} else {
312+
ast::visitor::walk_expr(self, expr);
313+
}
314+
}
315+
}
316+
}
317+
let mut finder = AwaitFinder(false);
318+
finder.visit_expr(expr);
319+
finder.0
320+
}
321+
302322
/// PEP 709: Merge symbols from an inlined comprehension into the parent scope.
303323
/// Matches symtable.c inline_comprehension().
304324
fn inline_comprehension(
@@ -2102,9 +2122,13 @@ impl SymbolTableBuilder {
21022122
// but only inside function-like scopes (fastlocals).
21032123
// Module/class scope uses STORE_NAME which is incompatible
21042124
// with LOAD_FAST_AND_CLEAR / STORE_FAST save/restore.
2125+
// Async comprehensions cannot be inlined because they need
2126+
// their own coroutine scope.
21052127
// Note: tables.last() is the comprehension scope we just pushed,
21062128
// so we check the second-to-last for the parent scope.
2107-
if !is_generator {
2129+
let element_has_await =
2130+
expr_contains_await(elt1) || elt2.is_some_and(expr_contains_await);
2131+
if !is_generator && !has_async_gen && !element_has_await {
21082132
let parent = self.tables.iter().rev().nth(1);
21092133
let parent_is_func = parent.is_some_and(|t| {
21102134
matches!(

0 commit comments

Comments
 (0)