Author: Ronan Lamy <[email protected]>
Branch: py3.6-asyncgen
Changeset: r97718:c2f8aa002dda
Date: 2019-10-03 18:14 +0100
http://bitbucket.org/pypy/pypy/changeset/c2f8aa002dda/

Log:    Allow YIELD_FROM to run even if the generator owning the frame died

diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -155,7 +155,7 @@
         if w_yf is not None:
             self.set_delegate(None)
             try:
-                self.next_yield_from(frame, w_yf, w_arg_or_err)
+                frame.next_yield_from(w_yf, w_arg_or_err)
             except OperationError as operr:
                 operr.record_context(space, space.getexecutioncontext())
                 return frame.handle_generator_error(operr)
@@ -178,43 +178,27 @@
             return r_uint(0)
 
     def get_delegate(self):
+        if self.frame is None:
+            return None
         return self.frame.w_yielding_from
 
+    def descr_delegate(self, space):
+        w_yf = self.get_delegate()
+        if w_yf is None:
+            return space.w_None
+        return w_yf
+
     def set_delegate(self, w_delegate):
         self.frame.w_yielding_from = w_delegate
 
+
+
     def next_yield_from(self, frame, w_yf, w_inputvalue_or_err):
         """Fetch the next item of the current 'yield from', push it on
         the frame stack, and raises Yield.  If there isn't one, push
         w_stopiteration_value and returns.  May also just raise.
         """
-        space = self.space
-        try:
-            if isinstance(w_yf, GeneratorOrCoroutine):
-                w_retval = w_yf.send_ex(w_inputvalue_or_err)
-            elif isinstance(w_yf, AsyncGenASend):   # performance only
-                w_retval = w_yf.do_send(w_inputvalue_or_err)
-            elif space.is_w(w_inputvalue_or_err, space.w_None):
-                w_retval = space.next(w_yf)
-            else:
-                w_retval = delegate_to_nongen(space, w_yf, w_inputvalue_or_err)
-        except OperationError as e:
-            if not e.match(space, space.w_StopIteration):
-                raise
-            frame._report_stopiteration_sometimes(w_yf, e)
-            try:
-                w_stop_value = space.getattr(e.get_w_value(space),
-                                             space.newtext("value"))
-            except OperationError as e:
-                if not e.match(space, space.w_AttributeError):
-                    raise
-                w_stop_value = space.w_None
-            frame.pushvalue(w_stop_value)
-            return
-        else:
-            frame.pushvalue(w_retval)
-            self.set_delegate(w_yf)
-            raise Yield
+        frame.next_yield_from(w_yf, w_inputvalue_or_err)
 
     def _leak_stopiteration(self, e):
         # Check for __future__ generator_stop and conditionally turn
@@ -493,27 +477,6 @@
         else:
             space.call_function(w_close)
 
-def delegate_to_nongen(space, w_yf, w_inputvalue_or_err):
-    # invoke a "send" or "throw" by method name to a non-generator w_yf
-    if isinstance(w_inputvalue_or_err, SApplicationException):
-        operr = w_inputvalue_or_err.operr
-        try:
-            w_meth = space.getattr(w_yf, space.newtext("throw"))
-        except OperationError as e:
-            if not e.match(space, space.w_AttributeError):
-                raise
-            raise operr
-        # bah, CPython calls here with the exact same arguments as
-        # originally passed to throw().  In our case it is far removed.
-        # Let's hope nobody will complain...
-        operr.normalize_exception(space)
-        w_exc = operr.w_type
-        w_val = operr.get_w_value(space)
-        w_tb  = operr.get_w_traceback(space)
-        return space.call_function(w_meth, w_exc, w_val, w_tb)
-    else:
-        return space.call_method(w_yf, "send", w_inputvalue_or_err)
-
 def gen_is_coroutine(w_obj):
     return (isinstance(w_obj, GeneratorIterator) and
             (w_obj.pycode.co_flags & consts.CO_ITERABLE_COROUTINE) != 0)
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -1113,24 +1113,51 @@
             self.pushvalue(w_value)
         raise Yield
 
+    def next_yield_from(self, w_yf, w_inputvalue_or_err):
+        """Fetch the next item of the current 'yield from', push it on
+        the frame stack, and raises Yield.  If there isn't one, push
+        w_stopiteration_value and returns.  May also just raise.
+        """
+        from pypy.interpreter.generator import (
+            GeneratorOrCoroutine, AsyncGenASend)
+        space = self.space
+        try:
+            if isinstance(w_yf, GeneratorOrCoroutine):
+                w_retval = w_yf.send_ex(w_inputvalue_or_err)
+            elif isinstance(w_yf, AsyncGenASend):   # performance only
+                w_retval = w_yf.do_send(w_inputvalue_or_err)
+            elif space.is_w(w_inputvalue_or_err, space.w_None):
+                w_retval = space.next(w_yf)
+            else:
+                w_retval = delegate_to_nongen(space, w_yf, w_inputvalue_or_err)
+        except OperationError as e:
+            if not e.match(space, space.w_StopIteration):
+                raise
+            self._report_stopiteration_sometimes(w_yf, e)
+            try:
+                w_stop_value = space.getattr(e.get_w_value(space),
+                                             space.newtext("value"))
+            except OperationError as e:
+                if not e.match(space, space.w_AttributeError):
+                    raise
+                w_stop_value = space.w_None
+            self.pushvalue(w_stop_value)
+            return
+        else:
+            self.pushvalue(w_retval)
+            self.w_yielding_from = w_yf
+            raise Yield
+
     def YIELD_FROM(self, oparg, next_instr):
         # Unlike CPython, we handle this not by repeating the same
         # bytecode over and over until the inner iterator is exhausted.
         # Instead, we directly set the generator's w_yielded_from.
         # This asks generator.resume_execute_frame() to exhaust that
         # sub-iterable first before continuing on the next bytecode.
-        in_generator = self.get_generator()
-        if in_generator is None:
-            # Issue #2786: rare case involving __del__ methods.
-            # XXX This is a workaround, not a proper solution.
-            raise oefmt(self.space.w_RuntimeError,
-                        "PyPy limitation: cannot use 'yield from' or 'await' "
-                        "in a generator/coroutine that has been partially "
-                        "deallocated already, typically seen via __del__")
         w_inputvalue = self.popvalue()    # that's always w_None, actually
         w_gen = self.popvalue()
         #
-        in_generator.next_yield_from(self, w_gen, w_inputvalue)
+        self.next_yield_from(w_gen, w_inputvalue)
         # Common case: the call above raises Yield.
         # If instead the iterable is empty, next_yield_from() pushed the
         # final result and returns.  In that case, we can just continue
@@ -1694,6 +1721,27 @@
         else:
             self.MISSING_OPCODE(oparg, next_instr)
 
+def delegate_to_nongen(space, w_yf, w_inputvalue_or_err):
+    # invoke a "send" or "throw" by method name to a non-generator w_yf
+    if isinstance(w_inputvalue_or_err, SApplicationException):
+        operr = w_inputvalue_or_err.operr
+        try:
+            w_meth = space.getattr(w_yf, space.newtext("throw"))
+        except OperationError as e:
+            if not e.match(space, space.w_AttributeError):
+                raise
+            raise operr
+        # bah, CPython calls here with the exact same arguments as
+        # originally passed to throw().  In our case it is far removed.
+        # Let's hope nobody will complain...
+        operr.normalize_exception(space)
+        w_exc = operr.w_type
+        w_val = operr.get_w_value(space)
+        w_tb = operr.get_w_traceback(space)
+        return space.call_function(w_meth, w_exc, w_val, w_tb)
+    else:
+        return space.call_method(w_yf, "send", w_inputvalue_or_err)
+
 
 ### ____________________________________________________________ ###
 
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -838,7 +838,7 @@
     gi_running = interp_attrproperty('running', cls=GeneratorIterator, 
wrapfn="newbool"),
     gi_frame   = GetSetProperty(GeneratorIterator.descr_gicr_frame),
     gi_code    = interp_attrproperty_w('pycode', cls=GeneratorIterator),
-    gi_yieldfrom=GetSetProperty(GeneratorIterator.get_delegate),
+    gi_yieldfrom=GetSetProperty(GeneratorIterator.descr_delegate),
     __name__   = GetSetProperty(GeneratorIterator.descr__name__,
                                 GeneratorIterator.descr_set__name__),
     __qualname__ = GetSetProperty(GeneratorIterator.descr__qualname__,
@@ -862,7 +862,7 @@
     cr_running = interp_attrproperty('running', cls=Coroutine, 
wrapfn="newbool"),
     cr_frame   = GetSetProperty(Coroutine.descr_gicr_frame),
     cr_code    = interp_attrproperty_w('pycode', cls=Coroutine),
-    cr_await=GetSetProperty(Coroutine.get_delegate),
+    cr_await=GetSetProperty(Coroutine.descr_delegate),
     __name__   = GetSetProperty(Coroutine.descr__name__,
                                 Coroutine.descr_set__name__,
                                 doc="name of the coroutine"),
@@ -890,7 +890,7 @@
     ag_running = interp_attrproperty('running', cls=AsyncGenerator, 
wrapfn="newbool"),
     ag_frame   = GetSetProperty(AsyncGenerator.descr_gicr_frame),
     ag_code    = interp_attrproperty_w('pycode', cls=AsyncGenerator),
-    ag_await=GetSetProperty(AsyncGenerator.get_delegate),
+    ag_await=GetSetProperty(AsyncGenerator.descr_delegate),
     __name__   = GetSetProperty(AsyncGenerator.descr__name__,
                                 AsyncGenerator.descr_set__name__,
                                 doc="name of the async generator"),
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to