From 28a78ed6e9a77c6854be8269ad82cb2792ad88a8 Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Mon, 27 Jun 2022 11:47:57 +0200
Subject: [PATCH 01/16] Bump version to 3.0.0-rc1

---
 version.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/version.txt b/version.txt
index e16bcc8be..cf8213193 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-3.0.0-dev1
+3.0.0-rc1

From e84731f8e8834d3eeac2fa442ccb4d6848a1f9c5 Mon Sep 17 00:00:00 2001
From: Victor Nova <lost@losttech.software>
Date: Thu, 30 Jun 2022 15:24:00 -0700
Subject: [PATCH 02/16] implemented dynamic equality and inequality for
 PyObject instances

fixed unhandled Python errors during comparison attempts

fixes https://github.com/pythonnet/pythonnet/issues/1848
---
 CHANGELOG.md                        |  2 +
 src/embed_tests/dynamic.cs          | 22 +++++++++
 src/runtime/PythonTypes/PyObject.cs | 75 +++++++++++++++++++++++------
 src/runtime/Runtime.cs              | 25 ----------
 4 files changed, 83 insertions(+), 41 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52ee08484..9b5dd1816 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -45,6 +45,8 @@ details about the cause of the failure
      able to access members that are part of the implementation class, but not the
      interface.  Use the new `__implementation__` or `__raw_implementation__` properties to
      if you need to "downcast" to the implementation class.
+-   BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison
+     (previously was equivalent to `object.ReferenceEquals(,)`)
 -   BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition
      to the regular method return value (unless they are passed with `ref` or `out` keyword).
 -   BREAKING: Drop support for the long-deprecated CLR.* prefix.
diff --git a/src/embed_tests/dynamic.cs b/src/embed_tests/dynamic.cs
index 0a181231c..6e3bfc4cb 100644
--- a/src/embed_tests/dynamic.cs
+++ b/src/embed_tests/dynamic.cs
@@ -128,6 +128,28 @@ public void PassPyObjectInNet()
             Assert.IsTrue(sys.testattr1.Equals(sys.testattr2));
         }
 
+        // regression test for https://github.com/pythonnet/pythonnet/issues/1848
+        [Test]
+        public void EnumEquality()
+        {
+            using var scope = Py.CreateScope();
+            scope.Exec(@"
+import enum
+
+class MyEnum(enum.IntEnum):
+    OK = 1
+    ERROR = 2
+
+def get_status():
+    return MyEnum.OK 
+"
+);
+
+            dynamic MyEnum = scope.Get("MyEnum");
+            dynamic status = scope.Get("get_status").Invoke();
+            Assert.IsTrue(status == MyEnum.OK);
+        }
+
         // regression test for https://github.com/pythonnet/pythonnet/issues/1680
         [Test]
         public void ForEach()
diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs
index cfd3e7158..3d48e22ed 100644
--- a/src/runtime/PythonTypes/PyObject.cs
+++ b/src/runtime/PythonTypes/PyObject.cs
@@ -1075,12 +1075,9 @@ public virtual bool Equals(PyObject? other)
             {
                 return true;
             }
-            int r = Runtime.PyObject_Compare(this, other);
-            if (Exceptions.ErrorOccurred())
-            {
-                throw PythonException.ThrowLastAsClrException();
-            }
-            return r == 0;
+            int result = Runtime.PyObject_RichCompareBool(obj, other.obj, Runtime.Py_EQ);
+            if (result < 0) throw PythonException.ThrowLastAsClrException();
+            return result != 0;
         }
 
 
@@ -1304,6 +1301,18 @@ public override bool TryConvert(ConvertBinder binder, out object? result)
             return false;
         }
 
+        private bool TryCompare(PyObject arg, int op, out object @out)
+        {
+            int result = Runtime.PyObject_RichCompareBool(this.obj, arg.obj, op);
+            @out = result != 0;
+            if (result < 0)
+            {
+                Exceptions.Clear();
+                return false;
+            }
+            return true;
+        }
+        
         public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result)
         {
             using var _ = Py.GIL();
@@ -1352,11 +1361,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
                     res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj);
                     break;
                 case ExpressionType.GreaterThan:
-                    result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0;
-                    return true;
+                    return this.TryCompare((PyObject)arg, Runtime.Py_GT, out result);
                 case ExpressionType.GreaterThanOrEqual:
-                    result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0;
-                    return true;
+                    return this.TryCompare((PyObject)arg, Runtime.Py_GE, out result);
                 case ExpressionType.LeftShift:
                     res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj);
                     break;
@@ -1364,11 +1371,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
                     res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj);
                     break;
                 case ExpressionType.LessThan:
-                    result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0;
-                    return true;
+                    return this.TryCompare((PyObject)arg, Runtime.Py_LT, out result);
                 case ExpressionType.LessThanOrEqual:
-                    result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0;
-                    return true;
+                    return this.TryCompare((PyObject)arg, Runtime.Py_LE, out result);
                 case ExpressionType.Modulo:
                     res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj);
                     break;
@@ -1376,8 +1381,9 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
                     res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj);
                     break;
                 case ExpressionType.NotEqual:
-                    result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0;
-                    return true;
+                    return this.TryCompare((PyObject)arg, Runtime.Py_NE, out result);
+                case ExpressionType.Equal:
+                    return this.TryCompare((PyObject)arg, Runtime.Py_EQ, out result);
                 case ExpressionType.Or:
                     res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj);
                     break;
@@ -1402,6 +1408,40 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
             return true;
         }
 
+        public static bool operator ==(PyObject? a, PyObject? b)
+        {
+            if (a is null && b is null)
+            {
+                return true;
+            }
+            if (a is null || b is null)
+            {
+                return false;
+            }
+
+            using var _ = Py.GIL();
+            int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_EQ);
+            if (result < 0) throw PythonException.ThrowLastAsClrException();
+            return result != 0;
+        }
+
+        public static bool operator !=(PyObject? a, PyObject? b)
+        {
+            if (a is null && b is null)
+            {
+                return false;
+            }
+            if (a is null || b is null)
+            {
+                return true;
+            }
+
+            using var _ = Py.GIL();
+            int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_NE);
+            if (result < 0) throw PythonException.ThrowLastAsClrException();
+            return result != 0;
+        }
+
         // Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509
         // See https://github.com/pythonnet/pythonnet/pull/219
         internal static object? CheckNone(PyObject pyObj)
@@ -1436,14 +1476,17 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object?
                 case ExpressionType.Not:
                     r = Runtime.PyObject_Not(this.obj);
                     result = r == 1;
+                    if (r == -1) Exceptions.Clear();
                     return r != -1;
                 case ExpressionType.IsFalse:
                     r = Runtime.PyObject_IsTrue(this.obj);
                     result = r == 0;
+                    if (r == -1) Exceptions.Clear();
                     return r != -1;
                 case ExpressionType.IsTrue:
                     r = Runtime.PyObject_IsTrue(this.obj);
                     result = r == 1;
+                    if (r == -1) Exceptions.Clear();
                     return r != -1;
                 case ExpressionType.Decrement:
                 case ExpressionType.Increment:
diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs
index 6ad1d459f..1eeb96b54 100644
--- a/src/runtime/Runtime.cs
+++ b/src/runtime/Runtime.cs
@@ -962,31 +962,6 @@ internal static IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args)
 
         internal static int PyObject_RichCompareBool(BorrowedReference value1, BorrowedReference value2, int opid) => Delegates.PyObject_RichCompareBool(value1, value2, opid);
 
-        internal static int PyObject_Compare(BorrowedReference value1, BorrowedReference value2)
-        {
-            int res;
-            res = PyObject_RichCompareBool(value1, value2, Py_LT);
-            if (-1 == res)
-                return -1;
-            else if (1 == res)
-                return -1;
-
-            res = PyObject_RichCompareBool(value1, value2, Py_EQ);
-            if (-1 == res)
-                return -1;
-            else if (1 == res)
-                return 0;
-
-            res = PyObject_RichCompareBool(value1, value2, Py_GT);
-            if (-1 == res)
-                return -1;
-            else if (1 == res)
-                return 1;
-
-            Exceptions.SetError(Exceptions.SystemError, "Error comparing objects");
-            return -1;
-        }
-
 
         internal static int PyObject_IsInstance(BorrowedReference ob, BorrowedReference type) => Delegates.PyObject_IsInstance(ob, type);
 

From 2edd8000e49caa24b6a7feb3f192eac0fd09161b Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Tue, 28 Jun 2022 14:45:43 +0200
Subject: [PATCH 03/16] Fix broken prefix and debug leftover

Additionally, fixes a type hint and makes sure that the new default
behaviour is to use the environment variable if given.
---
 pythonnet/__init__.py | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py
index fa6ed45cf..9876a0bec 100644
--- a/pythonnet/__init__.py
+++ b/pythonnet/__init__.py
@@ -3,7 +3,7 @@
 from typing import Dict, Optional, Union
 import clr_loader
 
-__all__ = ["set_runtime", "set_default_runtime", "load"]
+__all__ = ["set_runtime", "set_runtime_from_env", "load"]
 
 _RUNTIME: Optional[clr_loader.Runtime] = None
 _LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None
@@ -30,7 +30,7 @@ def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None:
 def _get_params_from_env(prefix: str) -> Dict[str, str]:
     from os import environ
 
-    full_prefix = f"PYTHONNET_{prefix.upper()}"
+    full_prefix = f"PYTHONNET_{prefix.upper()}_"
     len_ = len(full_prefix)
 
     env_vars = {
@@ -63,8 +63,8 @@ def _create_runtime_from_spec(
         raise RuntimeError(f"Invalid runtime name: '{spec}'")
 
 
-def set_default_runtime() -> None:
-    """Set up the default runtime
+def set_runtime_from_env() -> None:
+    """Set up the runtime using the environment
 
     This will use the environment variable PYTHONNET_RUNTIME to decide the
     runtime to use, which may be one of netfx, coreclr or mono. The parameters
@@ -80,16 +80,13 @@ def set_default_runtime() -> None:
     """
     from os import environ
 
-    print("Set default RUNTIME")
-    raise RuntimeError("Shouldn't be called here")
-
     spec = environ.get("PYTHONNET_RUNTIME", "default")
     runtime = _create_runtime_from_spec(spec)
     set_runtime(runtime)
 
 
 def load(
-    runtime: Union[clr_loader.Runtime, str] = "default", **params: Dict[str, str]
+    runtime: Union[clr_loader.Runtime, str, None] = None, **params: str
 ) -> None:
     """Load Python.NET in the specified runtime
 
@@ -102,7 +99,10 @@ def load(
         return
 
     if _RUNTIME is None:
-        set_runtime(runtime, **params)
+        if runtime is None:
+            set_runtime_from_env()
+        else:
+            set_runtime(runtime, **params)
 
     if _RUNTIME is None:
         raise RuntimeError("No valid runtime selected")

From 4b6ac1246362a6ecccbaf54bcdd15b105c11ea5d Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Tue, 5 Jul 2022 23:10:42 +0200
Subject: [PATCH 04/16] Bump release candidate version

---
 version.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/version.txt b/version.txt
index cf8213193..d15be84db 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-3.0.0-rc1
+3.0.0-rc2

From 201637127f158a3a930ef4cc74de29f557d62313 Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Wed, 6 Jul 2022 08:52:38 +0200
Subject: [PATCH 05/16] Make MANIFEST.in more explicit

- Only include src/runtime, no other .NET subproject
- In particular, tests are not contained anymore in the sdist
- Fix accidentally including obj and bin directories in the sdist
---
 MANIFEST.in | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/MANIFEST.in b/MANIFEST.in
index 4763ae70f..6458d5778 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,6 @@
-recursive-include src/ *
+graft src/runtime
+prune src/runtime/obj
+prune src/runtime/bin
 include Directory.Build.*
 include pythonnet.sln
 include version.txt
-global-exclude **/obj/* **/bin/*

From d48479703ed448c0a3063794cb302d8088b7362e Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Fri, 8 Jul 2022 17:33:28 +0200
Subject: [PATCH 06/16] Use informational version as clr's __version__
 attribute

---
 src/runtime/PythonEngine.cs  | 15 ++++++++++++---
 src/runtime/Resources/clr.py |  2 --
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs
index 6e9c4d1f1..e5879ae67 100644
--- a/src/runtime/PythonEngine.cs
+++ b/src/runtime/PythonEngine.cs
@@ -228,6 +228,7 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
                 Assembly assembly = Assembly.GetExecutingAssembly();
                 // add the contents of clr.py to the module
                 string clr_py = assembly.ReadStringResource("clr.py");
+
                 Exec(clr_py, module_globals, locals.Reference);
 
                 LoadSubmodule(module_globals, "clr.interop", "interop.py");
@@ -237,14 +238,22 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
                 // add the imported module to the clr module, and copy the API functions
                 // and decorators into the main clr module.
                 Runtime.PyDict_SetItemString(clr_dict, "_extras", module);
+
+                // append version
+                var version = typeof(PythonEngine)
+                    .Assembly
+                    .GetCustomAttribute<AssemblyInformationalVersionAttribute>()
+                    .InformationalVersion;
+                using var versionObj = Runtime.PyString_FromString(version);
+                Runtime.PyDict_SetItemString(clr_dict, "__version__", versionObj.Borrow());
+
                 using var keys = locals.Keys();
                 foreach (PyObject key in keys)
                 {
-                    if (!key.ToString()!.StartsWith("_") || key.ToString()!.Equals("__version__"))
+                    if (!key.ToString()!.StartsWith("_"))
                     {
-                        PyObject value = locals[key];
+                        using PyObject value = locals[key];
                         Runtime.PyDict_SetItem(clr_dict, key.Reference, value.Reference);
-                        value.Dispose();
                     }
                     key.Dispose();
                 }
diff --git a/src/runtime/Resources/clr.py b/src/runtime/Resources/clr.py
index 2254e7430..d4330a4d5 100644
--- a/src/runtime/Resources/clr.py
+++ b/src/runtime/Resources/clr.py
@@ -2,8 +2,6 @@
 Code in this module gets loaded into the main clr module.
 """
 
-__version__ = "3.0.0dev"
-
 
 class clrproperty(object):
     """

From 25f21f99b2a8f1100d7f4a9d78f7caab7d3f99b6 Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Mon, 11 Jul 2022 11:12:14 +0200
Subject: [PATCH 07/16] Drop obsolete packages.config

---
 src/embed_tests/packages.config | 6 ------
 1 file changed, 6 deletions(-)
 delete mode 100644 src/embed_tests/packages.config

diff --git a/src/embed_tests/packages.config b/src/embed_tests/packages.config
deleted file mode 100644
index 590eaef8c..000000000
--- a/src/embed_tests/packages.config
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
-  <package id="NUnit" version="3.12.0" targetFramework="net40" />
-  <package id="NUnit.ConsoleRunner" version="3.11.1" targetFramework="net40" />
-  <package id="System.ValueTuple" version="4.5.0" targetFramework="net40" />
-</packages>

From 332f8e79ef86a83ed4da927797f2499b17666957 Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Mon, 11 Jul 2022 14:38:54 +0200
Subject: [PATCH 08/16] Fix (U)IntPtr construction (#1861)

* Add unit tests for (U)IntPtr conversions
* Special-case construction of (U)IntPtr
* Check (U)IntPtr size explicitly
---
 src/runtime/Converter.cs         |   2 +-
 src/runtime/Runtime.cs           |  12 ++-
 src/runtime/Types/ClassObject.cs | 143 +++++++++++++++++++++++++++----
 src/testing/conversiontest.cs    |   7 +-
 tests/test_conversion.py         |  36 +++++++-
 5 files changed, 174 insertions(+), 26 deletions(-)

diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs
index f86ba7900..e1820f05b 100644
--- a/src/runtime/Converter.cs
+++ b/src/runtime/Converter.cs
@@ -361,7 +361,7 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType,
             // conversions (Python string -> managed string).
             if (obType == objectType)
             {
-                if (Runtime.IsStringType(value))
+                if (Runtime.PyString_Check(value))
                 {
                     return ToPrimitive(value, stringType, out result, setError);
                 }
diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs
index 1eeb96b54..20bef23d4 100644
--- a/src/runtime/Runtime.cs
+++ b/src/runtime/Runtime.cs
@@ -59,6 +59,11 @@ private static string GetDefaultDllName(Version version)
         internal static bool TypeManagerInitialized => _typesInitialized;
         internal static readonly bool Is32Bit = IntPtr.Size == 4;
 
+        // Available in newer .NET Core versions (>= 5) as IntPtr.MaxValue etc.
+        internal static readonly long IntPtrMaxValue = Is32Bit ? Int32.MaxValue : Int64.MaxValue;
+        internal static readonly long IntPtrMinValue = Is32Bit ? Int32.MinValue : Int64.MinValue;
+        internal static readonly ulong UIntPtrMaxValue = Is32Bit ? UInt32.MaxValue : UInt64.MaxValue;
+
         // .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
         internal static bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;
 
@@ -1281,13 +1286,6 @@ internal static bool PyFloat_Check(BorrowedReference ob)
         //====================================================================
         // Python string API
         //====================================================================
-        internal static bool IsStringType(BorrowedReference op)
-        {
-            BorrowedReference t = PyObject_TYPE(op);
-            return (t == PyStringType)
-                || (t == PyUnicodeType);
-        }
-
         internal static bool PyString_Check(BorrowedReference ob)
         {
             return PyObject_TYPE(ob) == PyStringType;
diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs
index 474e9dd7b..70ec53b18 100644
--- a/src/runtime/Types/ClassObject.cs
+++ b/src/runtime/Types/ClassObject.cs
@@ -70,22 +70,9 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo
             // Primitive types do not have constructors, but they look like
             // they do from Python. If the ClassObject represents one of the
             // convertible primitive types, just convert the arg directly.
-            if (type.IsPrimitive || type == typeof(string))
+            if (type.IsPrimitive)
             {
-                if (Runtime.PyTuple_Size(args) != 1)
-                {
-                    Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
-                    return default;
-                }
-
-                BorrowedReference op = Runtime.PyTuple_GetItem(args, 0);
-
-                if (!Converter.ToManaged(op, type, out var result, true))
-                {
-                    return default;
-                }
-
-                return CLRObject.GetReference(result!, tp);
+                return NewPrimitive(tp, args, type);
             }
 
             if (type.IsAbstract)
@@ -99,6 +86,11 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo
                 return NewEnum(type, args, tp);
             }
 
+            if (type == typeof(string))
+            {
+                return NewString(args, tp);
+            }
+
             if (IsGenericNullable(type))
             {
                 // Nullable<T> has special handling in .NET runtime.
@@ -112,6 +104,127 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo
             return self.NewObjectToPython(obj, tp);
         }
 
+        /// <summary>
+        /// Construct a new .NET String object from Python args
+        /// </summary>
+        private static NewReference NewString(BorrowedReference args, BorrowedReference tp)
+        {
+            if (Runtime.PyTuple_Size(args) == 1)
+            {
+                BorrowedReference ob = Runtime.PyTuple_GetItem(args, 0);
+                if (Runtime.PyString_Check(ob))
+                {
+                    if (Runtime.GetManagedString(ob) is string val)
+                        return CLRObject.GetReference(val, tp);
+                }
+
+                // TODO: Initialise using constructors instead
+
+                Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
+                return default;
+            }
+
+            return default;
+        }
+
+        /// <summary>
+        /// Create a new Python object for a primitive type
+        ///
+        /// The primitive types are Boolean, Byte, SByte, Int16, UInt16,
+        /// Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double,
+        /// and Single.
+        ///
+        /// All numeric types and Boolean can be handled by a simple
+        /// conversion, (U)IntPtr has to be handled separately as we
+        /// do not want to convert them automically to/from integers.
+        /// </summary>
+        /// <param name="type">.NET type to construct</param>
+        /// <param name="tp">Corresponding Python type</param>
+        /// <param name="args">Constructor arguments</param>
+        private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference args, Type type)
+        {
+            // TODO: Handle IntPtr
+            if (Runtime.PyTuple_Size(args) != 1)
+            {
+                Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
+                return default;
+            }
+
+            BorrowedReference op = Runtime.PyTuple_GetItem(args, 0);
+            object? result = null;
+
+            if (type == typeof(IntPtr))
+            {
+                if (ManagedType.GetManagedObject(op) is CLRObject clrObject)
+                {
+                    switch (clrObject.inst)
+                    {
+                        case nint val:
+                            result = new IntPtr(val);
+                            break;
+                        case Int64 val:
+                            result = new IntPtr(val);
+                            break;
+                        case Int32 val:
+                            result = new IntPtr(val);
+                            break;
+                    }
+                }
+                else if (Runtime.PyInt_Check(op))
+                {
+                    long? num = Runtime.PyLong_AsLongLong(op);
+                    if (num is long n && n >= Runtime.IntPtrMinValue && n <= Runtime.IntPtrMaxValue)
+                    {
+                        result = new IntPtr(n);
+                    }
+                    else
+                    {
+                        Exceptions.SetError(Exceptions.OverflowError, "value not in range for IntPtr");
+                        return default;
+                    }
+                }
+            }
+
+            if (type == typeof(UIntPtr))
+            {
+                if (ManagedType.GetManagedObject(op) is CLRObject clrObject)
+                {
+                    switch (clrObject.inst)
+                    {
+                        case nuint val:
+                            result = new UIntPtr(val);
+                            break;
+                        case UInt64 val:
+                            result = new UIntPtr(val);
+                            break;
+                        case UInt32 val:
+                            result = new UIntPtr(val);
+                            break;
+                    }
+                }
+                else if (Runtime.PyInt_Check(op))
+                {
+                    ulong? num = Runtime.PyLong_AsUnsignedLongLong(op);
+                    if (num is ulong n && n <= Runtime.UIntPtrMaxValue)
+                    {
+                        result = new UIntPtr(n);
+                    }
+                    else
+                    {
+                        Exceptions.SetError(Exceptions.OverflowError, "value not in range for UIntPtr");
+                        return default;
+                    }
+                }
+            }
+
+            if (result == null && !Converter.ToManaged(op, type, out result, true))
+            {
+                return default;
+            }
+
+            return CLRObject.GetReference(result!, tp);
+        }
+
         protected virtual void SetTypeNewSlot(BorrowedReference pyType, SlotsHolder slotsHolder)
         {
             TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_new, new Interop.BBB_N(tp_new_impl), slotsHolder);
diff --git a/src/testing/conversiontest.cs b/src/testing/conversiontest.cs
index 7a00f139e..272bb74c2 100644
--- a/src/testing/conversiontest.cs
+++ b/src/testing/conversiontest.cs
@@ -1,5 +1,6 @@
 namespace Python.Test
 {
+    using System;
     using System.Collections.Generic;
 
     /// <summary>
@@ -26,6 +27,8 @@ public ConversionTest()
         public ulong UInt64Field = 0;
         public float SingleField = 0.0F;
         public double DoubleField = 0.0;
+        public IntPtr IntPtrField = IntPtr.Zero;
+        public UIntPtr UIntPtrField = UIntPtr.Zero;
         public decimal DecimalField = 0;
         public string StringField;
         public ShortEnum EnumField;
@@ -42,7 +45,7 @@ public ConversionTest()
 
     }
 
-    
+
 
     public interface ISpam
     {
@@ -63,7 +66,7 @@ public string GetValue()
             return value;
         }
     }
-    
+
     public class UnicodeString
     {
         public string value = "안녕";
diff --git a/tests/test_conversion.py b/tests/test_conversion.py
index 6693d8000..a5b4c6fd9 100644
--- a/tests/test_conversion.py
+++ b/tests/test_conversion.py
@@ -25,7 +25,7 @@ def test_bool_conversion():
 
     with pytest.raises(TypeError):
         ob.BooleanField = 1
-    
+
     with pytest.raises(TypeError):
         ob.BooleanField = 0
 
@@ -679,3 +679,37 @@ def test_iconvertible_conversion():
     assert 1024 == change_type(1024, System.Int32)
     assert 1024 == change_type(1024, System.Int64)
     assert 1024 == change_type(1024, System.Int16)
+
+def test_intptr_construction():
+    from System import IntPtr, UIntPtr, Int64, UInt64
+    from ctypes import sizeof, c_void_p
+
+    ptr_size = sizeof(c_void_p)
+    max_intptr = 2 ** (ptr_size * 8 - 1) - 1
+    min_intptr = -max_intptr - 1
+    max_uintptr = 2 ** (ptr_size * 8) - 1
+    min_uintptr = 0
+
+    ob = ConversionTest()
+
+    assert ob.IntPtrField == IntPtr.Zero
+    assert ob.UIntPtrField == UIntPtr.Zero
+
+    for v in [0, -1, 1024, max_intptr, min_intptr]:
+        ob.IntPtrField = IntPtr(Int64(v))
+        assert ob.IntPtrField == IntPtr(v)
+        assert ob.IntPtrField.ToInt64() == v
+
+    for v in [min_intptr - 1, max_intptr + 1]:
+        with pytest.raises(OverflowError):
+            IntPtr(v)
+
+    for v in [0, 1024, min_uintptr, max_uintptr, max_intptr]:
+        ob.UIntPtrField = UIntPtr(UInt64(v))
+        assert ob.UIntPtrField == UIntPtr(v)
+        assert ob.UIntPtrField.ToUInt64() == v
+
+    for v in [min_uintptr - 1, max_uintptr + 1, min_intptr]:
+        with pytest.raises(OverflowError):
+            UIntPtr(v)
+

From 60a719c1f5039b40e0780817f04fd08e2e811a6c Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Mon, 11 Jul 2022 16:55:02 +0200
Subject: [PATCH 09/16] Implement non-simple String constructors explicitly
 (#1862)

---
 src/runtime/Types/ClassObject.cs | 51 ++++++++++++++++++++++++++++----
 tests/test_constructors.py       | 17 +++++++++++
 2 files changed, 62 insertions(+), 6 deletions(-)

diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs
index 70ec53b18..cc42039e8 100644
--- a/src/runtime/Types/ClassObject.cs
+++ b/src/runtime/Types/ClassObject.cs
@@ -106,24 +106,63 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo
 
         /// <summary>
         /// Construct a new .NET String object from Python args
+        ///
+        /// This manual implementation of all individual relevant constructors
+        /// is required because System.String can't be allocated uninitialized.
+        ///
+        /// Additionally, it implements `String(pythonStr)`
         /// </summary>
         private static NewReference NewString(BorrowedReference args, BorrowedReference tp)
         {
-            if (Runtime.PyTuple_Size(args) == 1)
+            var argCount = Runtime.PyTuple_Size(args);
+
+            string? result = null;
+            if (argCount == 1)
             {
                 BorrowedReference ob = Runtime.PyTuple_GetItem(args, 0);
                 if (Runtime.PyString_Check(ob))
                 {
                     if (Runtime.GetManagedString(ob) is string val)
-                        return CLRObject.GetReference(val, tp);
+                        result = val;
                 }
+                else if (Converter.ToManagedValue(ob, typeof(char[]), out object? arr, false))
+                {
+                    result = new String((char[])arr!);
+                }
+            }
+            else if (argCount == 2)
+            {
+                BorrowedReference p1 = Runtime.PyTuple_GetItem(args, 0);
+                BorrowedReference p2 = Runtime.PyTuple_GetItem(args, 1);
 
-                // TODO: Initialise using constructors instead
-
-                Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
-                return default;
+                if (
+                    Converter.ToManagedValue(p1, typeof(char), out object? chr, false) &&
+                    Converter.ToManagedValue(p2, typeof(int), out object? count, false)
+                   )
+                {
+                    result = new String((char)chr!, (int)count!);
+                }
+            }
+            else if (argCount == 3)
+            {
+                BorrowedReference p1 = Runtime.PyTuple_GetItem(args, 0);
+                BorrowedReference p2 = Runtime.PyTuple_GetItem(args, 1);
+                BorrowedReference p3 = Runtime.PyTuple_GetItem(args, 2);
+
+                if (
+                    Converter.ToManagedValue(p1, typeof(char[]), out object? arr, false) &&
+                    Converter.ToManagedValue(p2, typeof(int), out object? offset, false) &&
+                    Converter.ToManagedValue(p3, typeof(int), out object? length, false)
+                   )
+                {
+                    result = new String((char[])arr!, (int)offset!, (int)length!);
+                }
             }
 
+            if (result != null)
+                return CLRObject.GetReference(result!, tp);
+
+            Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
             return default;
         }
 
diff --git a/tests/test_constructors.py b/tests/test_constructors.py
index 8e7ef2794..6d8f00c12 100644
--- a/tests/test_constructors.py
+++ b/tests/test_constructors.py
@@ -69,3 +69,20 @@ def test_default_constructor_fallback():
 
     with pytest.raises(TypeError):
         ob = DefaultConstructorMatching("2")
+
+
+def test_string_constructor():
+    from System import String, Char, Array
+
+    ob = String('A', 10)
+    assert ob == 'A' * 10
+
+    arr = Array[Char](10)
+    for i in range(10):
+        arr[i] = Char(str(i))
+
+    ob = String(arr)
+    assert ob == "0123456789"
+
+    ob = String(arr, 5, 4)
+    assert ob == "5678"

From b79ae2f405ddb457c7449f81bb350209cc84d522 Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Mon, 11 Jul 2022 17:13:54 +0200
Subject: [PATCH 10/16] Fix missing docstring for types with custom
 constructors (#1865)

---
 src/runtime/ClassManager.cs | 15 ++++++++++++---
 tests/test_docstring.py     |  7 +++++++
 tests/test_enum.py          |  5 ++---
 3 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs
index 6c5558f3a..79ab20e82 100644
--- a/src/runtime/ClassManager.cs
+++ b/src/runtime/ClassManager.cs
@@ -87,7 +87,7 @@ internal static ClassManagerState SaveRuntimeData()
                     if ((Runtime.PyDict_DelItemString(dict.Borrow(), member) == -1) &&
                         (Exceptions.ExceptionMatches(Exceptions.KeyError)))
                     {
-                        // Trying to remove a key that's not in the dictionary 
+                        // Trying to remove a key that's not in the dictionary
                         // raises an error. We don't care about it.
                         Runtime.PyErr_Clear();
                     }
@@ -215,7 +215,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
             impl.indexer = info.indexer;
             impl.richcompare.Clear();
 
-            
+
             // Finally, initialize the class __dict__ and return the object.
             using var newDict = Runtime.PyObject_GenericGetDict(pyType.Reference);
             BorrowedReference dict = newDict.Borrow();
@@ -271,6 +271,15 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
                         Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc.Borrow());
                     }
                 }
+
+                if (Runtime.PySequence_Contains(dict, PyIdentifier.__doc__) != 1)
+                {
+                    // Ensure that at least some doc string is set
+                    using var fallbackDoc = Runtime.PyString_FromString(
+                        $"Python wrapper for .NET type {type}"
+                    );
+                    Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, fallbackDoc.Borrow());
+                }
             }
             doc.Dispose();
 
@@ -562,7 +571,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
 
             return ci;
         }
-        
+
         /// <summary>
         /// This class owns references to PyObjects in the `members` member.
         /// The caller has responsibility to DECREF them.
diff --git a/tests/test_docstring.py b/tests/test_docstring.py
index 640a61915..36c925a74 100644
--- a/tests/test_docstring.py
+++ b/tests/test_docstring.py
@@ -25,3 +25,10 @@ def test_doc_without_ctor():
     assert DocWithoutCtorTest.__doc__ == 'DocWithoutCtorTest Class'
     assert DocWithoutCtorTest.TestMethod.__doc__ == 'DocWithoutCtorTest TestMethod'
     assert DocWithoutCtorTest.StaticTestMethod.__doc__ == 'DocWithoutCtorTest StaticTestMethod'
+
+
+def test_doc_primitve():
+    from System import Int64, String
+
+    assert Int64.__doc__ is not None
+    assert String.__doc__ is not None
diff --git a/tests/test_enum.py b/tests/test_enum.py
index 981fb735c..f24f95b36 100644
--- a/tests/test_enum.py
+++ b/tests/test_enum.py
@@ -15,7 +15,6 @@ def test_enum_standard_attrs():
     assert DayOfWeek.__name__ == 'DayOfWeek'
     assert DayOfWeek.__module__ == 'System'
     assert isinstance(DayOfWeek.__dict__, DictProxyType)
-    assert DayOfWeek.__doc__ is None
 
 
 def test_enum_get_member():
@@ -139,7 +138,7 @@ def test_enum_undefined_value():
     # This should fail because our test enum doesn't have it.
     with pytest.raises(ValueError):
         Test.FieldTest().EnumField = Test.ShortEnum(20)
-    
+
     # explicitly permit undefined values
     Test.FieldTest().EnumField = Test.ShortEnum(20, True)
 
@@ -157,6 +156,6 @@ def test_enum_conversion():
 
     with pytest.raises(TypeError):
         Test.FieldTest().EnumField = "str"
-    
+
     with pytest.raises(TypeError):
         Test.FieldTest().EnumField = 1

From 863397a2316f8e6a1a1a5fc20030d5d32b6a3431 Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Mon, 11 Jul 2022 17:15:23 +0200
Subject: [PATCH 11/16] Bump release candidate version

---
 version.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/version.txt b/version.txt
index d15be84db..b9eb748c0 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-3.0.0-rc2
+3.0.0-rc3

From 59b1c51daad161fc5f85452102188b75cc7284e3 Mon Sep 17 00:00:00 2001
From: Victor Nova <lost@losttech.software>
Date: Thu, 14 Jul 2022 11:17:25 -0700
Subject: [PATCH 12/16] Finalizer.Instance.Collect() and
 Runtime.TryCollectingGarbage(...) are now callable from Python

---
 src/runtime/Finalizer.cs | 1 +
 src/runtime/Runtime.cs   | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/runtime/Finalizer.cs b/src/runtime/Finalizer.cs
index e796cacd1..f4b465ecb 100644
--- a/src/runtime/Finalizer.cs
+++ b/src/runtime/Finalizer.cs
@@ -106,6 +106,7 @@ internal IncorrectRefCountException(IntPtr ptr)
 
         #endregion
 
+        [ForbidPythonThreads]
         public void Collect() => this.DisposeAll();
 
         internal void ThrottledCollect()
diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs
index 20bef23d4..88a8ed173 100644
--- a/src/runtime/Runtime.cs
+++ b/src/runtime/Runtime.cs
@@ -359,6 +359,7 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops)
         /// </summary>
         /// <param name="runs">Total number of GC loops to run</param>
         /// <returns><c>true</c> if a steady state was reached upon the requested number of tries (e.g. on the last try no objects were collected).</returns>
+        [ForbidPythonThreads]
         public static bool TryCollectingGarbage(int runs)
             => TryCollectingGarbage(runs, forceBreakLoops: false);
 

From 469ec671fc9d7ee2f149a6687b5c7aeb5f861563 Mon Sep 17 00:00:00 2001
From: Victor Nova <lost@losttech.software>
Date: Thu, 14 Jul 2022 11:18:01 -0700
Subject: [PATCH 13/16] fixed leak in NewReference.Move

fixes https://github.com/pythonnet/pythonnet/issues/1872
---
 src/runtime/Native/NewReference.cs |  2 +-
 tests/test_constructors.py         | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/runtime/Native/NewReference.cs b/src/runtime/Native/NewReference.cs
index 25145fc4f..f7a030818 100644
--- a/src/runtime/Native/NewReference.cs
+++ b/src/runtime/Native/NewReference.cs
@@ -47,7 +47,7 @@ public PyObject MoveToPyObject()
         /// </summary>
         public NewReference Move()
         {
-            var result = new NewReference(this);
+            var result = DangerousFromPointer(this.DangerousGetAddress());
             this.pointer = default;
             return result;
         }
diff --git a/tests/test_constructors.py b/tests/test_constructors.py
index 6d8f00c12..3e0b1bb93 100644
--- a/tests/test_constructors.py
+++ b/tests/test_constructors.py
@@ -3,6 +3,7 @@
 """Test CLR class constructor support."""
 
 import pytest
+import sys
 
 import System
 
@@ -71,6 +72,19 @@ def test_default_constructor_fallback():
         ob = DefaultConstructorMatching("2")
 
 
+def test_constructor_leak():
+    from System import Uri
+    from Python.Runtime import Runtime
+
+    uri = Uri("http://www.python.org")
+    Runtime.TryCollectingGarbage(20)
+    ref_count = sys.getrefcount(uri)
+
+    # check disabled due to GC uncertainty
+    # assert ref_count == 1
+
+
+
 def test_string_constructor():
     from System import String, Char, Array
 

From 93631aff83b34a0665374cd41313c8552b88b545 Mon Sep 17 00:00:00 2001
From: Benedikt Reinartz <filmor@gmail.com>
Date: Sat, 16 Jul 2022 12:08:59 +0200
Subject: [PATCH 14/16] Bump release candidate version

---
 version.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/version.txt b/version.txt
index b9eb748c0..dc72b3783 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-3.0.0-rc3
+3.0.0-rc4

From a5e9b55bb0aa6620c59e9f108d17e1a617c07b78 Mon Sep 17 00:00:00 2001
From: Victor <lost@losttech.software>
Date: Sat, 16 Jul 2022 12:00:05 -0700
Subject: [PATCH 15/16] Generate XML documentation on build and add to it NuGet
 package (#1878)

also fixed issues with xml docs in the code

implements https://github.com/pythonnet/pythonnet/issues/1876
---
 src/runtime/AssemblyManager.cs        | 2 +-
 src/runtime/Python.Runtime.csproj     | 2 ++
 src/runtime/Runtime.cs                | 1 +
 src/runtime/Types/ArrayObject.cs      | 2 +-
 src/runtime/Types/EventBinding.cs     | 1 -
 src/runtime/Types/ReflectedClrType.cs | 1 -
 6 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs
index d09d2d76e..a8bbd1f6c 100644
--- a/src/runtime/AssemblyManager.cs
+++ b/src/runtime/AssemblyManager.cs
@@ -334,7 +334,7 @@ public static bool IsValidNamespace(string name)
         }
 
         /// <summary>
-        /// Returns an IEnumerable<string> containing the namepsaces exported
+        /// Returns an enumerable collection containing the namepsaces exported
         /// by loaded assemblies in the current app domain.
         /// </summary>
         public static IEnumerable<string> GetNamespaces ()
diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj
index fad5b9da8..5072f23cd 100644
--- a/src/runtime/Python.Runtime.csproj
+++ b/src/runtime/Python.Runtime.csproj
@@ -23,6 +23,8 @@
     <IncludeSymbols>true</IncludeSymbols>
     <SymbolPackageFormat>snupkg</SymbolPackageFormat>
 
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+
     <AssemblyOriginatorKeyFile>..\pythonnet.snk</AssemblyOriginatorKeyFile>
     <SignAssembly>true</SignAssembly>
 
diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs
index 88a8ed173..4dc904f43 100644
--- a/src/runtime/Runtime.cs
+++ b/src/runtime/Runtime.cs
@@ -99,6 +99,7 @@ internal static int GetRun()
         internal static bool HostedInPython;
         internal static bool ProcessIsTerminating;
 
+        /// <summary>
         /// Initialize the runtime...
         /// </summary>
         /// <remarks>Always call this method from the Main thread.  After the
diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs
index bda717e56..b95934baf 100644
--- a/src/runtime/Types/ArrayObject.cs
+++ b/src/runtime/Types/ArrayObject.cs
@@ -520,7 +520,7 @@ static IntPtr AllocateBufferProcs()
         #endregion
 
         /// <summary>
-        /// <see cref="TypeManager.InitializeSlots(IntPtr, Type, SlotsHolder)"/>
+        /// <see cref="TypeManager.InitializeSlots(PyType, Type, SlotsHolder?)"/>
         /// </summary>
         public static void InitializeSlots(PyType type, ISet<string> initialized, SlotsHolder slotsHolder)
         {
diff --git a/src/runtime/Types/EventBinding.cs b/src/runtime/Types/EventBinding.cs
index 9eb2382ec..5c47d4aab 100644
--- a/src/runtime/Types/EventBinding.cs
+++ b/src/runtime/Types/EventBinding.cs
@@ -70,7 +70,6 @@ public static NewReference nb_inplace_subtract(BorrowedReference ob, BorrowedRef
             return new NewReference(ob);
         }
 
-        /// </summary>
         public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, BorrowedReference val)
             => EventObject.tp_descr_set(ds, ob, val);
 
diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs
index 2e8f95924..b787939be 100644
--- a/src/runtime/Types/ReflectedClrType.cs
+++ b/src/runtime/Types/ReflectedClrType.cs
@@ -22,7 +22,6 @@ internal ReflectedClrType(BorrowedReference original) : base(original) { }
     /// </summary>
     /// <remarks>
     /// Returned <see cref="ReflectedClrType"/> might be partially initialized.
-    /// If you need fully initialized type, use <see cref="GetOrInitialize(ClassBase, Type)"/>
     /// </remarks>
     public static ReflectedClrType GetOrCreate(Type type)
     {

From 56155c29d500a1cc94b53b74dd458ec19a1a4e7c Mon Sep 17 00:00:00 2001
From: Victor Nova <lost@losttech.software>
Date: Mon, 8 Aug 2022 12:39:05 -0700
Subject: [PATCH 16/16] BREAKING: disabled implicit conversion from Python
 objects implementing sequence protocol to .NET arrays when the target .NET
 type is `System.Object`. The conversion is still attempted when the target
 type is a `System.Array`

fixes https://github.com/pythonnet/pythonnet/issues/1900
---
 CHANGELOG.md             | 3 +++
 src/runtime/Converter.cs | 5 -----
 tests/test_conversion.py | 7 +++++++
 3 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b5dd1816..9f0f212e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -67,6 +67,9 @@ or the DLL must be loaded in advance. This must be done before calling any other
 -   BREAKING: disabled implicit conversion from C# enums to Python `int` and back.
 One must now either use enum members (e.g. `MyEnum.Option`), or use enum constructor
 (e.g. `MyEnum(42)` or `MyEnum(42, True)` when `MyEnum` does not have a member with value 42).
+-   BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to
+.NET arrays when the target .NET type is `System.Object`. The conversion is still attempted when the
+target type is a `System.Array`.
 -   Sign Runtime DLL with a strong name
 -   Implement loading through `clr_loader` instead of the included `ClrModule`, enables
     support for .NET Core
diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs
index e1820f05b..2e0edc3b5 100644
--- a/src/runtime/Converter.cs
+++ b/src/runtime/Converter.cs
@@ -389,11 +389,6 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType,
                     return true;
                 }
 
-                if (Runtime.PySequence_Check(value))
-                {
-                    return ToArray(value, typeof(object[]), out result, setError);
-                }
-
                 result = new PyObject(value);
                 return true;
             }
diff --git a/tests/test_conversion.py b/tests/test_conversion.py
index a5b4c6fd9..f8d8039c6 100644
--- a/tests/test_conversion.py
+++ b/tests/test_conversion.py
@@ -577,6 +577,13 @@ class Foo(object):
     ob.ObjectField = Foo
     assert ob.ObjectField == Foo
 
+    class PseudoSeq:
+        def __getitem__(self, idx):
+           return 0
+
+    ob.ObjectField = PseudoSeq()
+    assert ob.ObjectField.__class__.__name__ == "PseudoSeq"
+
 
 def test_null_conversion():
     """Test null conversion."""