]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-39048: Look up __aenter__ before __aexit__ in async with (GH-17609)
authorGéry Ogam <gery.ogam@gmail.com>
Tue, 14 Jan 2020 11:58:29 +0000 (12:58 +0100)
committerNick Coghlan <ncoghlan@gmail.com>
Tue, 14 Jan 2020 11:58:29 +0000 (21:58 +1000)
* 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
Lib/test/test_coroutines.py
Misc/ACKS
Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst [new file with mode: 0644]
Python/ceval.c

index 564d6cc42136daeea781573141dfe2e9930dd34f..e2f44a55b180b1c9c3e6902d31f7956222387b67 100644 (file)
@@ -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
 
index 208b5c2ccf5cd26167c3c21c892236cc989d8ede..8d1e0692a24221bf024b061cebfff9fe9144ecfd 100644 (file)
@@ -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,
index d3e683d4a085fdff444b72da7d2f26c47a6fc8af..3e45d5d0f7f29d5d5a426bee47a1c6accfa357f1 100644 (file)
--- 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 (file)
index 0000000..1179ef4
--- /dev/null
@@ -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.
index 096645aeebfb9922f56f1037bc7103c494b5a4fd..5e586589e961894d27065746bcac7f274531ee45 100644 (file)
@@ -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;