From 16e26de5ebf5abdcfa4409319fb69c262938bd99 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Thu, 22 Jan 2026 14:39:59 +0900 Subject: [PATCH 1/6] Update types from v3.14.2 Co-Authored-By: CPython Developers <> --- Lib/types.py | 125 +++++++++++++++++++++++++-------------------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/Lib/types.py b/Lib/types.py index b036a850687..0480d3f1357 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -2,65 +2,78 @@ Define names for built-in types that aren't directly accessible as a builtin. """ -import sys - # Iterators in Python aren't a matter of type but of protocol. A large # and changing number of builtin types implement *some* flavor of # iterator. Don't check the type! Use hasattr to check for both # "__iter__" and "__next__" attributes instead. -def _f(): pass -FunctionType = type(_f) -LambdaType = type(lambda: None) # Same as FunctionType -CodeType = type(_f.__code__) -MappingProxyType = type(type.__dict__) -SimpleNamespace = type(sys.implementation) - -def _cell_factory(): - a = 1 - def f(): - nonlocal a - return f.__closure__[0] -CellType = type(_cell_factory()) - -def _g(): - yield 1 -GeneratorType = type(_g()) - -async def _c(): pass -_c = _c() -CoroutineType = type(_c) -_c.close() # Prevent ResourceWarning - -async def _ag(): - yield -_ag = _ag() -AsyncGeneratorType = type(_ag) - -class _C: - def _m(self): pass -MethodType = type(_C()._m) +try: + from _types import * +except ImportError: + import sys + + def _f(): pass + FunctionType = type(_f) + LambdaType = type(lambda: None) # Same as FunctionType + CodeType = type(_f.__code__) + MappingProxyType = type(type.__dict__) + SimpleNamespace = type(sys.implementation) + + def _cell_factory(): + a = 1 + def f(): + nonlocal a + return f.__closure__[0] + CellType = type(_cell_factory()) + + def _g(): + yield 1 + GeneratorType = type(_g()) + + async def _c(): pass + _c = _c() + CoroutineType = type(_c) + _c.close() # Prevent ResourceWarning + + async def _ag(): + yield + _ag = _ag() + AsyncGeneratorType = type(_ag) + + class _C: + def _m(self): pass + MethodType = type(_C()._m) + + BuiltinFunctionType = type(len) + BuiltinMethodType = type([].append) # Same as BuiltinFunctionType + + WrapperDescriptorType = type(object.__init__) + MethodWrapperType = type(object().__str__) + MethodDescriptorType = type(str.join) + ClassMethodDescriptorType = type(dict.__dict__['fromkeys']) + + ModuleType = type(sys) -BuiltinFunctionType = type(len) -BuiltinMethodType = type([].append) # Same as BuiltinFunctionType + try: + raise TypeError + except TypeError as exc: + TracebackType = type(exc.__traceback__) + FrameType = type(exc.__traceback__.tb_frame) -WrapperDescriptorType = type(object.__init__) -MethodWrapperType = type(object().__str__) -MethodDescriptorType = type(str.join) -ClassMethodDescriptorType = type(dict.__dict__['fromkeys']) + GetSetDescriptorType = type(FunctionType.__code__) + MemberDescriptorType = type(FunctionType.__globals__) -ModuleType = type(sys) + GenericAlias = type(list[int]) + UnionType = type(int | str) -try: - raise TypeError -except TypeError as exc: - TracebackType = type(exc.__traceback__) - FrameType = type(exc.__traceback__.tb_frame) + EllipsisType = type(Ellipsis) + NoneType = type(None) + NotImplementedType = type(NotImplemented) -GetSetDescriptorType = type(FunctionType.__code__) -MemberDescriptorType = type(FunctionType.__globals__) + # CapsuleType cannot be accessed from pure Python, + # so there is no fallback definition. -del sys, _f, _g, _C, _c, _ag, _cell_factory # Not for export + del sys, _f, _g, _C, _c, _ag, _cell_factory # Not for export # Provide a PEP 3115 compliant mechanism for class creation @@ -325,18 +338,4 @@ def wrapped(*args, **kwargs): return wrapped -GenericAlias = type(list[int]) -UnionType = type(int | str) - -EllipsisType = type(Ellipsis) -NoneType = type(None) -NotImplementedType = type(NotImplemented) - -def __getattr__(name): - if name == 'CapsuleType': - import _socket - return type(_socket.CAPI) - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - -__all__ = [n for n in globals() if n[:1] != '_'] -__all__ += ['CapsuleType'] +__all__ = [n for n in globals() if not n.startswith('_')] # for pydoc From 6b98862820b46e82ca49a13df172d4fb39a5e425 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Thu, 22 Jan 2026 14:53:41 +0900 Subject: [PATCH 2/6] Add CO_ITERABLE_COROUTINE flag and fix types tests - Add ITERABLE_COROUTINE (0x0100) to CodeFlags in bytecode.rs - Update frame.rs to check for both COROUTINE and ITERABLE_COROUTINE flags when validating 'yield from' on coroutine objects - Remove False and workaround in types.coroutine() now that the flag is supported - Remove @unittest.expectedFailure from test_async_def and test_genfunc Co-Authored-By: Claude Opus 4.5 --- Lib/test/test_collections.py | 2 -- Lib/test/test_types.py | 2 -- Lib/types.py | 3 +-- crates/compiler-core/src/bytecode.rs | 1 + crates/vm/src/frame.rs | 4 +++- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 20aba892352..b68305dd7aa 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -787,7 +787,6 @@ def _test_gen(): class TestOneTrickPonyABCs(ABCTestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_Awaitable(self): def gen(): yield @@ -840,7 +839,6 @@ class CoroLike: pass CoroLike = None support.gc_collect() # Kill CoroLike to clean-up ABCMeta cache - @unittest.expectedFailure # TODO: RUSTPYTHON def test_Coroutine(self): def gen(): yield diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 073599e1dd2..06099b87427 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -2174,7 +2174,6 @@ def foo(): foo = types.coroutine(foo) self.assertIs(aw, foo()) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_async_def(self): # Test that types.coroutine passes 'async def' coroutines # without modification @@ -2431,7 +2430,6 @@ def foo(): foo = types.coroutine(foo) self.assertIs(foo(), gencoro) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_genfunc(self): def gen(): yield self.assertIs(types.coroutine(gen), gen) diff --git a/Lib/types.py b/Lib/types.py index 0480d3f1357..6efac339434 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -292,8 +292,7 @@ def coroutine(func): if not callable(func): raise TypeError('types.coroutine() expects a callable') - # XXX RUSTPYTHON TODO: iterable coroutine - if (False and func.__class__ is FunctionType and + if (func.__class__ is FunctionType and getattr(func, '__code__', None).__class__ is CodeType): co_flags = func.__code__.co_flags diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index daf954166e6..3657c3b1b09 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -297,6 +297,7 @@ bitflags! { const VARKEYWORDS = 0x0008; const GENERATOR = 0x0020; const COROUTINE = 0x0080; + const ITERABLE_COROUTINE = 0x0100; /// If a code object represents a function and has a docstring, /// this bit is set and the first item in co_consts is the docstring. const HAS_DOCSTRING = 0x4000000; diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index d0de7d8709f..f86ddab39a0 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1051,7 +1051,9 @@ impl ExecutingFrame<'_> { let iterable = self.pop_value(); let iter = if iterable.class().is(vm.ctx.types.coroutine_type) { // Coroutine requires CO_COROUTINE or CO_ITERABLE_COROUTINE flag - if !self.code.flags.intersects(bytecode::CodeFlags::COROUTINE) { + if !self.code.flags.intersects( + bytecode::CodeFlags::COROUTINE | bytecode::CodeFlags::ITERABLE_COROUTINE, + ) { return Err(vm.new_type_error( "cannot 'yield from' a coroutine object in a non-coroutine generator" .to_owned(), From be354cd3d86500a719205aa8a28ee45d70fa7f9a Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Thu, 22 Jan 2026 16:08:05 +0900 Subject: [PATCH 3/6] Handle CO_ITERABLE_COROUTINE flag in GetAwaitable instruction Generators decorated with @types.coroutine have the CO_ITERABLE_COROUTINE flag set, making them awaitable. The GetAwaitable instruction now properly handles this case instead of only checking for __await__ method. This fixes the "object generator can't be used in 'await' expression" error that occurred with asyncio.sleep(0) which uses @types.coroutine decorated __sleep0() generator internally. Co-Authored-By: Claude Opus 4.5 --- crates/vm/src/frame.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index f86ddab39a0..3ef52abeb85 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1013,6 +1013,23 @@ impl ExecutingFrame<'_> { ); } awaited_obj + } else if let Some(generator) = awaited_obj.downcast_ref::() { + // Generator with CO_ITERABLE_COROUTINE flag can be awaited + // (e.g., generators decorated with @types.coroutine) + if generator + .as_coro() + .frame() + .code + .flags + .contains(bytecode::CodeFlags::ITERABLE_COROUTINE) + { + awaited_obj + } else { + return Err(vm.new_type_error(format!( + "object {} can't be used in 'await' expression", + awaited_obj.class().name(), + ))); + } } else { let await_method = vm.get_method_or_type_error( awaited_obj.clone(), From ae3a2eb8961e8197bf5bc1d923e5993772843ff6 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Thu, 22 Jan 2026 17:01:52 +0900 Subject: [PATCH 4/6] Handle CO_ITERABLE_COROUTINE flag in anext_awaitable Generators decorated with @types.coroutine have the CO_ITERABLE_COROUTINE flag and can be used in await expressions. The PyAnextAwaitable's get_awaitable_iter method was missing this check, causing builtin anext() to fail with "object generator can't be used in 'await' expression" when used with such generators. Co-Authored-By: Claude Opus 4.5 --- crates/vm/src/builtins/asyncgenerator.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index 8b7c107d4b8..479e489ea54 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -1,4 +1,4 @@ -use super::{PyCode, PyGenericAlias, PyStrRef, PyType, PyTypeRef}; +use super::{PyCode, PyGenerator, PyGenericAlias, PyStrRef, PyType, PyTypeRef}; use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::PyBaseExceptionRef, @@ -592,6 +592,24 @@ impl PyAnextAwaitable { let awaitable = if wrapped.class().is(vm.ctx.types.coroutine_type) { // Coroutine - get __await__ later wrapped.clone() + } else if let Some(generator) = wrapped.downcast_ref::() { + // Generator with CO_ITERABLE_COROUTINE flag can be awaited + // (e.g., generators decorated with @types.coroutine) + if generator + .as_coro() + .frame() + .code + .flags + .contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE) + { + // Return the generator itself as the iterator + return Ok(wrapped.clone()); + } else { + return Err(vm.new_type_error(format!( + "object {} can't be used in 'await' expression", + wrapped.class().name() + ))); + } } else { // Try to get __await__ method if let Some(await_method) = vm.get_method(wrapped.clone(), identifier!(vm, __await__)) { From b76dde11c547b2d84ba02245abf714841b152e35 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Thu, 22 Jan 2026 17:28:46 +0900 Subject: [PATCH 5/6] Try __await__ method for generator subclasses in anext_awaitable For generators without CO_ITERABLE_COROUTINE flag, try to get __await__ method instead of returning an error immediately. This matches CPython's _PyCoro_GetAwaitableIter behavior which allows generator subclasses that define __await__ to be used in await expressions. Co-Authored-By: Claude Opus 4.5 --- crates/vm/src/builtins/asyncgenerator.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index 479e489ea54..7cf0ea31f85 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -604,6 +604,10 @@ impl PyAnextAwaitable { { // Return the generator itself as the iterator return Ok(wrapped.clone()); + } + // Fall through: try to get __await__ method for generator subclasses + if let Some(await_method) = vm.get_method(wrapped.clone(), identifier!(vm, __await__)) { + await_method?.call((), vm)? } else { return Err(vm.new_type_error(format!( "object {} can't be used in 'await' expression", From 0be2f40178f7dfc2c0f86374396e427c98e3c895 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 23 Jan 2026 20:33:12 +0900 Subject: [PATCH 6/6] apply review --- crates/vm/src/frame.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 3ef52abeb85..281827984ab 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1013,23 +1013,20 @@ impl ExecutingFrame<'_> { ); } awaited_obj - } else if let Some(generator) = awaited_obj.downcast_ref::() { + } else if awaited_obj + .downcast_ref::() + .is_some_and(|generator| { + generator + .as_coro() + .frame() + .code + .flags + .contains(bytecode::CodeFlags::ITERABLE_COROUTINE) + }) + { // Generator with CO_ITERABLE_COROUTINE flag can be awaited // (e.g., generators decorated with @types.coroutine) - if generator - .as_coro() - .frame() - .code - .flags - .contains(bytecode::CodeFlags::ITERABLE_COROUTINE) - { - awaited_obj - } else { - return Err(vm.new_type_error(format!( - "object {} can't be used in 'await' expression", - awaited_obj.class().name(), - ))); - } + awaited_obj } else { let await_method = vm.get_method_or_type_error( awaited_obj.clone(),