From 1d1b97ae643dd8b22d87785ed7bd2599c6c8dc8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Tue, 14 Jan 2020 12:58:29 +0100 Subject: bpo-39048: Look up __aenter__ before __aexit__ in async with (GH-17609) * Reorder the __aenter__ and __aexit__ checks for async with * Add assertions for async with body being skipped * Swap __aexit__ and __aenter__ loading in the documentation --- Doc/reference/compound_stmts.rst | 2 +- Lib/test/test_coroutines.py | 20 +++++++++++--------- Misc/ACKS | 1 + .../2020-01-13-14-45-22.bpo-39048.iPsj81.rst | 4 ++++ Python/ceval.c | 19 ++++++++++--------- 5 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 564d6cc..e2f44a5 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -844,8 +844,8 @@ The following code:: is semantically equivalent to:: manager = (EXPRESSION) - aexit = type(manager).__aexit__ aenter = type(manager).__aenter__ + aexit = type(manager).__aexit__ value = await aenter(manager) hit_except = False diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index 208b5c2..8d1e069 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -1203,39 +1203,41 @@ class CoroutineTest(unittest.TestCase): def __aenter__(self): pass + body_executed = False async def foo(): async with CM(): - pass + body_executed = True with self.assertRaisesRegex(AttributeError, '__aexit__'): run_async(foo()) + self.assertFalse(body_executed) def test_with_3(self): class CM: def __aexit__(self): pass + body_executed = False async def foo(): async with CM(): - pass + body_executed = True with self.assertRaisesRegex(AttributeError, '__aenter__'): run_async(foo()) + self.assertFalse(body_executed) def test_with_4(self): class CM: - def __enter__(self): - pass - - def __exit__(self): - pass + pass + body_executed = False async def foo(): async with CM(): - pass + body_executed = True - with self.assertRaisesRegex(AttributeError, '__aexit__'): + with self.assertRaisesRegex(AttributeError, '__aenter__'): run_async(foo()) + self.assertFalse(body_executed) def test_with_5(self): # While this test doesn't make a lot of sense, diff --git a/Misc/ACKS b/Misc/ACKS index d3e683d..3e45d5d 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1219,6 +1219,7 @@ Elena Oat Jon Oberheide Milan Oberkirch Pascal Oberndoerfer +Géry Ogam Jeffrey Ollie Adam Olsen Bryan Olson diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst b/Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst new file mode 100644 index 0000000..1179ef4 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst @@ -0,0 +1,4 @@ +Improve the displayed error message when incorrect types are passed to ``async +with`` statements by looking up the :meth:`__aenter__` special method before +the :meth:`__aexit__` special method when entering an asynchronous context +manager. Patch by Géry Ogam. diff --git a/Python/ceval.c b/Python/ceval.c index 096645a..5e58658 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3230,20 +3230,21 @@ main_loop: } case TARGET(BEFORE_ASYNC_WITH): { - _Py_IDENTIFIER(__aexit__); _Py_IDENTIFIER(__aenter__); - + _Py_IDENTIFIER(__aexit__); PyObject *mgr = TOP(); - PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__), - *enter; + PyObject *enter = special_lookup(tstate, mgr, &PyId___aenter__); PyObject *res; - if (exit == NULL) + if (enter == NULL) { + goto error; + } + PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__); + if (exit == NULL) { + Py_DECREF(enter); goto error; + } SET_TOP(exit); - enter = special_lookup(tstate, mgr, &PyId___aenter__); Py_DECREF(mgr); - if (enter == NULL) - goto error; res = _PyObject_CallNoArg(enter); Py_DECREF(enter); if (res == NULL) @@ -3264,8 +3265,8 @@ main_loop: } case TARGET(SETUP_WITH): { - _Py_IDENTIFIER(__exit__); _Py_IDENTIFIER(__enter__); + _Py_IDENTIFIER(__exit__); PyObject *mgr = TOP(); PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__); PyObject *res; -- cgit v0.12