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."""