From dac9d4767175541aaaaf0e2f873322f5f1db3b0c Mon Sep 17 00:00:00 2001
From: Damien George <damien.p.george@gmail.com>
Date: Thu, 21 Feb 2019 14:07:44 +1100
Subject: [PATCH] py/objgenerator: Fix handling of None passed as 2nd arg to
 throw().

Fixes issue #4527.
---
 py/objgenerator.c                    | 17 ++++++++++++++++-
 tests/basics/gen_yield_from_throw.py | 22 ++++++++++++++++++++--
 tests/basics/generator_throw.py      | 14 ++++++++++++--
 3 files changed, 48 insertions(+), 5 deletions(-)

diff --git a/py/objgenerator.c b/py/objgenerator.c
index c306bb0c5..92bafea6a 100644
--- a/py/objgenerator.c
+++ b/py/objgenerator.c
@@ -252,7 +252,22 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send);
 
 STATIC mp_obj_t gen_instance_close(mp_obj_t self_in);
 STATIC mp_obj_t gen_instance_throw(size_t n_args, const mp_obj_t *args) {
-    mp_obj_t exc = (n_args == 2) ? args[1] : args[2];
+    // The signature of this function is: throw(type[, value[, traceback]])
+    // CPython will pass all given arguments through the call chain and process them
+    // at the point they are used (native generators will handle them differently to
+    // user-defined generators with a throw() method).  To save passing multiple
+    // values, MicroPython instead does partial processing here to reduce it down to
+    // one argument and passes that through:
+    // - if only args[1] is given, or args[2] is given but is None, args[1] is
+    //   passed through (in the standard case it is an exception class or instance)
+    // - if args[2] is given and not None it is passed through (in the standard
+    //   case it would be an exception instance and args[1] its corresponding class)
+    // - args[3] is always ignored
+
+    mp_obj_t exc = args[1];
+    if (n_args > 2 && args[2] != mp_const_none) {
+        exc = args[2];
+    }
 
     mp_obj_t ret = gen_resume_and_raise(args[0], mp_const_none, exc);
     if (ret == MP_OBJ_STOP_ITERATION) {
diff --git a/tests/basics/gen_yield_from_throw.py b/tests/basics/gen_yield_from_throw.py
index 703158f4d..804c53dda 100644
--- a/tests/basics/gen_yield_from_throw.py
+++ b/tests/basics/gen_yield_from_throw.py
@@ -1,8 +1,8 @@
 def gen():
     try:
         yield 1
-    except ValueError:
-        print("got ValueError from upstream!")
+    except ValueError as e:
+        print("got ValueError from upstream!", repr(e.args))
     yield "str1"
     raise TypeError
 
@@ -16,3 +16,21 @@ try:
     print(next(g))
 except TypeError:
     print("got TypeError from downstream!")
+
+# passing None as second argument to throw
+g = gen2()
+print(next(g))
+print(g.throw(ValueError, None))
+try:
+    print(next(g))
+except TypeError:
+    print("got TypeError from downstream!")
+
+# passing an exception instance as second argument to throw
+g = gen2()
+print(next(g))
+print(g.throw(ValueError, ValueError(123)))
+try:
+    print(next(g))
+except TypeError:
+    print("got TypeError from downstream!")
diff --git a/tests/basics/generator_throw.py b/tests/basics/generator_throw.py
index bf5ff33a2..1b43c125e 100644
--- a/tests/basics/generator_throw.py
+++ b/tests/basics/generator_throw.py
@@ -28,8 +28,8 @@ except StopIteration:
 def gen():
     try:
         yield 123
-    except GeneratorExit:
-        print('GeneratorExit')
+    except GeneratorExit as e:
+        print('GeneratorExit', repr(e.args))
     yield 456
         
 # thrown a class
@@ -41,3 +41,13 @@ print(g.throw(GeneratorExit))
 g = gen()
 print(next(g))
 print(g.throw(GeneratorExit()))
+
+# thrown an instance with None as second arg
+g = gen()
+print(next(g))
+print(g.throw(GeneratorExit(), None))
+
+# thrown a class and instance
+g = gen()
+print(next(g))
+print(g.throw(GeneratorExit, GeneratorExit(123)))
-- 
GitLab