diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ba315e2..13838e0ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added +- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax + ### Changed - Drop support for Python 2, 3.4, and 3.5 - `clr.AddReference` may now throw errors besides `FileNotFoundException`, that provide more diff --git a/src/runtime/BorrowedReference.cs b/src/runtime/BorrowedReference.cs index 8ae382e77..d82763d40 100644 --- a/src/runtime/BorrowedReference.cs +++ b/src/runtime/BorrowedReference.cs @@ -22,5 +22,10 @@ public BorrowedReference(IntPtr pointer) { this.pointer = pointer; } + + public static bool operator ==(BorrowedReference a, BorrowedReference b) + => a.pointer == b.pointer; + public static bool operator !=(BorrowedReference a, BorrowedReference b) + => a.pointer != b.pointer; } } diff --git a/src/runtime/NewReference.cs b/src/runtime/NewReference.cs index 89d53bb36..a4ed75918 100644 --- a/src/runtime/NewReference.cs +++ b/src/runtime/NewReference.cs @@ -28,6 +28,14 @@ public PyObject MoveToPyObject() return result; } + /// Moves ownership of this instance to unmanged pointer + public IntPtr DangerousMoveToPointerOrNull() + { + var result = this.pointer; + this.pointer = IntPtr.Zero; + return result; + } + /// /// Removes this reference to a Python object, and sets it to null. /// diff --git a/src/runtime/arrayobject.cs b/src/runtime/arrayobject.cs index 0db84dd90..e6a4bee19 100644 --- a/src/runtime/arrayobject.cs +++ b/src/runtime/arrayobject.cs @@ -20,21 +20,109 @@ internal override bool CanSubclass() return false; } - public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) + public static IntPtr tp_new(IntPtr tpRaw, IntPtr args, IntPtr kw) { + if (kw != IntPtr.Zero) + { + return Exceptions.RaiseTypeError("array constructor takes no keyword arguments"); + } + + var tp = new BorrowedReference(tpRaw); + var self = GetManagedObject(tp) as ArrayObject; - if (Runtime.PyTuple_Size(args) != 1) + + long[] dimensions = new long[Runtime.PyTuple_Size(args)]; + if (dimensions.Length == 0) { - return Exceptions.RaiseTypeError("array expects 1 argument"); + return Exceptions.RaiseTypeError("array constructor requires at least one integer argument or an object convertible to array"); } + if (dimensions.Length != 1) + { + return CreateMultidimensional(self.type.GetElementType(), dimensions, + shapeTuple: new BorrowedReference(args), + pyType: tp) + .DangerousMoveToPointerOrNull(); + } + IntPtr op = Runtime.PyTuple_GetItem(args, 0); + + // create single dimensional array + if (Runtime.PyInt_Check(op)) + { + dimensions[0] = Runtime.PyLong_AsLongLong(op); + if (dimensions[0] == -1 && Exceptions.ErrorOccurred()) + { + Exceptions.Clear(); + } + else + { + return NewInstance(self.type.GetElementType(), tp, dimensions) + .DangerousMoveToPointerOrNull(); + } + } object result; + // this implements casting to Array[T] if (!Converter.ToManaged(op, self.type, out result, true)) { return IntPtr.Zero; } - return CLRObject.GetInstHandle(result, tp); + return CLRObject.GetInstHandle(result, tp) + .DangerousGetAddress(); + } + + static NewReference CreateMultidimensional(Type elementType, long[] dimensions, BorrowedReference shapeTuple, BorrowedReference pyType) + { + for (int dimIndex = 0; dimIndex < dimensions.Length; dimIndex++) + { + BorrowedReference dimObj = Runtime.PyTuple_GetItem(shapeTuple, dimIndex); + PythonException.ThrowIfIsNull(dimObj); + + if (!Runtime.PyInt_Check(dimObj)) + { + Exceptions.RaiseTypeError("array constructor expects integer dimensions"); + return default; + } + + dimensions[dimIndex] = Runtime.PyLong_AsLongLong(dimObj); + if (dimensions[dimIndex] == -1 && Exceptions.ErrorOccurred()) + { + Exceptions.RaiseTypeError("array constructor expects integer dimensions"); + return default; + } + } + + return NewInstance(elementType, pyType, dimensions); + } + + static NewReference NewInstance(Type elementType, BorrowedReference arrayPyType, long[] dimensions) + { + object result; + try + { + result = Array.CreateInstance(elementType, dimensions); + } + catch (ArgumentException badArgument) + { + Exceptions.SetError(Exceptions.ValueError, badArgument.Message); + return default; + } + catch (OverflowException overflow) + { + Exceptions.SetError(overflow); + return default; + } + catch (NotSupportedException notSupported) + { + Exceptions.SetError(notSupported); + return default; + } + catch (OutOfMemoryException oom) + { + Exceptions.SetError(Exceptions.MemoryError, oom.Message); + return default; + } + return CLRObject.GetInstHandle(result, arrayPyType); } diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index 0b62fecba..a79662ccc 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -51,7 +51,11 @@ static CLRObject GetInstance(object ob) return GetInstance(ob, cc.tpHandle); } - + internal static NewReference GetInstHandle(object ob, BorrowedReference pyType) + { + CLRObject co = GetInstance(ob, pyType.DangerousGetAddress()); + return NewReference.DangerousFromPointer(co.pyHandle); + } internal static IntPtr GetInstHandle(object ob, IntPtr pyType) { CLRObject co = GetInstance(ob, pyType); diff --git a/src/runtime/managedtype.cs b/src/runtime/managedtype.cs index bc2805d80..87a89b00a 100644 --- a/src/runtime/managedtype.cs +++ b/src/runtime/managedtype.cs @@ -75,6 +75,8 @@ internal void FreeGCHandle() } } + internal static ManagedType GetManagedObject(BorrowedReference ob) + => GetManagedObject(ob.DangerousGetAddress()); /// /// Given a Python object, return the associated managed object or null. /// diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index a11e9002e..10aa165c8 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1013,6 +1013,8 @@ internal static unsafe IntPtr PyObject_TYPE(IntPtr op) ? new IntPtr((void*)(*((uint*)p + n))) : new IntPtr((void*)(*((ulong*)p + n))); } + internal static unsafe BorrowedReference PyObject_TYPE(BorrowedReference op) + => new BorrowedReference(PyObject_TYPE(op.DangerousGetAddress())); /// /// Managed version of the standard Python C API PyObject_Type call. @@ -1202,6 +1204,8 @@ internal static long PyObject_Size(IntPtr pointer) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern bool PyNumber_Check(IntPtr ob); + internal static bool PyInt_Check(BorrowedReference ob) + => PyObject_TypeCheck(ob, new BorrowedReference(PyIntType)); internal static bool PyInt_Check(IntPtr ob) { return PyObject_TypeCheck(ob, PyIntType); @@ -1291,6 +1295,8 @@ internal static object PyLong_AsUnsignedLong(IntPtr value) return PyLong_AsUnsignedLong64(value); } + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern long PyLong_AsLongLong(BorrowedReference value); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern long PyLong_AsLongLong(IntPtr value); @@ -1829,11 +1835,15 @@ internal static IntPtr PyTuple_New(long size) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyTuple_New(IntPtr size); + internal static BorrowedReference PyTuple_GetItem(BorrowedReference pointer, long index) + => PyTuple_GetItem(pointer, new IntPtr(index)); internal static IntPtr PyTuple_GetItem(IntPtr pointer, long index) { return PyTuple_GetItem(pointer, new IntPtr(index)); } + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + private static extern BorrowedReference PyTuple_GetItem(BorrowedReference pointer, IntPtr index); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyTuple_GetItem(IntPtr pointer, IntPtr index); @@ -1950,10 +1960,14 @@ internal static bool PyType_Check(IntPtr ob) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern bool PyType_IsSubtype(IntPtr t1, IntPtr t2); + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern bool PyType_IsSubtype(BorrowedReference t1, BorrowedReference t2); internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp) + => PyObject_TypeCheck(new BorrowedReference(ob), new BorrowedReference(tp)); + internal static bool PyObject_TypeCheck(BorrowedReference ob, BorrowedReference tp) { - IntPtr t = PyObject_TYPE(ob); + BorrowedReference t = PyObject_TYPE(ob); return (t == tp) || PyType_IsSubtype(t, tp); } diff --git a/src/tests/test_array.py b/src/tests/test_array.py index 428bb2065..9ab044b29 100644 --- a/src/tests/test_array.py +++ b/src/tests/test_array.py @@ -1174,6 +1174,20 @@ def test_boxed_value_type_mutation_result(): assert items[i].X == i + 1 assert items[i].Y == i + 1 +def test_create_array_from_shape(): + from System import Array + + value = Array[int](3) + assert value[1] == 0 + assert value.Length == 3 + + value = Array[int](3, 4) + assert value[1, 1] == 0 + assert value.GetLength(0) == 3 + assert value.GetLength(1) == 4 + + with pytest.raises(ValueError): + Array[int](-1) def test_special_array_creation(): """Test using the Array[] syntax for creating arrays."""