From fb1a4ad55d3d6945fa3d671472659a55ab210ffb Mon Sep 17 00:00:00 2001 From: "quantconnect-kronos[bot]" <346399058+quantconnect-kronos[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 13:48:33 +0000 Subject: [PATCH] Keep the Python error when it was set without a value An error set with a type but no value, as PyErr_SetNone does (CPython's own next() among others, before 3.12), made FetchCurrentOrNull throw a NullReferenceException while converting it, losing the Python error. The value now comes from normalizing the error when it is missing. Co-Authored-By: Claude Opus 5.5 --- src/embed_tests/TestPythonException.cs | 13 +++++++++++++ src/runtime/PythonException.cs | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index a5c2353a6..9a3343460 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -113,6 +113,19 @@ public void InnerIsEmptyWithNoCause() Assert.IsNull(ex.InnerException); } + [Test] + public void TestErrorSetWithoutValue() + { + using var _ = Py.GIL(); + // what PyErr_SetNone does, as C extensions use it: the error's value stays NULL until it is normalized + Runtime.Runtime.PyErr_SetObject(Exceptions.ValueError.Reference, BorrowedReference.Null); + + var ex = Assert.Throws(() => PythonException.ThrowLastAsClrException()); + + Assert.AreEqual("ValueError", ex.Type.Name); + Assert.IsTrue(Runtime.Runtime.PyErr_Occurred().IsNull); + } + [Test] public void TestPythonExceptionFormat() { diff --git a/src/runtime/PythonException.cs b/src/runtime/PythonException.cs index 129668239..9f26e3a8d 100644 --- a/src/runtime/PythonException.cs +++ b/src/runtime/PythonException.cs @@ -111,10 +111,11 @@ internal static PythonException FetchCurrentRaw() try { - var normalizedValue = new NewReference(value.Borrow()); + // an error set without a value, as PyErr_SetNone does, only gets one once normalized + var normalizedValue = new NewReference(value.BorrowNullable(), canBeNull: true); Runtime.PyErr_NormalizeException(type: ref type, val: ref normalizedValue, tb: ref traceback); - return FromPyErr(typeRef: type.Borrow(), valRef: value.Borrow(), nValRef: normalizedValue.Borrow(), tbRef: traceback.BorrowNullable(), out dispatchInfo); + return FromPyErr(typeRef: type.Borrow(), valRef: value.IsNull() ? normalizedValue.Borrow() : value.Borrow(), nValRef: normalizedValue.Borrow(), tbRef: traceback.BorrowNullable(), out dispatchInfo); } finally {