diff --git a/CHANGELOG.md b/CHANGELOG.md index c30b4c393..57aee39c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added `clr.GetClrType` (#432, #433) - Allowed passing `None` for nullable args (#460) - Added keyword arguments based on C# syntax for calling CPython methods (#461) +- Catches exceptions thrown in C# iterators (yield returns) and rethrows them in python (#475) - Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger (#443) ### Changed @@ -29,7 +30,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Fixed conversion of 'float' and 'double' values (#486) - Fixed 'clrmethod' for python 2 (#492) - Fixed double calling of constructor when deriving from .NET class (#495) -- Fixed `clr.GetClrType` when iterating over `System` members (#607) +- Fixed `clr.GetClrType` when iterating over `System` members (#607) - Fixed `LockRecursionException` when loading assemblies (#627) - Fixed errors breaking .NET Remoting on method invoke (#276) - Fixed PyObject.GetHashCode (#676) diff --git a/src/runtime/iterator.cs b/src/runtime/iterator.cs index c7c60ab19..f9cf10178 100644 --- a/src/runtime/iterator.cs +++ b/src/runtime/iterator.cs @@ -23,9 +23,21 @@ public Iterator(IEnumerator e) public static IntPtr tp_iternext(IntPtr ob) { var self = GetManagedObject(ob) as Iterator; - if (!self.iter.MoveNext()) + try { - Exceptions.SetError(Exceptions.StopIteration, Runtime.PyNone); + if (!self.iter.MoveNext()) + { + Exceptions.SetError(Exceptions.StopIteration, Runtime.PyNone); + return IntPtr.Zero; + } + } + catch (Exception e) + { + if (e.InnerException != null) + { + e = e.InnerException; + } + Exceptions.SetError(e); return IntPtr.Zero; } object item = self.iter.Current; diff --git a/src/testing/exceptiontest.cs b/src/testing/exceptiontest.cs index e4f683721..45acaa2cc 100644 --- a/src/testing/exceptiontest.cs +++ b/src/testing/exceptiontest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; namespace Python.Test { @@ -54,6 +56,13 @@ public static bool ThrowException() throw new OverflowException("error"); } + public static IEnumerable ThrowExceptionInIterator(Exception e) + { + yield return 1; + yield return 2; + throw e; + } + public static void ThrowChainedExceptions() { try diff --git a/src/tests/test_exceptions.py b/src/tests/test_exceptions.py index f47209f6d..c697290ee 100644 --- a/src/tests/test_exceptions.py +++ b/src/tests/test_exceptions.py @@ -345,3 +345,44 @@ def test_chained_exceptions(): assert exc.Message == msg assert exc.__cause__ == exc.InnerException exc = exc.__cause__ + +def test_iteration_exception(): + from Python.Test import ExceptionTest + from System import OverflowException + + exception = OverflowException("error") + + val = ExceptionTest.ThrowExceptionInIterator(exception).__iter__() + assert next(val) == 1 + assert next(val) == 2 + with pytest.raises(OverflowException) as cm: + next(val) + + exc = cm.value + + assert exc == exception + + # after exception is thrown iterator is no longer valid + with pytest.raises(StopIteration): + next(val) + + +def test_iteration_innerexception(): + from Python.Test import ExceptionTest + from System import OverflowException + + exception = System.Exception("message", OverflowException("error")) + + val = ExceptionTest.ThrowExceptionInIterator(exception).__iter__() + assert next(val) == 1 + assert next(val) == 2 + with pytest.raises(OverflowException) as cm: + next(val) + + exc = cm.value + + assert exc == exception.InnerException + + # after exception is thrown iterator is no longer valid + with pytest.raises(StopIteration): + next(val)