Skip to content

Commit 1cbae85

Browse files
authored
Keep coroutine generator expressions on the ordinary call path (#9075)
Match Python 3.14 by excluding coroutine-marked generator expressions from builtin call optimization. Reuse the existing cursor-preserving comprehension lookup so nested outer iterable scopes do not select the wrong symbol table; propagate lookup errors. Assisted-by: OpenAI Codex:model identifier unavailable
1 parent eb6cefd commit 1cbae85

1 file changed

Lines changed: 37 additions & 34 deletions

File tree

‎crates/codegen/src/compile.rs‎

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9395,36 +9395,38 @@ impl<'warnings> Compiler<'warnings> {
93959395
Ok(())
93969396
}
93979397

9398-
/// The called name of a `name(genexpr)` call, the shape
9399-
/// `maybe_optimize_function_call()` reserves a `skip_optimization` label
9400-
/// for. An `await` or an `async for` inside the generator does not
9401-
/// disqualify it: the inlined loop raises the same `TypeError` that
9402-
/// calling the builtin on an async generator would.
9398+
/// The called name of a synchronous `name(genexpr)` call, the shape
9399+
/// `maybe_optimize_function_call()` reserves a `skip_optimization` label for.
94039400
fn cpython_genexpr_call_name<'a>(
9404-
&self,
9401+
&mut self,
94059402
func: &'a ast::Expr,
94069403
args: &ast::Arguments,
9407-
) -> Option<&'a str> {
9404+
) -> CompileResult<Option<&'a str>> {
94089405
let ast::Expr::Name(ast::ExprName { id, .. }) = func else {
9409-
return None;
9406+
return Ok(None);
94109407
};
9411-
let [ast::Expr::Generator(ast::ExprGenerator { .. })] = &args.args[..] else {
9412-
return None;
9408+
let [ast::Expr::Generator(ast::ExprGenerator { generators, .. })] = &args.args[..] else {
9409+
return Ok(None);
94139410
};
94149411
if !args.keywords.is_empty() {
9415-
return None;
9412+
return Ok(None);
94169413
}
9417-
let table = self.current_symbol_table();
9418-
table.sub_tables.get(table.next_sub_table)?;
9419-
Some(id.as_str())
9414+
let Some(outermost) = generators.first() else {
9415+
return Ok(None);
9416+
};
9417+
let (comp_table, _) = self
9418+
.lookup_comprehension_symbol_table_after_skipped_nested_scopes_in_expr(
9419+
&outermost.iter,
9420+
ComprehensionType::Generator,
9421+
)?;
9422+
if comp_table.is_coroutine {
9423+
return Ok(None);
9424+
}
9425+
Ok(Some(id.as_str()))
94209426
}
94219427

9422-
fn detect_builtin_generator_call(
9423-
&self,
9424-
func: &ast::Expr,
9425-
args: &ast::Arguments,
9426-
) -> Option<BuiltinGeneratorCallKind> {
9427-
match self.cpython_genexpr_call_name(func, args)? {
9428+
fn detect_builtin_generator_call(&self, name: &str) -> Option<BuiltinGeneratorCallKind> {
9429+
match name {
94289430
"tuple" => Some(BuiltinGeneratorCallKind::Tuple),
94299431
"all" => Some(BuiltinGeneratorCallKind::All),
94309432
"any" => Some(BuiltinGeneratorCallKind::Any),
@@ -9615,6 +9617,11 @@ impl<'warnings> Compiler<'warnings> {
96159617
let call_range = self.current_source_range;
96169618
self.validate_keywords(&args.keywords)?;
96179619
let uses_ex_call = self.call_uses_ex_call(args);
9620+
let genexpr_call_name = if uses_ex_call {
9621+
None
9622+
} else {
9623+
self.cpython_genexpr_call_name(func, args)?
9624+
};
96189625

96199626
// Method call: obj → LOAD_ATTR_METHOD → [method, self_or_null] → args → CALL
96209627
// Regular call: func → PUSH_NULL → args → CALL
@@ -9681,9 +9688,8 @@ impl<'warnings> Compiler<'warnings> {
96819688
self.emit_load_attr_method(idx);
96829689
self.compile_method_call_arguments(args, method_call_range, attr_access_range)?;
96839690
}
9684-
} else if let Some(kind) = (!uses_ex_call)
9685-
.then(|| self.detect_builtin_generator_call(func, args))
9686-
.flatten()
9691+
} else if let Some(kind) =
9692+
genexpr_call_name.and_then(|name| self.detect_builtin_generator_call(name))
96879693
{
96889694
let skip_normal_call = self.new_block();
96899695
self.check_caller(func)?;
@@ -9705,13 +9711,9 @@ impl<'warnings> Compiler<'warnings> {
97059711
// `skip_normal_call`, even when `maybe_optimize_function_call()`
97069712
// leaves it untargeted.
97079713
let skip_normal_call = self.current_code_info().new_instr_sequence_label();
9708-
let genexpr_call_name = (!uses_ex_call)
9709-
.then(|| self.cpython_genexpr_call_name(func, args))
9710-
.flatten()
9711-
.is_some();
97129714
self.check_caller(func)?;
97139715
self.compile_expression(func)?;
9714-
if genexpr_call_name {
9716+
if genexpr_call_name.is_some() {
97159717
// CPython `maybe_optimize_function_call()` creates and uses
97169718
// `skip_optimization` for every name(genexpr) shape after
97179719
// loading the function, even when the name is not all/any/tuple.
@@ -20608,24 +20610,25 @@ def f(xs):
2060820610
}
2060920611

2061020612
#[test]
20611-
fn builtin_any_async_genexpr_call_is_optimized_like_cpython() {
20613+
fn builtin_any_async_genexpr_call_is_not_optimized_like_cpython() {
2061220614
for source in [
2061320615
"async def f(xs):\n return any(x async for x in xs)\n",
2061420616
"async def f(xs):\n return any(await x for x in xs)\n",
20617+
"async def f(xs):\n return any(x async for x in (lambda: xs)())\n",
20618+
"async def f(xs):\n return any(await x for x in (y for y in xs))\n",
2061520619
] {
2061620620
let code = compile_exec(source);
2061720621
let f = find_code(&code, "f").expect("missing function code");
2061820622

2061920623
assert!(
20620-
has_common_constant(f, bytecode::CommonConstant::BuiltinAny),
20621-
"maybe_optimize_function_call() guards any(genexpr) whether or not the \
20622-
generator is a coroutine: {source}"
20624+
!has_common_constant(f, bytecode::CommonConstant::BuiltinAny),
20625+
"coroutine generator expressions must use the ordinary call path: {source}"
2062320626
);
2062420627
assert!(
20625-
f.instructions
20628+
!f.instructions
2062620629
.iter()
2062720630
.any(|unit| matches!(unit.op, Instruction::ForIter { .. })),
20628-
"the guarded path inlines the loop: {source}"
20631+
"the ordinary call path must not inline an async generator loop: {source}"
2062920632
);
2063020633
assert!(
2063120634
f.instructions

0 commit comments

Comments
 (0)