@@ -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