From 465ecda19e22e46beaaf76fb2196e7635a9443fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Fri, 13 Mar 2026 16:56:13 -0400 Subject: [PATCH 1/7] Uncomment async code from 3.6 --- .../IronPython.StdLib/lib/_collections_abc.py | 282 ++++++----- .../lib/test/test_collections.py | 462 +++++++++--------- .../lib/test/test_grammar.py | 141 +++--- .../IronPython.StdLib/lib/test/test_types.py | 8 +- src/core/IronPython.StdLib/lib/types.py | 23 +- 5 files changed, 453 insertions(+), 463 deletions(-) diff --git a/src/core/IronPython.StdLib/lib/_collections_abc.py b/src/core/IronPython.StdLib/lib/_collections_abc.py index 5f8c3186e..aafd43046 100644 --- a/src/core/IronPython.StdLib/lib/_collections_abc.py +++ b/src/core/IronPython.StdLib/lib/_collections_abc.py @@ -9,8 +9,8 @@ from abc import ABCMeta, abstractmethod import sys -__all__ = ["Awaitable", - # "Coroutine", "AsyncIterable", "AsyncIterator", "AsyncGenerator", # https://github.com/IronLanguages/ironpython3/issues/1428 +__all__ = ["Awaitable", "Coroutine", + "AsyncIterable", "AsyncIterator", "AsyncGenerator", "Hashable", "Iterable", "Iterator", "Generator", "Reversible", "Sized", "Container", "Callable", "Collection", "Set", "MutableSet", @@ -54,18 +54,17 @@ ## misc ## mappingproxy = type(type.__dict__) generator = type((lambda: (yield))()) -# https://github.com/IronLanguages/ironpython3/issues/1428 -# ## coroutine ## -# async def _coro(): pass -# _coro = _coro() -# coroutine = type(_coro) -# _coro.close() # Prevent ResourceWarning -# del _coro -# ## asynchronous generator ## -# async def _ag(): yield -# _ag = _ag() -# async_generator = type(_ag) -# del _ag +## coroutine ## +async def _coro(): pass +_coro = _coro() +coroutine = type(_coro) +_coro.close() # Prevent ResourceWarning +del _coro +## asynchronous generator ## +async def _ag(): yield +_ag = _ag() +async_generator = type(_ag) +del _ag ### ONE-TRICK PONIES ### @@ -112,134 +111,133 @@ def __subclasshook__(cls, C): return NotImplemented -# https://github.com/IronLanguages/ironpython3/issues/1428 -# class Coroutine(Awaitable): -# -# __slots__ = () -# -# @abstractmethod -# def send(self, value): -# """Send a value into the coroutine. -# Return next yielded value or raise StopIteration. -# """ -# raise StopIteration -# -# @abstractmethod -# def throw(self, typ, val=None, tb=None): -# """Raise an exception in the coroutine. -# Return next yielded value or raise StopIteration. -# """ -# if val is None: -# if tb is None: -# raise typ -# val = typ() -# if tb is not None: -# val = val.with_traceback(tb) -# raise val -# -# def close(self): -# """Raise GeneratorExit inside coroutine. -# """ -# try: -# self.throw(GeneratorExit) -# except (GeneratorExit, StopIteration): -# pass -# else: -# raise RuntimeError("coroutine ignored GeneratorExit") -# -# @classmethod -# def __subclasshook__(cls, C): -# if cls is Coroutine: -# return _check_methods(C, '__await__', 'send', 'throw', 'close') -# return NotImplemented -# -# -# Coroutine.register(coroutine) -# -# -# class AsyncIterable(metaclass=ABCMeta): -# -# __slots__ = () -# -# @abstractmethod -# def __aiter__(self): -# return AsyncIterator() -# -# @classmethod -# def __subclasshook__(cls, C): -# if cls is AsyncIterable: -# return _check_methods(C, "__aiter__") -# return NotImplemented -# -# -# class AsyncIterator(AsyncIterable): -# -# __slots__ = () -# -# @abstractmethod -# async def __anext__(self): -# """Return the next item or raise StopAsyncIteration when exhausted.""" -# raise StopAsyncIteration -# -# def __aiter__(self): -# return self -# -# @classmethod -# def __subclasshook__(cls, C): -# if cls is AsyncIterator: -# return _check_methods(C, "__anext__", "__aiter__") -# return NotImplemented -# -# -# class AsyncGenerator(AsyncIterator): -# -# __slots__ = () -# -# async def __anext__(self): -# """Return the next item from the asynchronous generator. -# When exhausted, raise StopAsyncIteration. -# """ -# return await self.asend(None) -# -# @abstractmethod -# async def asend(self, value): -# """Send a value into the asynchronous generator. -# Return next yielded value or raise StopAsyncIteration. -# """ -# raise StopAsyncIteration -# -# @abstractmethod -# async def athrow(self, typ, val=None, tb=None): -# """Raise an exception in the asynchronous generator. -# Return next yielded value or raise StopAsyncIteration. -# """ -# if val is None: -# if tb is None: -# raise typ -# val = typ() -# if tb is not None: -# val = val.with_traceback(tb) -# raise val -# -# async def aclose(self): -# """Raise GeneratorExit inside coroutine. -# """ -# try: -# await self.athrow(GeneratorExit) -# except (GeneratorExit, StopAsyncIteration): -# pass -# else: -# raise RuntimeError("asynchronous generator ignored GeneratorExit") -# -# @classmethod -# def __subclasshook__(cls, C): -# if cls is AsyncGenerator: -# return _check_methods(C, '__aiter__', '__anext__', -# 'asend', 'athrow', 'aclose') -# return NotImplemented -# -# -# AsyncGenerator.register(async_generator) +class Coroutine(Awaitable): + + __slots__ = () + + @abstractmethod + def send(self, value): + """Send a value into the coroutine. + Return next yielded value or raise StopIteration. + """ + raise StopIteration + + @abstractmethod + def throw(self, typ, val=None, tb=None): + """Raise an exception in the coroutine. + Return next yielded value or raise StopIteration. + """ + if val is None: + if tb is None: + raise typ + val = typ() + if tb is not None: + val = val.with_traceback(tb) + raise val + + def close(self): + """Raise GeneratorExit inside coroutine. + """ + try: + self.throw(GeneratorExit) + except (GeneratorExit, StopIteration): + pass + else: + raise RuntimeError("coroutine ignored GeneratorExit") + + @classmethod + def __subclasshook__(cls, C): + if cls is Coroutine: + return _check_methods(C, '__await__', 'send', 'throw', 'close') + return NotImplemented + + +Coroutine.register(coroutine) + + +class AsyncIterable(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __aiter__(self): + return AsyncIterator() + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncIterable: + return _check_methods(C, "__aiter__") + return NotImplemented + + +class AsyncIterator(AsyncIterable): + + __slots__ = () + + @abstractmethod + async def __anext__(self): + """Return the next item or raise StopAsyncIteration when exhausted.""" + raise StopAsyncIteration + + def __aiter__(self): + return self + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncIterator: + return _check_methods(C, "__anext__", "__aiter__") + return NotImplemented + + +class AsyncGenerator(AsyncIterator): + + __slots__ = () + + async def __anext__(self): + """Return the next item from the asynchronous generator. + When exhausted, raise StopAsyncIteration. + """ + return await self.asend(None) + + @abstractmethod + async def asend(self, value): + """Send a value into the asynchronous generator. + Return next yielded value or raise StopAsyncIteration. + """ + raise StopAsyncIteration + + @abstractmethod + async def athrow(self, typ, val=None, tb=None): + """Raise an exception in the asynchronous generator. + Return next yielded value or raise StopAsyncIteration. + """ + if val is None: + if tb is None: + raise typ + val = typ() + if tb is not None: + val = val.with_traceback(tb) + raise val + + async def aclose(self): + """Raise GeneratorExit inside coroutine. + """ + try: + await self.athrow(GeneratorExit) + except (GeneratorExit, StopAsyncIteration): + pass + else: + raise RuntimeError("asynchronous generator ignored GeneratorExit") + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncGenerator: + return _check_methods(C, '__aiter__', '__anext__', + 'asend', 'athrow', 'aclose') + return NotImplemented + + +AsyncGenerator.register(async_generator) class Iterable(metaclass=ABCMeta): diff --git a/src/core/IronPython.StdLib/lib/test/test_collections.py b/src/core/IronPython.StdLib/lib/test/test_collections.py index 9e0774c9a..6f3b44eba 100644 --- a/src/core/IronPython.StdLib/lib/test/test_collections.py +++ b/src/core/IronPython.StdLib/lib/test/test_collections.py @@ -19,9 +19,8 @@ from collections import UserDict, UserString, UserList from collections import ChainMap from collections import deque -# https://github.com/IronLanguages/ironpython3/issues/1428 -# from collections.abc import Awaitable, Coroutine -# from collections.abc import AsyncIterator, AsyncIterable, AsyncGenerator +from collections.abc import Awaitable, Coroutine +from collections.abc import AsyncIterator, AsyncIterable, AsyncGenerator from collections.abc import Hashable, Iterable, Iterator, Generator, Reversible from collections.abc import Sized, Container, Callable, Collection from collections.abc import Set, MutableSet @@ -510,121 +509,120 @@ def _test_gen(): class TestOneTrickPonyABCs(ABCTestCase): -# https://github.com/IronLanguages/ironpython3/issues/1428 -# def test_Awaitable(self): -# def gen(): -# yield -# -# @types.coroutine -# def coro(): -# yield -# -# async def new_coro(): -# pass -# -# class Bar: -# def __await__(self): -# yield -# -# class MinimalCoro(Coroutine): -# def send(self, value): -# return value -# def throw(self, typ, val=None, tb=None): -# super().throw(typ, val, tb) -# def __await__(self): -# yield -# -# non_samples = [None, int(), gen(), object()] -# for x in non_samples: -# self.assertNotIsInstance(x, Awaitable) -# self.assertFalse(issubclass(type(x), Awaitable), repr(type(x))) -# -# samples = [Bar(), MinimalCoro()] -# for x in samples: -# self.assertIsInstance(x, Awaitable) -# self.assertTrue(issubclass(type(x), Awaitable)) -# -# c = coro() -# # Iterable coroutines (generators with CO_ITERABLE_COROUTINE -# # flag don't have '__await__' method, hence can't be instances -# # of Awaitable. Use inspect.isawaitable to detect them. -# self.assertNotIsInstance(c, Awaitable) -# -# c = new_coro() -# self.assertIsInstance(c, Awaitable) -# c.close() # avoid RuntimeWarning that coro() was not awaited -# -# class CoroLike: pass -# Coroutine.register(CoroLike) -# self.assertTrue(isinstance(CoroLike(), Awaitable)) -# self.assertTrue(issubclass(CoroLike, Awaitable)) -# CoroLike = None -# support.gc_collect() # Kill CoroLike to clean-up ABCMeta cache -# -# def test_Coroutine(self): -# def gen(): -# yield -# -# @types.coroutine -# def coro(): -# yield -# -# async def new_coro(): -# pass -# -# class Bar: -# def __await__(self): -# yield -# -# class MinimalCoro(Coroutine): -# def send(self, value): -# return value -# def throw(self, typ, val=None, tb=None): -# super().throw(typ, val, tb) -# def __await__(self): -# yield -# -# non_samples = [None, int(), gen(), object(), Bar()] -# for x in non_samples: -# self.assertNotIsInstance(x, Coroutine) -# self.assertFalse(issubclass(type(x), Coroutine), repr(type(x))) -# -# samples = [MinimalCoro()] -# for x in samples: -# self.assertIsInstance(x, Awaitable) -# self.assertTrue(issubclass(type(x), Awaitable)) -# -# c = coro() -# # Iterable coroutines (generators with CO_ITERABLE_COROUTINE -# # flag don't have '__await__' method, hence can't be instances -# # of Coroutine. Use inspect.isawaitable to detect them. -# self.assertNotIsInstance(c, Coroutine) -# -# c = new_coro() -# self.assertIsInstance(c, Coroutine) -# c.close() # avoid RuntimeWarning that coro() was not awaited -# -# class CoroLike: -# def send(self, value): -# pass -# def throw(self, typ, val=None, tb=None): -# pass -# def close(self): -# pass -# def __await__(self): -# pass -# self.assertTrue(isinstance(CoroLike(), Coroutine)) -# self.assertTrue(issubclass(CoroLike, Coroutine)) -# -# class CoroLike: -# def send(self, value): -# pass -# def close(self): -# pass -# def __await__(self): -# pass -# self.assertFalse(isinstance(CoroLike(), Coroutine)) -# self.assertFalse(issubclass(CoroLike, Coroutine)) + def test_Awaitable(self): + def gen(): + yield + + @types.coroutine + def coro(): + yield + + async def new_coro(): + pass + + class Bar: + def __await__(self): + yield + + class MinimalCoro(Coroutine): + def send(self, value): + return value + def throw(self, typ, val=None, tb=None): + super().throw(typ, val, tb) + def __await__(self): + yield + + non_samples = [None, int(), gen(), object()] + for x in non_samples: + self.assertNotIsInstance(x, Awaitable) + self.assertFalse(issubclass(type(x), Awaitable), repr(type(x))) + + samples = [Bar(), MinimalCoro()] + for x in samples: + self.assertIsInstance(x, Awaitable) + self.assertTrue(issubclass(type(x), Awaitable)) + + c = coro() + # Iterable coroutines (generators with CO_ITERABLE_COROUTINE + # flag don't have '__await__' method, hence can't be instances + # of Awaitable. Use inspect.isawaitable to detect them. + self.assertNotIsInstance(c, Awaitable) + + c = new_coro() + self.assertIsInstance(c, Awaitable) + c.close() # avoid RuntimeWarning that coro() was not awaited + + class CoroLike: pass + Coroutine.register(CoroLike) + self.assertTrue(isinstance(CoroLike(), Awaitable)) + self.assertTrue(issubclass(CoroLike, Awaitable)) + CoroLike = None + support.gc_collect() # Kill CoroLike to clean-up ABCMeta cache + + def test_Coroutine(self): + def gen(): + yield + + @types.coroutine + def coro(): + yield + + async def new_coro(): + pass + + class Bar: + def __await__(self): + yield + + class MinimalCoro(Coroutine): + def send(self, value): + return value + def throw(self, typ, val=None, tb=None): + super().throw(typ, val, tb) + def __await__(self): + yield + + non_samples = [None, int(), gen(), object(), Bar()] + for x in non_samples: + self.assertNotIsInstance(x, Coroutine) + self.assertFalse(issubclass(type(x), Coroutine), repr(type(x))) + + samples = [MinimalCoro()] + for x in samples: + self.assertIsInstance(x, Awaitable) + self.assertTrue(issubclass(type(x), Awaitable)) + + c = coro() + # Iterable coroutines (generators with CO_ITERABLE_COROUTINE + # flag don't have '__await__' method, hence can't be instances + # of Coroutine. Use inspect.isawaitable to detect them. + self.assertNotIsInstance(c, Coroutine) + + c = new_coro() + self.assertIsInstance(c, Coroutine) + c.close() # avoid RuntimeWarning that coro() was not awaited + + class CoroLike: + def send(self, value): + pass + def throw(self, typ, val=None, tb=None): + pass + def close(self): + pass + def __await__(self): + pass + self.assertTrue(isinstance(CoroLike(), Coroutine)) + self.assertTrue(issubclass(CoroLike, Coroutine)) + + class CoroLike: + def send(self, value): + pass + def close(self): + pass + def __await__(self): + pass + self.assertFalse(isinstance(CoroLike(), Coroutine)) + self.assertFalse(issubclass(CoroLike, Coroutine)) def test_Hashable(self): # Check some non-hashables @@ -652,40 +650,39 @@ def __hash__(self): self.validate_abstract_methods(Hashable, '__hash__') self.validate_isinstance(Hashable, '__hash__') -# https://github.com/IronLanguages/ironpython3/issues/1428 -# def test_AsyncIterable(self): -# class AI: -# async def __aiter__(self): -# return self -# self.assertTrue(isinstance(AI(), AsyncIterable)) -# self.assertTrue(issubclass(AI, AsyncIterable)) -# # Check some non-iterables -# non_samples = [None, object, []] -# for x in non_samples: -# self.assertNotIsInstance(x, AsyncIterable) -# self.assertFalse(issubclass(type(x), AsyncIterable), repr(type(x))) -# self.validate_abstract_methods(AsyncIterable, '__aiter__') -# self.validate_isinstance(AsyncIterable, '__aiter__') -# -# def test_AsyncIterator(self): -# class AI: -# async def __aiter__(self): -# return self -# async def __anext__(self): -# raise StopAsyncIteration -# self.assertTrue(isinstance(AI(), AsyncIterator)) -# self.assertTrue(issubclass(AI, AsyncIterator)) -# non_samples = [None, object, []] -# # Check some non-iterables -# for x in non_samples: -# self.assertNotIsInstance(x, AsyncIterator) -# self.assertFalse(issubclass(type(x), AsyncIterator), repr(type(x))) -# # Similarly to regular iterators (see issue 10565) -# class AnextOnly: -# async def __anext__(self): -# raise StopAsyncIteration -# self.assertNotIsInstance(AnextOnly(), AsyncIterator) -# self.validate_abstract_methods(AsyncIterator, '__anext__', '__aiter__') + def test_AsyncIterable(self): + class AI: + async def __aiter__(self): + return self + self.assertTrue(isinstance(AI(), AsyncIterable)) + self.assertTrue(issubclass(AI, AsyncIterable)) + # Check some non-iterables + non_samples = [None, object, []] + for x in non_samples: + self.assertNotIsInstance(x, AsyncIterable) + self.assertFalse(issubclass(type(x), AsyncIterable), repr(type(x))) + self.validate_abstract_methods(AsyncIterable, '__aiter__') + self.validate_isinstance(AsyncIterable, '__aiter__') + + def test_AsyncIterator(self): + class AI: + async def __aiter__(self): + return self + async def __anext__(self): + raise StopAsyncIteration + self.assertTrue(isinstance(AI(), AsyncIterator)) + self.assertTrue(issubclass(AI, AsyncIterator)) + non_samples = [None, object, []] + # Check some non-iterables + for x in non_samples: + self.assertNotIsInstance(x, AsyncIterator) + self.assertFalse(issubclass(type(x), AsyncIterator), repr(type(x))) + # Similarly to regular iterators (see issue 10565) + class AnextOnly: + async def __anext__(self): + raise StopAsyncIteration + self.assertNotIsInstance(AnextOnly(), AsyncIterator) + self.validate_abstract_methods(AsyncIterator, '__anext__', '__aiter__') def test_Iterable(self): # Check some non-iterables @@ -963,87 +960,86 @@ def throw(self, *args): pass self.assertRaises(RuntimeError, IgnoreGeneratorExit().close) -# https://github.com/IronLanguages/ironpython3/issues/1428 -# def test_AsyncGenerator(self): -# class NonAGen1: -# def __aiter__(self): return self -# def __anext__(self): return None -# def aclose(self): pass -# def athrow(self, typ, val=None, tb=None): pass -# -# class NonAGen2: -# def __aiter__(self): return self -# def __anext__(self): return None -# def aclose(self): pass -# def asend(self, value): return value -# -# class NonAGen3: -# def aclose(self): pass -# def asend(self, value): return value -# def athrow(self, typ, val=None, tb=None): pass -# -# non_samples = [ -# None, 42, 3.14, 1j, b"", "", (), [], {}, set(), -# iter(()), iter([]), NonAGen1(), NonAGen2(), NonAGen3()] -# for x in non_samples: -# self.assertNotIsInstance(x, AsyncGenerator) -# self.assertFalse(issubclass(type(x), AsyncGenerator), repr(type(x))) -# -# class Gen: -# def __aiter__(self): return self -# async def __anext__(self): return None -# async def aclose(self): pass -# async def asend(self, value): return value -# async def athrow(self, typ, val=None, tb=None): pass -# -# class MinimalAGen(AsyncGenerator): -# async def asend(self, value): -# return value -# async def athrow(self, typ, val=None, tb=None): -# await super().athrow(typ, val, tb) -# -# async def gen(): -# yield 1 -# -# samples = [gen(), Gen(), MinimalAGen()] -# for x in samples: -# self.assertIsInstance(x, AsyncIterator) -# self.assertIsInstance(x, AsyncGenerator) -# self.assertTrue(issubclass(type(x), AsyncGenerator), repr(type(x))) -# self.validate_abstract_methods(AsyncGenerator, 'asend', 'athrow') -# -# def run_async(coro): -# result = None -# while True: -# try: -# coro.send(None) -# except StopIteration as ex: -# result = ex.args[0] if ex.args else None -# break -# return result -# -# # mixin tests -# mgen = MinimalAGen() -# self.assertIs(mgen, mgen.__aiter__()) -# self.assertIs(run_async(mgen.asend(None)), run_async(mgen.__anext__())) -# self.assertEqual(2, run_async(mgen.asend(2))) -# self.assertIsNone(run_async(mgen.aclose())) -# with self.assertRaises(ValueError): -# run_async(mgen.athrow(ValueError)) -# -# class FailOnClose(AsyncGenerator): -# async def asend(self, value): return value -# async def athrow(self, *args): raise ValueError -# -# with self.assertRaises(ValueError): -# run_async(FailOnClose().aclose()) -# -# class IgnoreGeneratorExit(AsyncGenerator): -# async def asend(self, value): return value -# async def athrow(self, *args): pass -# -# with self.assertRaises(RuntimeError): -# run_async(IgnoreGeneratorExit().aclose()) + def test_AsyncGenerator(self): + class NonAGen1: + def __aiter__(self): return self + def __anext__(self): return None + def aclose(self): pass + def athrow(self, typ, val=None, tb=None): pass + + class NonAGen2: + def __aiter__(self): return self + def __anext__(self): return None + def aclose(self): pass + def asend(self, value): return value + + class NonAGen3: + def aclose(self): pass + def asend(self, value): return value + def athrow(self, typ, val=None, tb=None): pass + + non_samples = [ + None, 42, 3.14, 1j, b"", "", (), [], {}, set(), + iter(()), iter([]), NonAGen1(), NonAGen2(), NonAGen3()] + for x in non_samples: + self.assertNotIsInstance(x, AsyncGenerator) + self.assertFalse(issubclass(type(x), AsyncGenerator), repr(type(x))) + + class Gen: + def __aiter__(self): return self + async def __anext__(self): return None + async def aclose(self): pass + async def asend(self, value): return value + async def athrow(self, typ, val=None, tb=None): pass + + class MinimalAGen(AsyncGenerator): + async def asend(self, value): + return value + async def athrow(self, typ, val=None, tb=None): + await super().athrow(typ, val, tb) + + async def gen(): + yield 1 + + samples = [gen(), Gen(), MinimalAGen()] + for x in samples: + self.assertIsInstance(x, AsyncIterator) + self.assertIsInstance(x, AsyncGenerator) + self.assertTrue(issubclass(type(x), AsyncGenerator), repr(type(x))) + self.validate_abstract_methods(AsyncGenerator, 'asend', 'athrow') + + def run_async(coro): + result = None + while True: + try: + coro.send(None) + except StopIteration as ex: + result = ex.args[0] if ex.args else None + break + return result + + # mixin tests + mgen = MinimalAGen() + self.assertIs(mgen, mgen.__aiter__()) + self.assertIs(run_async(mgen.asend(None)), run_async(mgen.__anext__())) + self.assertEqual(2, run_async(mgen.asend(2))) + self.assertIsNone(run_async(mgen.aclose())) + with self.assertRaises(ValueError): + run_async(mgen.athrow(ValueError)) + + class FailOnClose(AsyncGenerator): + async def asend(self, value): return value + async def athrow(self, *args): raise ValueError + + with self.assertRaises(ValueError): + run_async(FailOnClose().aclose()) + + class IgnoreGeneratorExit(AsyncGenerator): + async def asend(self, value): return value + async def athrow(self, *args): pass + + with self.assertRaises(RuntimeError): + run_async(IgnoreGeneratorExit().aclose()) def test_Sized(self): non_samples = [None, 42, 3.14, 1j, diff --git a/src/core/IronPython.StdLib/lib/test/test_grammar.py b/src/core/IronPython.StdLib/lib/test/test_grammar.py index fb18489cf..ac8d85a3c 100644 --- a/src/core/IronPython.StdLib/lib/test/test_grammar.py +++ b/src/core/IronPython.StdLib/lib/test/test_grammar.py @@ -1414,77 +1414,76 @@ def __imatmul__(self, o): m @= 42 self.assertEqual(m.other, 42) -# https://github.com/IronLanguages/ironpython3/issues/1428 -# def test_async_await(self): -# async def test(): -# def sum(): -# pass -# if 1: -# await someobj() -# -# self.assertEqual(test.__name__, 'test') -# self.assertTrue(bool(test.__code__.co_flags & inspect.CO_COROUTINE)) -# -# def decorator(func): -# setattr(func, '_marked', True) -# return func -# -# @decorator -# async def test2(): -# return 22 -# self.assertTrue(test2._marked) -# self.assertEqual(test2.__name__, 'test2') -# self.assertTrue(bool(test2.__code__.co_flags & inspect.CO_COROUTINE)) -# -# def test_async_for(self): -# class Done(Exception): pass -# -# class AIter: -# def __aiter__(self): -# return self -# async def __anext__(self): -# raise StopAsyncIteration -# -# async def foo(): -# async for i in AIter(): -# pass -# async for i, j in AIter(): -# pass -# async for i in AIter(): -# pass -# else: -# pass -# raise Done -# -# with self.assertRaises(Done): -# foo().send(None) -# -# def test_async_with(self): -# class Done(Exception): pass -# -# class manager: -# async def __aenter__(self): -# return (1, 2) -# async def __aexit__(self, *exc): -# return False -# -# async def foo(): -# async with manager(): -# pass -# async with manager() as x: -# pass -# async with manager() as (x, y): -# pass -# async with manager(), manager(): -# pass -# async with manager() as x, manager() as y: -# pass -# async with manager() as x, manager(): -# pass -# raise Done -# -# with self.assertRaises(Done): -# foo().send(None) + def test_async_await(self): + async def test(): + def sum(): + pass + if 1: + await someobj() + + self.assertEqual(test.__name__, 'test') + self.assertTrue(bool(test.__code__.co_flags & inspect.CO_COROUTINE)) + + def decorator(func): + setattr(func, '_marked', True) + return func + + @decorator + async def test2(): + return 22 + self.assertTrue(test2._marked) + self.assertEqual(test2.__name__, 'test2') + self.assertTrue(bool(test2.__code__.co_flags & inspect.CO_COROUTINE)) + + def test_async_for(self): + class Done(Exception): pass + + class AIter: + def __aiter__(self): + return self + async def __anext__(self): + raise StopAsyncIteration + + async def foo(): + async for i in AIter(): + pass + async for i, j in AIter(): + pass + async for i in AIter(): + pass + else: + pass + raise Done + + with self.assertRaises(Done): + foo().send(None) + + def test_async_with(self): + class Done(Exception): pass + + class manager: + async def __aenter__(self): + return (1, 2) + async def __aexit__(self, *exc): + return False + + async def foo(): + async with manager(): + pass + async with manager() as x: + pass + async with manager() as (x, y): + pass + async with manager(), manager(): + pass + async with manager() as x, manager() as y: + pass + async with manager() as x, manager(): + pass + raise Done + + with self.assertRaises(Done): + foo().send(None) if __name__ == '__main__': diff --git a/src/core/IronPython.StdLib/lib/test/test_types.py b/src/core/IronPython.StdLib/lib/test/test_types.py index fa935c0ca..73fbdbf9b 100644 --- a/src/core/IronPython.StdLib/lib/test/test_types.py +++ b/src/core/IronPython.StdLib/lib/test/test_types.py @@ -1264,8 +1264,7 @@ def test_async_def(self): # Test that types.coroutine passes 'async def' coroutines # without modification -# https://github.com/IronLanguages/ironpython3/issues/1428 -# async def foo(): pass + async def foo(): pass foo_code = foo.__code__ foo_flags = foo.__code__.co_flags decorated_foo = types.coroutine(foo) @@ -1456,9 +1455,8 @@ def foo(): return Generator('spam') wrapper = foo() self.assertIsInstance(wrapper, types._GeneratorWrapper) -# https://github.com/IronLanguages/ironpython3/issues/1428 -# async def corofunc(): -# return await foo() + 100 + async def corofunc(): + return await foo() + 100 coro = corofunc() self.assertEqual(coro.send(None), 'spam') diff --git a/src/core/IronPython.StdLib/lib/types.py b/src/core/IronPython.StdLib/lib/types.py index b59b0681a..05ff22cbe 100644 --- a/src/core/IronPython.StdLib/lib/types.py +++ b/src/core/IronPython.StdLib/lib/types.py @@ -19,16 +19,15 @@ def _g(): yield 1 GeneratorType = type(_g()) -# https://github.com/IronLanguages/ironpython3/issues/1428 -# async def _c(): pass -# _c = _c() -# CoroutineType = type(_c) -# _c.close() # Prevent ResourceWarning -# -# async def _ag(): -# yield -# _ag = _ag() -# AsyncGeneratorType = type(_ag) +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 @@ -51,8 +50,8 @@ def _m(self): pass GetSetDescriptorType = type(FunctionType.__code__) MemberDescriptorType = type(ModuleType.__dict__["__dict__"]) # ironpython: type(FunctionType.__globals__) is getset_descriptor -del sys, _f, _g, _C, # Not for export - #_c, # https://github.com/IronLanguages/ironpython3/issues/1428 +del sys, _f, _g, _C, _c, # Not for export + # Provide a PEP 3115 compliant mechanism for class creation def new_class(name, bases=(), kwds=None, exec_body=None): From ab524316c1ecd0ea32620c1a5b1983f13ec966c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Fri, 13 Mar 2026 18:13:29 -0400 Subject: [PATCH 2/7] Fix failing tests --- src/core/IronPython.StdLib/lib/types.py | 1 + tests/suite/test_types_stdlib.py | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/core/IronPython.StdLib/lib/types.py b/src/core/IronPython.StdLib/lib/types.py index 05ff22cbe..84bb5514d 100644 --- a/src/core/IronPython.StdLib/lib/types.py +++ b/src/core/IronPython.StdLib/lib/types.py @@ -227,6 +227,7 @@ def coroutine(func): # Check if 'func' is a generator function. # (0x20 == CO_GENERATOR) if co_flags & 0x20: + return func # ironpython: todo figure this out # TODO: Implement this in C. co = func.__code__ func.__code__ = CodeType( diff --git a/tests/suite/test_types_stdlib.py b/tests/suite/test_types_stdlib.py index b6c41c32e..916c00e40 100644 --- a/tests/suite/test_types_stdlib.py +++ b/tests/suite/test_types_stdlib.py @@ -17,16 +17,9 @@ def load_tests(loader, standard_tests, pattern): failing_tests = [ test.test_types.ClassCreationTests('test_bad___prepare__'), # AssertionError test.test_types.ClassCreationTests('test_one_argument_type'), # AssertionError: TypeError not raised - test.test_types.CoroutineTests('test_async_def'), # https://github.com/IronLanguages/ironpython3/issues/98 - test.test_types.CoroutineTests('test_duck_coro'), # https://github.com/IronLanguages/ironpython3/issues/98 - test.test_types.CoroutineTests('test_duck_corogen'), # https://github.com/IronLanguages/ironpython3/issues/98 - test.test_types.CoroutineTests('test_duck_functional_gen'), # https://github.com/IronLanguages/ironpython3/issues/98 - test.test_types.CoroutineTests('test_duck_gen'), # https://github.com/IronLanguages/ironpython3/issues/98 test.test_types.CoroutineTests('test_gen'), # https://github.com/IronLanguages/ironpython3/issues/98 test.test_types.CoroutineTests('test_genfunc'), # https://github.com/IronLanguages/ironpython3/issues/98 - test.test_types.CoroutineTests('test_non_gen_values'), # https://github.com/IronLanguages/ironpython3/issues/98 test.test_types.CoroutineTests('test_returning_itercoro'), # https://github.com/IronLanguages/ironpython3/issues/98 - test.test_types.CoroutineTests('test_wrapper_object'), # https://github.com/IronLanguages/ironpython3/issues/98 test.test_types.MappingProxyTests('test_chainmap'), # TypeError: expected dict, got Object_1$1 test.test_types.MappingProxyTests('test_constructor'), # TypeError: expected dict, got Object_1$1 test.test_types.MappingProxyTests('test_customdict'), # AssertionError: False is not true From d0b5b3612a33ab7ac69c760d72d2d299da1830c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Fri, 13 Mar 2026 19:21:12 -0400 Subject: [PATCH 3/7] Disable failing grammar --- src/core/IronPython.StdLib/lib/test/test_grammar.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/IronPython.StdLib/lib/test/test_grammar.py b/src/core/IronPython.StdLib/lib/test/test_grammar.py index ac8d85a3c..e89748067 100644 --- a/src/core/IronPython.StdLib/lib/test/test_grammar.py +++ b/src/core/IronPython.StdLib/lib/test/test_grammar.py @@ -1474,12 +1474,13 @@ async def foo(): pass async with manager() as (x, y): pass - async with manager(), manager(): - pass - async with manager() as x, manager() as y: - pass - async with manager() as x, manager(): - pass + # ironpython: todo implement this + #async with manager(), manager(): + # pass + #async with manager() as x, manager() as y: + # pass + #async with manager() as x, manager(): + # pass raise Done with self.assertRaises(Done): From 41691f9faba951b6a02895af85006e05f6d0bf8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Fri, 13 Mar 2026 20:56:57 -0400 Subject: [PATCH 4/7] Change FunctionAttributes.Coroutine to 0x80 --- src/core/IronPython/Runtime/FunctionAttributes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/IronPython/Runtime/FunctionAttributes.cs b/src/core/IronPython/Runtime/FunctionAttributes.cs index 8e7f667e5..5f2a7196c 100644 --- a/src/core/IronPython/Runtime/FunctionAttributes.cs +++ b/src/core/IronPython/Runtime/FunctionAttributes.cs @@ -25,7 +25,7 @@ public enum FunctionAttributes { /// /// Set if the function is a coroutine (async def). /// - Coroutine = 0x100, + Coroutine = 0x80, /// /// IronPython specific: Set if the function includes nested exception handling and therefore can alter /// sys.exc_info(). From 37dbf6f5a009c9bc2792c96d2e51f3b19d9facbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Fri, 13 Mar 2026 21:25:24 -0400 Subject: [PATCH 5/7] Fix failing test --- tests/suite/test_socket_stdlib.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/suite/test_socket_stdlib.py b/tests/suite/test_socket_stdlib.py index a35eed082..73e53e641 100644 --- a/tests/suite/test_socket_stdlib.py +++ b/tests/suite/test_socket_stdlib.py @@ -37,7 +37,7 @@ def load_tests(loader, standard_tests, pattern): test.test_socket.TestSocketSharing('testTypes'), # https://github.com/IronLanguages/ironpython3/issues/1226 test.test_socket.UnbufferedFileObjectClassTestCase('testSmallReadNonBlocking'), # TODO: figure out ] - if is_linux or (is_osx and net_version < (10, 0)): + if is_mono or (is_osx and net_version < (10, 0)): failing_tests += [ test.test_socket.NonBlockingTCPTests('testRecv'), # TODO: figure out ] @@ -63,10 +63,6 @@ def load_tests(loader, standard_tests, pattern): skip_tests += [ test.test_socket.NonBlockingTCPTests('testAccept') ] - if is_osx: # TODO: figure out - skip_tests += [ - test.test_socket.NonBlockingTCPTests('testRecv'), - ] return generate_suite(tests, failing_tests, skip_tests) From a1c9eb26254e4f5d508002857433f2d8c1d6a36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Fri, 13 Mar 2026 21:48:22 -0400 Subject: [PATCH 6/7] Uncomment code in typing --- .../IronPython.StdLib/lib/test/test_typing.py | 4 +- src/core/IronPython.StdLib/lib/typing.py | 48 +++++++++---------- src/core/IronPython/Compiler/Parser.cs | 2 +- tests/suite/test_typing_stdlib.py | 7 +++ 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/core/IronPython.StdLib/lib/test/test_typing.py b/src/core/IronPython.StdLib/lib/test/test_typing.py index ba46420a9..4843d6faf 100644 --- a/src/core/IronPython.StdLib/lib/test/test_typing.py +++ b/src/core/IronPython.StdLib/lib/test/test_typing.py @@ -37,7 +37,7 @@ from test import mod_generics_cache -PY36 = sys.version_info[:2] >= (3, 6) and not sys.implementation.name == 'ironpython' # https://github.com/IronLanguages/ironpython3/issues/98 +PY36 = sys.version_info[:2] >= (3, 6) class BaseTestCase(TestCase): @@ -1647,7 +1647,7 @@ def blah(): blah() -ASYNCIO = sys.version_info[:2] >= (3, 5) and not sys.implementation.name == 'ironpython' # https://github.com/IronLanguages/ironpython3/issues/98 +ASYNCIO = sys.version_info[:2] >= (3, 5) ASYNCIO_TESTS = """ import asyncio diff --git a/src/core/IronPython.StdLib/lib/typing.py b/src/core/IronPython.StdLib/lib/typing.py index 5744601c8..f2b6aaf3a 100644 --- a/src/core/IronPython.StdLib/lib/typing.py +++ b/src/core/IronPython.StdLib/lib/typing.py @@ -1975,30 +1975,30 @@ class AsyncContextManager(Generic[T_co], __slots__ = () __all__.append('AsyncContextManager') -#elif sys.version_info[:2] >= (3, 5): -# exec(""" -#class AsyncContextManager(Generic[T_co]): -# __slots__ = () -# -# async def __aenter__(self): -# return self -# -# @abc.abstractmethod -# async def __aexit__(self, exc_type, exc_value, traceback): -# return None -# -# @classmethod -# def __subclasshook__(cls, C): -# if cls is AsyncContextManager: -# if sys.version_info[:2] >= (3, 6): -# return _collections_abc._check_methods(C, "__aenter__", "__aexit__") -# if (any("__aenter__" in B.__dict__ for B in C.__mro__) and -# any("__aexit__" in B.__dict__ for B in C.__mro__)): -# return True -# return NotImplemented -# -#__all__.append('AsyncContextManager') -#""") +elif sys.version_info[:2] >= (3, 5): + exec(""" +class AsyncContextManager(Generic[T_co]): + __slots__ = () + + async def __aenter__(self): + return self + + @abc.abstractmethod + async def __aexit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncContextManager: + if sys.version_info[:2] >= (3, 6): + return _collections_abc._check_methods(C, "__aenter__", "__aexit__") + if (any("__aenter__" in B.__dict__ for B in C.__mro__) and + any("__aexit__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + +__all__.append('AsyncContextManager') +""") class Dict(dict, MutableMapping[KT, VT], extra=dict): diff --git a/src/core/IronPython/Compiler/Parser.cs b/src/core/IronPython/Compiler/Parser.cs index 8853fe6f9..5033b6cf3 100644 --- a/src/core/IronPython/Compiler/Parser.cs +++ b/src/core/IronPython/Compiler/Parser.cs @@ -1457,8 +1457,8 @@ private WithItem ParseWithItem() { // async_stmt: 'async' (funcdef | with_stmt | for_stmt) private Statement ParseAsyncStmt() { - var start = GetStart(); Eat(TokenKind.KeywordAsync); + var start = GetStart(); switch (PeekToken().Kind) { case TokenKind.KeywordDef: diff --git a/tests/suite/test_typing_stdlib.py b/tests/suite/test_typing_stdlib.py index ca9a7ca90..3993ce78e 100644 --- a/tests/suite/test_typing_stdlib.py +++ b/tests/suite/test_typing_stdlib.py @@ -16,10 +16,17 @@ def load_tests(loader, standard_tests, pattern): if is_ironpython: failing_tests = [ test.test_typing.GenericTests('test_generic_hashes'), # https://github.com/IronLanguages/ironpython3/issues/30 + test.test_typing.GenericTests('test_init_subclass'), # AttributeError: 'GenericMeta' object has no attribute 'attr' test.test_typing.GenericTests('test_repr_2'), # https://github.com/IronLanguages/ironpython3/issues/30 test.test_typing.GenericTests('test_parameterized_slots_dict'), # TypeError: slots must be one string or a list of strings test.test_typing.GenericTests('test_type_erasure_special'), # TypeError: Parameterized Tuple cannot be used with isinstance(). + test.test_typing.GetTypeHintTests('test_get_type_hints_ClassVar'), # AssertionError + test.test_typing.GetTypeHintTests('test_get_type_hints_classes'), # AssertionError + test.test_typing.GetTypeHintTests('test_get_type_hints_modules'), # AssertionError test.test_typing.IOTests('test_io_submodule'), # ImportError: Cannot import name __name__ + test.test_typing.NamedTupleTests('test_annotation_usage'), # TypeError: __new__() takes exactly 1 argument (3 given) + test.test_typing.NamedTupleTests('test_annotation_usage_with_default'), # TypeError: __new__() takes exactly 1 argument (2 given) + test.test_typing.NamedTupleTests('test_annotation_usage_with_methods'), # TypeError: __new__() takes exactly 1 argument (2 given) test.test_typing.RETests('test_basics'), # TypeError: issubclass(): _TypeAlias is not a class nor a tuple of classes test.test_typing.RETests('test_cannot_subclass'), # AssertionError test.test_typing.RETests('test_re_submodule'), # ImportError: Cannot import name __name__ From 99473f7496b021bba2bb687624d21e31fc7adc8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Fri, 13 Mar 2026 22:16:36 -0400 Subject: [PATCH 7/7] Disable failing test --- tests/suite/test_socket_stdlib.py | 2 +- tests/suite/test_types_stdlib.py | 2 +- tests/suite/test_typing_stdlib.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/suite/test_socket_stdlib.py b/tests/suite/test_socket_stdlib.py index 73e53e641..cd5360a79 100644 --- a/tests/suite/test_socket_stdlib.py +++ b/tests/suite/test_socket_stdlib.py @@ -37,7 +37,7 @@ def load_tests(loader, standard_tests, pattern): test.test_socket.TestSocketSharing('testTypes'), # https://github.com/IronLanguages/ironpython3/issues/1226 test.test_socket.UnbufferedFileObjectClassTestCase('testSmallReadNonBlocking'), # TODO: figure out ] - if is_mono or (is_osx and net_version < (10, 0)): + if is_mono: failing_tests += [ test.test_socket.NonBlockingTCPTests('testRecv'), # TODO: figure out ] diff --git a/tests/suite/test_types_stdlib.py b/tests/suite/test_types_stdlib.py index 916c00e40..43db83ef6 100644 --- a/tests/suite/test_types_stdlib.py +++ b/tests/suite/test_types_stdlib.py @@ -6,7 +6,7 @@ ## Run selected tests from test_types from StdLib ## -from iptest import is_ironpython, generate_suite, run_test, is_linux, is_netcoreapp21 +from iptest import is_ironpython, generate_suite, run_test import test.test_types diff --git a/tests/suite/test_typing_stdlib.py b/tests/suite/test_typing_stdlib.py index 3993ce78e..a01ad5245 100644 --- a/tests/suite/test_typing_stdlib.py +++ b/tests/suite/test_typing_stdlib.py @@ -45,7 +45,9 @@ def load_tests(loader, standard_tests, pattern): test.test_typing.GenericTests('test_type_erasure'), ] - skip_tests = [] + skip_tests = [ + test.test_typing.NamedTupleTests('test_namedtuple_keyword_usage'), # AssertionError + ] return generate_suite(tests, failing_tests, skip_tests)