From 92e6f8cc1dc0d7611731a35f99f89bbf02f6d093 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Sat, 23 May 2020 19:03:05 -0700 Subject: [PATCH] allow substituting base types for CLR types (as seen from Python) When embedding Python, host can now provide custom implementations of IPythonBaseTypeProvider via PythonEngine.InteropConfiguration. When .NET type is reflected to Python, this type provider will be able to specify which bases the resulting Python class will have. This implements https://github.com/pythonnet/pythonnet/issues/862 --- src/embed_tests/Inheritance.cs | 175 +++++++++++++++++++++ src/runtime/DefaultBaseTypeProvider.cs | 34 ++++ src/runtime/IPythonBaseTypeProvider.cs | 14 ++ src/runtime/InteropConfiguration.cs | 25 +++ src/runtime/PythonBaseTypeProviderGroup.cs | 24 +++ src/runtime/PythonReferenceComparer.cs | 22 +++ src/runtime/StolenReference.cs | 7 + src/runtime/classmanager.cs | 12 +- src/runtime/pythonengine.cs | 17 +- src/runtime/pytype.cs | 42 +++++ src/runtime/typemanager.cs | 145 +++++++++++------ 11 files changed, 468 insertions(+), 49 deletions(-) create mode 100644 src/embed_tests/Inheritance.cs create mode 100644 src/runtime/DefaultBaseTypeProvider.cs create mode 100644 src/runtime/IPythonBaseTypeProvider.cs create mode 100644 src/runtime/InteropConfiguration.cs create mode 100644 src/runtime/PythonBaseTypeProviderGroup.cs create mode 100644 src/runtime/PythonReferenceComparer.cs diff --git a/src/embed_tests/Inheritance.cs b/src/embed_tests/Inheritance.cs new file mode 100644 index 000000000..50a461adb --- /dev/null +++ b/src/embed_tests/Inheritance.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class Inheritance + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + var locals = new PyDict(); + PythonEngine.Exec(InheritanceTestBaseClassWrapper.ClassSourceCode, locals: locals.Handle); + ExtraBaseTypeProvider.ExtraBase = new PyType(locals[InheritanceTestBaseClassWrapper.ClassName]); + var baseTypeProviders = PythonEngine.InteropConfiguration.PythonBaseTypeProviders; + baseTypeProviders.Add(new ExtraBaseTypeProvider()); + baseTypeProviders.Add(new NoEffectBaseTypeProvider()); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void ExtraBase_PassesInstanceCheck() + { + var inherited = new Inherited(); + bool properlyInherited = PyIsInstance(inherited, ExtraBaseTypeProvider.ExtraBase); + Assert.IsTrue(properlyInherited); + } + + static dynamic PyIsInstance => PythonEngine.Eval("isinstance"); + + [Test] + public void InheritingWithExtraBase_CreatesNewClass() + { + PyObject a = ExtraBaseTypeProvider.ExtraBase; + var inherited = new Inherited(); + PyObject inheritedClass = inherited.ToPython().GetAttr("__class__"); + Assert.IsFalse(PythonReferenceComparer.Instance.Equals(a, inheritedClass)); + } + + [Test] + public void InheritedFromInheritedClassIsSelf() + { + using var scope = Py.CreateScope(); + scope.Exec($"from {typeof(Inherited).Namespace} import {nameof(Inherited)}"); + scope.Exec($"class B({nameof(Inherited)}): pass"); + PyObject b = scope.Eval("B"); + PyObject bInstance = b.Invoke(); + PyObject bInstanceClass = bInstance.GetAttr("__class__"); + Assert.IsTrue(PythonReferenceComparer.Instance.Equals(b, bInstanceClass)); + } + + [Test] + public void Grandchild_PassesExtraBaseInstanceCheck() + { + using var scope = Py.CreateScope(); + scope.Exec($"from {typeof(Inherited).Namespace} import {nameof(Inherited)}"); + scope.Exec($"class B({nameof(Inherited)}): pass"); + PyObject b = scope.Eval("B"); + PyObject bInst = b.Invoke(); + bool properlyInherited = PyIsInstance(bInst, ExtraBaseTypeProvider.ExtraBase); + Assert.IsTrue(properlyInherited); + } + + [Test] + public void CallInheritedClrMethod_WithExtraPythonBase() + { + var instance = new Inherited().ToPython(); + string result = instance.InvokeMethod(nameof(PythonWrapperBase.WrapperBaseMethod)).As(); + Assert.AreEqual(result, nameof(PythonWrapperBase.WrapperBaseMethod)); + } + + [Test] + public void CallExtraBaseMethod() + { + var instance = new Inherited(); + using var scope = Py.CreateScope(); + scope.Set(nameof(instance), instance); + int actual = instance.ToPython().InvokeMethod("callVirt").As(); + Assert.AreEqual(expected: Inherited.OverridenVirtValue, actual); + } + + [Test] + public void SetAdHocAttributes_WhenExtraBasePresent() + { + var instance = new Inherited(); + using var scope = Py.CreateScope(); + scope.Set(nameof(instance), instance); + scope.Exec($"super({nameof(instance)}.__class__, {nameof(instance)}).set_x_to_42()"); + int actual = scope.Eval($"{nameof(instance)}.{nameof(Inherited.XProp)}"); + Assert.AreEqual(expected: Inherited.X, actual); + } + } + + class ExtraBaseTypeProvider : IPythonBaseTypeProvider + { + internal static PyType ExtraBase; + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + if (type == typeof(InheritanceTestBaseClassWrapper)) + { + return new[] { PyType.Get(type.BaseType), ExtraBase }; + } + return existingBases; + } + } + + class NoEffectBaseTypeProvider : IPythonBaseTypeProvider + { + public IEnumerable GetBaseTypes(Type type, IList existingBases) + => existingBases; + } + + public class PythonWrapperBase + { + public string WrapperBaseMethod() => nameof(WrapperBaseMethod); + } + + public class InheritanceTestBaseClassWrapper : PythonWrapperBase + { + public const string ClassName = "InheritanceTestBaseClass"; + public const string ClassSourceCode = "class " + ClassName + +@": + def virt(self): + return 42 + def set_x_to_42(self): + self.XProp = 42 + def callVirt(self): + return self.virt() + def __getattr__(self, name): + return '__getattr__:' + name + def __setattr__(self, name, value): + value[name] = name +" + ClassName + " = " + ClassName + "\n"; + } + + public class Inherited : InheritanceTestBaseClassWrapper + { + public const int OverridenVirtValue = -42; + public const int X = 42; + readonly Dictionary extras = new Dictionary(); + public int virt() => OverridenVirtValue; + public int XProp + { + get + { + using (var scope = Py.CreateScope()) + { + scope.Set("this", this); + try + { + return scope.Eval($"super(this.__class__, this).{nameof(XProp)}"); + } + catch (PythonException ex) when (ex.Type.Handle == Exceptions.AttributeError) + { + if (this.extras.TryGetValue(nameof(this.XProp), out object value)) + return (int)value; + throw; + } + } + } + set => this.extras[nameof(this.XProp)] = value; + } + } +} diff --git a/src/runtime/DefaultBaseTypeProvider.cs b/src/runtime/DefaultBaseTypeProvider.cs new file mode 100644 index 000000000..92acb47cf --- /dev/null +++ b/src/runtime/DefaultBaseTypeProvider.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace Python.Runtime +{ + /// Minimal Python base type provider + public sealed class DefaultBaseTypeProvider : IPythonBaseTypeProvider + { + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + if (existingBases is null) + throw new ArgumentNullException(nameof(existingBases)); + if (existingBases.Count > 0) + throw new ArgumentException("To avoid confusion, this type provider requires the initial set of base types to be empty"); + + return new[] { new PyType(GetBaseType(type)) }; + } + + static BorrowedReference GetBaseType(Type type) + { + if (type == typeof(Exception)) + return new BorrowedReference(Exceptions.Exception); + + return type.BaseType is not null + ? ClassManager.GetClass(type.BaseType).ObjectReference + : new BorrowedReference(Runtime.PyBaseObjectType); + } + + DefaultBaseTypeProvider(){} + public static DefaultBaseTypeProvider Instance { get; } = new DefaultBaseTypeProvider(); + } +} diff --git a/src/runtime/IPythonBaseTypeProvider.cs b/src/runtime/IPythonBaseTypeProvider.cs new file mode 100644 index 000000000..14e65afdc --- /dev/null +++ b/src/runtime/IPythonBaseTypeProvider.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Python.Runtime +{ + public interface IPythonBaseTypeProvider + { + /// + /// Get Python types, that should be presented to Python as the base types + /// for the specified .NET type. + /// + IEnumerable GetBaseTypes(Type type, IList existingBases); + } +} diff --git a/src/runtime/InteropConfiguration.cs b/src/runtime/InteropConfiguration.cs new file mode 100644 index 000000000..6853115fe --- /dev/null +++ b/src/runtime/InteropConfiguration.cs @@ -0,0 +1,25 @@ +namespace Python.Runtime +{ + using System; + using System.Collections.Generic; + + public sealed class InteropConfiguration + { + internal readonly PythonBaseTypeProviderGroup pythonBaseTypeProviders + = new PythonBaseTypeProviderGroup(); + + /// Enables replacing base types of CLR types as seen from Python + public IList PythonBaseTypeProviders => this.pythonBaseTypeProviders; + + public static InteropConfiguration MakeDefault() + { + return new InteropConfiguration + { + PythonBaseTypeProviders = + { + DefaultBaseTypeProvider.Instance, + }, + }; + } + } +} diff --git a/src/runtime/PythonBaseTypeProviderGroup.cs b/src/runtime/PythonBaseTypeProviderGroup.cs new file mode 100644 index 000000000..d25ae473a --- /dev/null +++ b/src/runtime/PythonBaseTypeProviderGroup.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Python.Runtime +{ + class PythonBaseTypeProviderGroup : List, IPythonBaseTypeProvider + { + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + if (existingBases is null) + throw new ArgumentNullException(nameof(existingBases)); + + foreach (var provider in this) + { + existingBases = provider.GetBaseTypes(type, existingBases).ToList(); + } + + return existingBases; + } + } +} diff --git a/src/runtime/PythonReferenceComparer.cs b/src/runtime/PythonReferenceComparer.cs new file mode 100644 index 000000000..d05e5191f --- /dev/null +++ b/src/runtime/PythonReferenceComparer.cs @@ -0,0 +1,22 @@ +#nullable enable +using System.Collections.Generic; + +namespace Python.Runtime +{ + /// + /// Compares Python object wrappers by Python object references. + /// Similar to but for Python objects + /// + public sealed class PythonReferenceComparer : IEqualityComparer + { + public static PythonReferenceComparer Instance { get; } = new PythonReferenceComparer(); + public bool Equals(PyObject? x, PyObject? y) + { + return x?.Handle == y?.Handle; + } + + public int GetHashCode(PyObject obj) => obj.Handle.GetHashCode(); + + private PythonReferenceComparer() { } + } +} diff --git a/src/runtime/StolenReference.cs b/src/runtime/StolenReference.cs index 1130cff06..415fedc7f 100644 --- a/src/runtime/StolenReference.cs +++ b/src/runtime/StolenReference.cs @@ -35,6 +35,13 @@ public override bool Equals(object obj) [Pure] public override int GetHashCode() => Pointer.GetHashCode(); + + [Pure] + public static StolenReference DangerousFromPointer(IntPtr ptr) + { + if (ptr == IntPtr.Zero) throw new ArgumentNullException(nameof(ptr)); + return new StolenReference(ptr); + } } static class StolenReferenceExtensions diff --git a/src/runtime/classmanager.cs b/src/runtime/classmanager.cs index 811b802c9..18b9f6911 100644 --- a/src/runtime/classmanager.cs +++ b/src/runtime/classmanager.cs @@ -252,6 +252,14 @@ private static ClassBase CreateClass(Type type) private static void InitClassBase(Type type, ClassBase impl) { + // Ensure, that matching Python type exists first. + // It is required for self-referential classes + // (e.g. with members, that refer to the same class) + var pyType = TypeManager.GetOrCreateClass(type); + + // Set the handle attributes on the implementing instance. + impl.tpHandle = impl.pyHandle = pyType.Handle; + // First, we introspect the managed type and build some class // information, including generating the member descriptors // that we'll be putting in the Python class __dict__. @@ -261,12 +269,12 @@ private static void InitClassBase(Type type, ClassBase impl) impl.indexer = info.indexer; impl.richcompare = new Dictionary(); - // Now we allocate the Python type object to reflect the given + // Now we force initialize the Python type object to reflect the given // managed type, filling the Python type slots with thunks that // point to the managed methods providing the implementation. - var pyType = TypeManager.GetType(impl, type); + TypeManager.GetOrInitializeClass(impl, type); // Finally, initialize the class __dict__ and return the object. using var dict = Runtime.PyObject_GenericGetDict(pyType.Reference); diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index f6340a59c..13df54a5d 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -26,6 +26,7 @@ public static ShutdownMode ShutdownMode private static IntPtr _pythonHome = IntPtr.Zero; private static IntPtr _programName = IntPtr.Zero; private static IntPtr _pythonPath = IntPtr.Zero; + private static InteropConfiguration interopConfiguration = InteropConfiguration.MakeDefault(); public PythonEngine() { @@ -68,6 +69,18 @@ internal static DelegateManager DelegateManager } } + public static InteropConfiguration InteropConfiguration + { + get => interopConfiguration; + set + { + if (IsInitialized) + throw new NotSupportedException("Changing interop configuration when engine is running is not supported"); + + interopConfiguration = value ?? throw new ArgumentNullException(nameof(InteropConfiguration)); + } + } + public static string ProgramName { get @@ -334,6 +347,8 @@ public static void Shutdown(ShutdownMode mode) PyObjectConversions.Reset(); initialized = false; + + InteropConfiguration = InteropConfiguration.MakeDefault(); } /// @@ -563,7 +578,7 @@ public static ulong GetPythonThreadID() /// Interrupts the execution of a thread. /// /// The Python thread ID. - /// The number of thread states modified; this is normally one, but will be zero if the thread id isn’t found. + /// The number of thread states modified; this is normally one, but will be zero if the thread id is not found. public static int Interrupt(ulong pythonThreadID) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/src/runtime/pytype.cs b/src/runtime/pytype.cs index e9c80ebf3..afa957ecb 100644 --- a/src/runtime/pytype.cs +++ b/src/runtime/pytype.cs @@ -44,6 +44,15 @@ public string Name } } + /// Returns true when type is fully initialized + public bool IsReady => Flags.HasFlag(TypeFlags.Ready); + + internal TypeFlags Flags + { + get => (TypeFlags)Util.ReadCLong(Handle, TypeOffset.tp_flags); + set => Util.WriteCLong(Handle, TypeOffset.tp_flags, (long)value); + } + /// Checks if specified object is a Python type. public static bool IsType(PyObject value) { @@ -52,6 +61,39 @@ public static bool IsType(PyObject value) return Runtime.PyType_Check(value.obj); } + /// + /// Gets , which represents the specified CLR type. + /// Must be called after the CLR type was mapped to its Python type. + /// + internal static PyType Get(Type clrType) + { + if (clrType == null) + { + throw new ArgumentNullException(nameof(clrType)); + } + + ClassBase pyClass = ClassManager.GetClass(clrType); + return new PyType(pyClass.ObjectReference); + } + + internal BorrowedReference BaseReference + { + get + { + return new(Marshal.ReadIntPtr(Handle, TypeOffset.tp_base)); + } + set + { + var old = BaseReference.DangerousGetAddressOrNull(); + IntPtr @new = value.DangerousGetAddress(); + + Runtime.XIncref(@new); + Marshal.WriteIntPtr(Handle, TypeOffset.tp_base, @new); + + Runtime.XDecref(old); + } + } + internal IntPtr GetSlot(TypeSlotID slot) { IntPtr result = Runtime.PyType_GetSlot(this.Reference, slot); diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index e1bfe6aef..8d5600e4f 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -19,6 +19,7 @@ internal class TypeManager { internal static IntPtr subtype_traverse; internal static IntPtr subtype_clear; + internal static IPythonBaseTypeProvider pythonBaseTypeProvider; private const BindingFlags tbFlags = BindingFlags.Public | BindingFlags.Static; private static Dictionary cache = new(); @@ -41,6 +42,7 @@ internal static void Initialize() subtype_traverse = Marshal.ReadIntPtr(type, TypeOffset.tp_traverse); subtype_clear = Marshal.ReadIntPtr(type, TypeOffset.tp_clear); Runtime.XDecref(type); + pythonBaseTypeProvider = PythonEngine.InteropConfiguration.pythonBaseTypeProviders; } internal static void RemoveTypes() @@ -115,18 +117,36 @@ internal static PyType GetType(Type type) internal static BorrowedReference GetTypeReference(Type type) => GetType(type).Reference; + /// + /// Get the fully initialized Python type that reflects the given CLR type. + /// The given ManagedType instance is a managed object that implements + /// the appropriate semantics in Python for the reflected managed type. + /// + internal static PyType GetOrInitializeClass(ClassBase obj, Type type) + { + var pyType = GetOrCreateClass(type); + if (!pyType.IsReady) + { + InitializeClass(pyType, obj, type); + _slotsImpls.Add(type, obj.GetType()); + } + return pyType; + } + /// /// Get the Python type that reflects the given CLR type. /// The given ManagedType instance is a managed object that implements /// the appropriate semantics in Python for the reflected managed type. /// - internal static PyType GetType(ClassBase obj, Type type) + /// + /// Returned might be partially initialized. + /// If you need fully initialized type, use + /// + internal static PyType GetOrCreateClass(Type type) { if (!cache.TryGetValue(type, out var pyType)) { - pyType = CreateType(obj, type); - cache[type] = pyType; - _slotsImpls.Add(type, obj.GetType()); + pyType = CreateClass(type); } return pyType; } @@ -143,11 +163,14 @@ internal static PyType GetType(ClassBase obj, Type type) internal static unsafe PyType CreateType(Type impl) { IntPtr type = AllocateTypeObject(impl.Name, metatype: Runtime.PyCLRMetaType); + // TODO: use PyType(TypeSpec) constructor + var pyType = new PyType(StolenReference.DangerousFromPointer(type)); + IntPtr base_ = impl == typeof(CLRModule) ? Runtime.PyModuleType : Runtime.PyBaseObjectType; - int newFieldOffset = InheritOrAllocateStandardFields(type, base_); + int newFieldOffset = InheritOrAllocateStandardFields(type, new BorrowedReference(base_)); int tp_clr_inst_offset = newFieldOffset; newFieldOffset += IntPtr.Size; @@ -161,18 +184,14 @@ internal static unsafe PyType CreateType(Type impl) SlotsHolder slotsHolder = CreateSolotsHolder(type); InitializeSlots(type, impl, slotsHolder); - var flags = TypeFlags.Default | TypeFlags.HasClrInstance | - TypeFlags.HeapType | TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); + pyType.Flags = TypeFlags.Default | TypeFlags.HasClrInstance | + TypeFlags.HeapType | TypeFlags.HaveGC; if (Runtime.PyType_Ready(type) != 0) { throw PythonException.ThrowLastAsClrException(); } - // TODO: use PyType(TypeSpec) constructor - var pyType = new PyType(new BorrowedReference(type)); - Runtime.XDecref(type); NewReference dict = Runtime.PyObject_GenericGetDict(pyType.Reference); var mod = NewReference.DangerousFromPointer(Runtime.PyString_FromString("CLR")); @@ -190,7 +209,7 @@ internal static unsafe PyType CreateType(Type impl) } - internal static PyType CreateType(ClassBase impl, Type clrType) + static PyType CreateClass(Type clrType) { // Cleanup the type name to get rid of funny nested type names. string name = $"clr.{clrType.FullName}"; @@ -205,28 +224,54 @@ internal static PyType CreateType(ClassBase impl, Type clrType) name = name.Substring(i + 1); } - IntPtr base_ = Runtime.PyBaseObjectType; - if (clrType == typeof(Exception)) + using var baseTuple = GetBaseTypeTuple(clrType); + + IntPtr type = AllocateTypeObject(name, Runtime.PyCLRMetaType); + var pyType = new PyType(StolenReference.DangerousFromPointer(type)); + pyType.Flags = TypeFlags.Default + | TypeFlags.HasClrInstance + | TypeFlags.HeapType + | TypeFlags.BaseType + | TypeFlags.HaveGC; + + cache.Add(clrType, pyType); + try { - base_ = Exceptions.Exception; + InitializeBases(pyType, baseTuple); + // core fields must be initialized in partically constructed classes, + // otherwise it would be impossible to manipulate GCHandle and check type size + InitializeCoreFields(pyType); } - else if (clrType.BaseType != null) + catch { - ClassBase bc = ClassManager.GetClass(clrType.BaseType); - if (bc.ObjectReference != null) - { - // there are cases when base class has not been fully initialized yet (nested types) - base_ = bc.pyHandle; - } + cache.Remove(clrType); + throw; } - IntPtr type = AllocateTypeObject(name, Runtime.PyCLRMetaType); + return pyType; + } - int newFieldOffset = InheritOrAllocateStandardFields(type, base_); + static BorrowedReference InitializeBases(PyType pyType, PyTuple baseTuple) + { + Debug.Assert(baseTuple.Length() > 0); + var primaryBase = baseTuple[0].Reference; + pyType.BaseReference = primaryBase; + + if (baseTuple.Length() > 1) + { + Marshal.WriteIntPtr(pyType.Handle, TypeOffset.tp_bases, baseTuple.NewReferenceOrNull().DangerousMoveToPointer()); + } + return primaryBase; + } + + static void InitializeCoreFields(PyType pyType) + { + IntPtr type = pyType.Handle; + int newFieldOffset = InheritOrAllocateStandardFields(type); - if (ManagedType.IsManagedType(new BorrowedReference(base_))) + if (ManagedType.IsManagedType(pyType.BaseReference)) { - int baseClrInstOffset = Marshal.ReadInt32(base_, ManagedType.Offsets.tp_clr_inst_offset); + int baseClrInstOffset = Marshal.ReadInt32(pyType.BaseReference.DangerousGetAddress(), ManagedType.Offsets.tp_clr_inst_offset); Marshal.WriteInt32(type, ManagedType.Offsets.tp_clr_inst_offset, baseClrInstOffset); } else @@ -239,6 +284,11 @@ internal static PyType CreateType(ClassBase impl, Type clrType) Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size); Marshal.WriteIntPtr(type, TypeOffset.tp_itemsize, IntPtr.Zero); + } + + static void InitializeClass(PyType pyType, ClassBase impl, Type clrType) + { + IntPtr type = pyType.Handle; // we want to do this after the slot stuff above in case the class itself implements a slot method SlotsHolder slotsHolder = CreateSolotsHolder(type); @@ -271,19 +321,6 @@ internal static PyType CreateType(ClassBase impl, Type clrType) } } - if (base_ != IntPtr.Zero) - { - Marshal.WriteIntPtr(type, TypeOffset.tp_base, base_); - Runtime.XIncref(base_); - } - - const TypeFlags flags = TypeFlags.Default - | TypeFlags.HasClrInstance - | TypeFlags.HeapType - | TypeFlags.BaseType - | TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); - OperatorMethod.FixupSlots(type, clrType); // Leverage followup initialization from the Python runtime. Note // that the type of the new type must PyType_Type at the time we @@ -311,19 +348,22 @@ internal static PyType CreateType(ClassBase impl, Type clrType) impl.pyHandle = type; //DebugUtil.DumpType(type); - var pyType = new PyType(new BorrowedReference(type)); - Runtime.XDecref(type); - return pyType; } - static int InheritOrAllocateStandardFields(IntPtr type, IntPtr @base) + static int InheritOrAllocateStandardFields(IntPtr type) { - int baseSize = Marshal.ReadInt32(@base, TypeOffset.tp_basicsize); + var @base = new BorrowedReference(Marshal.ReadIntPtr(type, TypeOffset.tp_base)); + return InheritOrAllocateStandardFields(type, @base); + } + static int InheritOrAllocateStandardFields(IntPtr type, BorrowedReference @base) + { + IntPtr baseAddress = @base.DangerousGetAddress(); + int baseSize = Marshal.ReadInt32(baseAddress, TypeOffset.tp_basicsize); int newFieldOffset = baseSize; void InheritOrAllocate(int typeField) { - int value = Marshal.ReadInt32(@base, typeField); + int value = Marshal.ReadInt32(baseAddress, typeField); if (value == 0) { Marshal.WriteIntPtr(type, typeField, new IntPtr(newFieldOffset)); @@ -341,6 +381,19 @@ void InheritOrAllocate(int typeField) return newFieldOffset; } + static PyTuple GetBaseTypeTuple(Type clrType) + { + var bases = pythonBaseTypeProvider + .GetBaseTypes(clrType, new PyType[0]) + ?.ToArray(); + if (bases is null || bases.Length == 0) + { + throw new InvalidOperationException("At least one base type must be specified"); + } + + return new PyTuple(bases); + } + internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, BorrowedReference dictRef) { // Utility to create a subtype of a managed type with the ability for the @@ -395,7 +448,7 @@ internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, Borrow // create the new ManagedType and python type ClassBase subClass = ClassManager.GetClass(subType); - IntPtr py_type = GetType(subClass, subType).Handle; + IntPtr py_type = GetOrInitializeClass(subClass, subType).Handle; // by default the class dict will have all the C# methods in it, but as this is a // derived class we want the python overrides in there instead if they exist.