Skip to content

Commit 081f803

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 1493a9b commit 081f803

File tree

1 file changed

+24
-1
lines changed

1 file changed

+24
-1
lines changed

crates/codegen/src/symboltable.rs

Lines changed: 24 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,12 @@ 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 = expr_contains_await(elt1) || elt2.is_some_and(expr_contains_await);
2130+
if !is_generator && !has_async_gen && !element_has_await {
21082131
let parent = self.tables.iter().rev().nth(1);
21092132
let parent_is_func = parent.is_some_and(|t| {
21102133
matches!(

0 commit comments

Comments
 (0)