Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
added new class PyType to wrap Python type objects and enable new typ…
…e construction from PyType_Spec (TypeSpec class)
  • Loading branch information
lostmsu committed Mar 30, 2021
commit d3c565475cee7c5ed13b59f79d7865c8e0c20d55
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]).
- Add GetPythonThreadID and Interrupt methods in PythonEngine
- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355])
- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec`

### Changed
- Drop support for Python 2, 3.4, and 3.5
Expand Down
45 changes: 45 additions & 0 deletions src/embed_tests/TestPyType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Text;

using NUnit.Framework;

using Python.Runtime;
using Python.Runtime.Native;

namespace Python.EmbeddingTest
{
public class TestPyType
{
[OneTimeSetUp]
public void SetUp()
{
PythonEngine.Initialize();
}

[OneTimeTearDown]
public void Dispose()
{
PythonEngine.Shutdown();
}

[Test]
public void CanCreateHeapType()
{
const string name = "nÁmæ";
const string docStr = "dÁcæ";

using var doc = new StrPtr(docStr, Encoding.UTF8);
var spec = new TypeSpec(
name: name,
basicSize: ObjectOffset.Size(Runtime.Runtime.PyTypeType),
slots: new TypeSpec.Slot[] {
new (TypeSlotID.tp_doc, doc.RawPointer),
},
TypeFlags.Default | TypeFlags.HeapType
);

using var type = new PyType(spec);
Assert.AreEqual(name, type.GetAttr("__name__").As<string>());
Assert.AreEqual(docStr, type.GetAttr("__doc__").As<string>());
}
}
}
121 changes: 121 additions & 0 deletions src/runtime/TypeSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace Python.Runtime
{
public class TypeSpec
{
public TypeSpec(string name, int basicSize, IEnumerable<Slot> slots, TypeFlags flags, int itemSize = 0)
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this.BasicSize = basicSize;
this.Slots = slots.ToArray();
this.Flags = flags;
this.ItemSize = itemSize;
}
public string Name { get; }
public int BasicSize { get; }
public int ItemSize { get; }
public TypeFlags Flags { get; }
public IReadOnlyList<Slot> Slots { get; }

[StructLayout(LayoutKind.Sequential)]
public struct Slot
{
public Slot(TypeSlotID id, IntPtr value)
{
ID = id;
Value = value;
}

public TypeSlotID ID { get; }
public IntPtr Value { get; }
}
}

public enum TypeSlotID : int
{
mp_ass_subscript = 3,
mp_length = 4,
mp_subscript = 5,
nb_absolute = 6,
nb_add = 7,
nb_and = 8,
nb_bool = 9,
nb_divmod = 10,
nb_float = 11,
nb_floor_divide = 12,
nb_index = 13,
nb_inplace_add = 14,
nb_inplace_and = 15,
nb_inplace_floor_divide = 16,
nb_inplace_lshift = 17,
nb_inplace_multiply = 18,
nb_inplace_or = 19,
nb_inplace_power = 20,
nb_inplace_remainder = 21,
nb_inplace_rshift = 22,
nb_inplace_subtract = 23,
nb_inplace_true_divide = 24,
nb_inplace_xor = 25,
nb_int = 26,
nb_invert = 27,
nb_lshift = 28,
nb_multiply = 29,
nb_negative = 30,
nb_or = 31,
nb_positive = 32,
nb_power = 33,
nb_remainder = 34,
nb_rshift = 35,
nb_subtract = 36,
nb_true_divide = 37,
nb_xor = 38,
sq_ass_item = 39,
sq_concat = 40,
sq_contains = 41,
sq_inplace_concat = 42,
sq_inplace_repeat = 43,
sq_item = 44,
sq_length = 45,
sq_repeat = 46,
tp_alloc = 47,
tp_base = 48,
tp_bases = 49,
tp_call = 50,
tp_clear = 51,
tp_dealloc = 52,
tp_del = 53,
tp_descr_get = 54,
tp_descr_set = 55,
tp_doc = 56,
tp_getattr = 57,
tp_getattro = 58,
tp_hash = 59,
tp_init = 60,
tp_is_gc = 61,
tp_iter = 62,
tp_iternext = 63,
tp_methods = 64,
tp_new = 65,
tp_repr = 66,
tp_richcompare = 67,
tp_setattr = 68,
tp_setattro = 69,
tp_str = 70,
tp_traverse = 71,
tp_members = 72,
tp_getset = 73,
tp_free = 74,
nb_matrix_multiply = 75,
nb_inplace_matrix_multiply = 76,
am_await = 77,
am_aiter = 78,
am_anext = 79,
/// <remarks>New in 3.5</remarks>
tp_finalize = 80,
}
}
45 changes: 45 additions & 0 deletions src/runtime/native/NativeTypeSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#nullable enable
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Python.Runtime.Native
{
[StructLayout(LayoutKind.Sequential)]
struct NativeTypeSpec : IDisposable
{
public readonly StrPtr Name;
public readonly int BasicSize;
public readonly int ItemSize;
public readonly TypeFlags Flags;
public IntPtr Slots;

public NativeTypeSpec(TypeSpec spec)
{
if (spec is null) throw new ArgumentNullException(nameof(spec));

this.Name = new StrPtr(spec.Name, Encoding.UTF8);
this.BasicSize = spec.BasicSize;
this.ItemSize = spec.ItemSize;
this.Flags = spec.Flags;

unsafe
{
int slotsBytes = checked((spec.Slots.Count + 1) * Marshal.SizeOf<TypeSpec.Slot>());
var slots = (TypeSpec.Slot*)Marshal.AllocHGlobal(slotsBytes);
for (int slotIndex = 0; slotIndex < spec.Slots.Count; slotIndex++)
slots[slotIndex] = spec.Slots[slotIndex];
slots[spec.Slots.Count] = default;
this.Slots = (IntPtr)slots;
}
}

public void Dispose()
{
// we have to leak the name
// this.Name.Dispose();
Marshal.FreeHGlobal(this.Slots);
this.Slots = IntPtr.Zero;
}
}
}
50 changes: 50 additions & 0 deletions src/runtime/pytype.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#nullable enable
using System;
using System.Runtime.InteropServices;

using Python.Runtime.Native;

namespace Python.Runtime
{
public class PyType : PyObject
{
/// <summary>Creates heap type object from the <paramref name="spec"/>.</summary>
public PyType(TypeSpec spec, PyTuple? bases = null) : base(FromSpec(spec, bases)) { }
/// <summary>Wraps an existing type object.</summary>
public PyType(PyObject o) : base(FromObject(o)) { }

/// <summary>Checks if specified object is a Python type.</summary>
public static bool IsType(PyObject value)
{
if (value is null) throw new ArgumentNullException(nameof(value));

return Runtime.PyType_Check(value.obj);
}

private static BorrowedReference FromObject(PyObject o)
{
if (o is null) throw new ArgumentNullException(nameof(o));
if (!IsType(o)) throw new ArgumentException("object is not a type");

return o.Reference;
}

private static IntPtr FromSpec(TypeSpec spec, PyTuple? bases = null)
{
if (spec is null) throw new ArgumentNullException(nameof(spec));

if ((spec.Flags & TypeFlags.HeapType) == 0)
throw new NotSupportedException("Only heap types are supported");

var nativeSpec = new NativeTypeSpec(spec);
var basesRef = bases is null ? default : bases.Reference;
var result = Runtime.PyType_FromSpecWithBases(in nativeSpec, basesRef);

PythonException.ThrowIfIsNull(result);

nativeSpec.Dispose();

return result.DangerousMoveToPointer();
}
}
}
4 changes: 4 additions & 0 deletions src/runtime/runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2006,6 +2006,8 @@ internal static IntPtr PyType_GenericAlloc(IntPtr type, long n)


private static IntPtr PyType_GenericAlloc(IntPtr type, IntPtr n) => Delegates.PyType_GenericAlloc(type, n);

internal static NewReference PyType_FromSpecWithBases(in NativeTypeSpec spec, BorrowedReference bases) => Delegates.PyType_FromSpecWithBases(in spec, bases);

/// <summary>
/// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a type’s base class. Return 0 on success, or return -1 and sets an exception on error.
Expand Down Expand Up @@ -2509,6 +2511,7 @@ static Delegates()
PyException_SetCause = (delegate* unmanaged[Cdecl]<IntPtr, IntPtr, void>)GetFunctionByName(nameof(PyException_SetCause), GetUnmanagedDll(_PythonDll));
PyThreadState_SetAsyncExcLLP64 = (delegate* unmanaged[Cdecl]<uint, IntPtr, int>)GetFunctionByName("PyThreadState_SetAsyncExc", GetUnmanagedDll(_PythonDll));
PyThreadState_SetAsyncExcLP64 = (delegate* unmanaged[Cdecl]<ulong, IntPtr, int>)GetFunctionByName("PyThreadState_SetAsyncExc", GetUnmanagedDll(_PythonDll));
PyType_FromSpecWithBases = (delegate* unmanaged[Cdecl]<in NativeTypeSpec, BorrowedReference, NewReference>)GetFunctionByName(nameof(PyType_FromSpecWithBases), GetUnmanagedDll(PythonDLL));
}

static global::System.IntPtr GetUnmanagedDll(string libraryName)
Expand Down Expand Up @@ -2786,6 +2789,7 @@ static Delegates()
internal static delegate* unmanaged[Cdecl]<uint, IntPtr, int> PyThreadState_SetAsyncExcLLP64 { get; }
internal static delegate* unmanaged[Cdecl]<ulong, IntPtr, int> PyThreadState_SetAsyncExcLP64 { get; }
internal static delegate* unmanaged[Cdecl]<BorrowedReference, IntPtr, NewReference> PyObject_GenericGetDict { get; }
internal static delegate* unmanaged[Cdecl]<in NativeTypeSpec, BorrowedReference, NewReference> PyType_FromSpecWithBases { get; }
}
}

Expand Down