| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c2fa467 commit a404d6e
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,6 +16,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. | |||
| 16 | 16 | - Fixed error occuring when inheriting a class containing a virtual generic method. | |
| 17 | 17 | - Make a second call to `pythonnet.load` a no-op, as it was intended. | |
| 18 | 18 | ||
| 19 | + - Added support for multiple inheritance when inheriting from a class and/or multiple interfaces. | ||
| 20 | + | ||
| 19 | 21 | ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 | |
| 20 | 22 | ||
| 21 | 23 | ### Added | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,6 +35,7 @@ static IEnumerable<string[]> PythonTestCases() | |||
| 35 | 35 | // Add the test that you want to debug here. | |
| 36 | 36 | yield return new[] { "test_indexer", "test_boolean_indexer" }; | |
| 37 | 37 | yield return new[] { "test_delegate", "test_bool_delegate" }; | |
| 38 | + yield return new[] { "test_subclass", "test_implement_interface_and_class" }; | ||
| 38 | 39 | } | |
| 39 | 40 | ||
| 40 | 41 | /// <summary> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,7 +15,7 @@ internal struct MaybeType : ISerializable | |||
| 15 | 15 | const string SerializationName = "n"; | |
| 16 | 16 | readonly string name; | |
| 17 | 17 | readonly Type type; | |
| 18 | - | ||
| 18 | + | ||
| 19 | 19 | public string DeletedMessage | |
| 20 | 20 | { | |
| 21 | 21 | get | |
@@ -61,4 +61,4 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext | |||
| 61 | 61 | serializationInfo.AddValue(SerializationName, name); | |
| 62 | 62 | } | |
| 63 | 63 | } | |
| 64 | - } | ||
| 64 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -374,7 +374,7 @@ static PyTuple GetBaseTypeTuple(Type clrType) | |||
| 374 | 374 | return new PyTuple(bases); | |
| 375 | 375 | } | |
| 376 | 376 | ||
| 377 | - internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedReference py_base_type, BorrowedReference dictRef) | ||
| 377 | + internal static NewReference CreateSubType(BorrowedReference py_name, ClassBase py_base_type, IList<Type> interfaces, BorrowedReference dictRef) | ||
| 378 | 378 | { | |
| 379 | 379 | // Utility to create a subtype of a managed type with the ability for the | |
| 380 | 380 | // a python subtype able to override the managed implementation | |
@@ -415,17 +415,10 @@ internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedRe | |||
| 415 | 415 | } | |
| 416 | 416 | ||
| 417 | 417 | // create the new managed type subclassing the base managed type | |
| 418 | - if (ManagedType.GetManagedObject(py_base_type) is ClassBase baseClass) | ||
| 419 | - { | ||
| 420 | - return ReflectedClrType.CreateSubclass(baseClass, name, | ||
| 421 | - ns: (string?)namespaceStr, | ||
| 422 | - assembly: (string?)assembly, | ||
| 423 | - dict: dictRef); | ||
| 424 | - } | ||
| 425 | - else | ||
| 426 | - { | ||
| 427 | - return Exceptions.RaiseTypeError("invalid base class, expected CLR class type"); | ||
| 428 | - } | ||
| 418 | + return ReflectedClrType.CreateSubclass(py_base_type, interfaces, name, | ||
| 419 | + ns: (string?)namespaceStr, | ||
| 420 | + assembly: (string?)assembly, | ||
| 421 | + dict: dictRef); | ||
| 429 | 422 | } | |
| 430 | 423 | ||
| 431 | 424 | internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -144,6 +144,7 @@ internal static NewReference ToPython(IPythonDerivedType obj) | |||
| 144 | 144 | /// </summary> | |
| 145 | 145 | internal static Type CreateDerivedType(string name, | |
| 146 | 146 | Type baseType, | |
| 147 | + IList<Type> typeInterfaces, | ||
| 147 | 148 | BorrowedReference py_dict, | |
| 148 | 149 | string? namespaceStr, | |
| 149 | 150 | string? assemblyName, | |
@@ -163,7 +164,9 @@ internal static Type CreateDerivedType(string name, | |||
| 163 | 164 | ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName); | |
| 164 | 165 | ||
| 165 | 166 | Type baseClass = baseType; | |
| 166 | - var interfaces = new List<Type> { typeof(IPythonDerivedType) }; | ||
| 167 | + var interfaces = new HashSet<Type> { typeof(IPythonDerivedType) }; | ||
| 168 | + foreach(var interfaceType in typeInterfaces) | ||
| 169 | + interfaces.Add(interfaceType); | ||
| 167 | 170 | ||
| 168 | 171 | // if the base type is an interface then use System.Object as the base class | |
| 169 | 172 | // and add the base type to the list of interfaces this new class will implement. | |
@@ -214,8 +217,9 @@ internal static Type CreateDerivedType(string name, | |||
| 214 | 217 | } | |
| 215 | 218 | } | |
| 216 | 219 | ||
| 217 | - // override any virtual methods not already overridden by the properties above | ||
| 218 | - MethodInfo[] methods = baseType.GetMethods(); | ||
| 220 | + // override any virtual not already overridden by the properties above | ||
| 221 | + // also override any interface method. | ||
| 222 | + var methods = baseType.GetMethods().Concat(interfaces.SelectMany(x => x.GetMethods())); | ||
| 219 | 223 | var virtualMethods = new HashSet<string>(); | |
| 220 | 224 | foreach (MethodInfo method in methods) | |
| 221 | 225 | { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,8 @@ | |||
| 1 | 1 | using System; | |
| 2 | + using System.Collections.Generic; | ||
| 2 | 3 | using System.Diagnostics; | |
| 4 | + using System.Linq; | ||
| 5 | + using System.Reflection; | ||
| 3 | 6 | using System.Runtime.InteropServices; | |
| 4 | 7 | using System.Runtime.Serialization; | |
| 5 | 8 | ||
@@ -79,41 +82,80 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, | |||
| 79 | 82 | BorrowedReference bases = Runtime.PyTuple_GetItem(args, 1); | |
| 80 | 83 | BorrowedReference dict = Runtime.PyTuple_GetItem(args, 2); | |
| 81 | 84 | ||
| 82 | - // We do not support multiple inheritance, so the bases argument | ||
| 83 | - // should be a 1-item tuple containing the type we are subtyping. | ||
| 84 | - // That type must itself have a managed implementation. We check | ||
| 85 | - // that by making sure its metatype is the CLR metatype. | ||
| 85 | + // Extract interface types and base class types. | ||
| 86 | + var interfaces = new List<Type>(); | ||
| 86 | 87 | ||
| 87 | - if (Runtime.PyTuple_Size(bases) != 1) | ||
| 88 | - { | ||
| 89 | - return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes"); | ||
| 90 | - } | ||
| 88 | + // More than one base type case be declared, but an exception will be thrown | ||
| 89 | + // if more than one is a class/not an interface. | ||
| 90 | + var baseTypes = new List<ClassBase>(); | ||
| 91 | 91 | ||
| 92 | - BorrowedReference base_type = Runtime.PyTuple_GetItem(bases, 0); | ||
| 93 | - BorrowedReference mt = Runtime.PyObject_TYPE(base_type); | ||
| 94 | - | ||
| 95 | - if (!(mt == PyCLRMetaType || mt == Runtime.PyTypeType)) | ||
| 92 | + var baseClassCount = Runtime.PyTuple_Size(bases); | ||
| 93 | + if (baseClassCount == 0) | ||
| 96 | 94 | { | |
| 97 | - return Exceptions.RaiseTypeError("invalid metatype"); | ||
| 95 | + return Exceptions.RaiseTypeError("zero base classes "); | ||
| 98 | 96 | } | |
| 99 | 97 | ||
| 100 | - // Ensure that the reflected type is appropriate for subclassing, | ||
| 101 | - // disallowing subclassing of delegates, enums and array types. | ||
| 102 | - | ||
| 103 | - if (GetManagedObject(base_type) is ClassBase cb) | ||
| 98 | + for (nint i = 0; i < baseClassCount; i++) | ||
| 104 | 99 | { | |
| 105 | - try | ||
| 100 | + var baseTypeIt = Runtime.PyTuple_GetItem(bases, (int)i); | ||
| 101 | + | ||
| 102 | + if (GetManagedObject(baseTypeIt) is ClassBase classBaseIt) | ||
| 106 | 103 | { | |
| 107 | - if (!cb.CanSubclass()) | ||
| 104 | + if (!classBaseIt.type.Valid) | ||
| 105 | + { | ||
| 106 | + return Exceptions.RaiseTypeError("Invalid type used as a super type."); | ||
| 107 | + } | ||
| 108 | + if (classBaseIt.type.Value.IsInterface) | ||
| 108 | 109 | { | |
| 109 | - return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); | ||
| 110 | + interfaces.Add(classBaseIt.type.Value); | ||
| 110 | 111 | } | |
| 112 | + else | ||
| 113 | + { | ||
| 114 | + baseTypes.Add(classBaseIt); | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + else | ||
| 118 | + { | ||
| 119 | + return Exceptions.RaiseTypeError("Non .NET type used as super class for meta type. This is not supported."); | ||
| 111 | 120 | } | |
| 112 | - catch (SerializationException) | ||
| 121 | + } | ||
| 122 | + // if the base type count is 0, there might still be interfaces to implement. | ||
| 123 | + if (baseTypes.Count == 0) | ||
| 124 | + { | ||
| 125 | + baseTypes.Add(new ClassBase(typeof(object))); | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + // Multiple inheritance is not supported, unless the other types are interfaces | ||
| 129 | + if (baseTypes.Count > 1) | ||
| 130 | + { | ||
| 131 | + var types = string.Join(", ", baseTypes.Select(baseType => baseType.type.Value)); | ||
| 132 | + return Exceptions.RaiseTypeError($"Multiple inheritance with managed classes cannot be used. Types: {types} "); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + // check if the list of interfaces contains no duplicates. | ||
| 136 | + if (interfaces.Distinct().Count() != interfaces.Count) | ||
| 137 | + { | ||
| 138 | + // generate a string containing the problematic types. | ||
| 139 | + var duplicateTypes = interfaces.GroupBy(type => type) | ||
| 140 | + .Where(typeGroup => typeGroup.Count() > 1) | ||
| 141 | + .Select(typeGroup => typeGroup.Key); | ||
| 142 | + var duplicateTypesString = string.Join(", ", duplicateTypes); | ||
| 143 | + | ||
| 144 | + return Exceptions.RaiseTypeError($"An interface can only be implemented once. Duplicate types: {duplicateTypesString}"); | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + var cb = baseTypes[0]; | ||
| 148 | + try | ||
| 149 | + { | ||
| 150 | + if (!cb.CanSubclass()) | ||
| 113 | 151 | { | |
| 114 | - return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted"); | ||
| 152 | + return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); | ||
| 115 | 153 | } | |
| 116 | 154 | } | |
| 155 | + catch (SerializationException) | ||
| 156 | + { | ||
| 157 | + return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted"); | ||
| 158 | + } | ||
| 117 | 159 | ||
| 118 | 160 | BorrowedReference slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__); | |
| 119 | 161 | if (slots != null) | |
@@ -130,10 +172,12 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, | |||
| 130 | 172 | using var clsDict = new PyDict(dict); | |
| 131 | 173 | if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__")) | |
| 132 | 174 | { | |
| 133 | - return TypeManager.CreateSubType(name, base_type, clsDict); | ||
| 175 | + return TypeManager.CreateSubType(name, baseTypes[0], interfaces, clsDict); | ||
| 134 | 176 | } | |
| 135 | 177 | } | |
| 136 | 178 | ||
| 179 | + var base_type = Runtime.PyTuple_GetItem(bases, 0); | ||
| 180 | + | ||
| 137 | 181 | // otherwise just create a basic type without reflecting back into the managed side. | |
| 138 | 182 | IntPtr func = Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new); | |
| 139 | 183 | NewReference type = NativeCall.Call_3(func, tp, args, kw); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -68,14 +68,15 @@ internal void Restore(ClassBase cb) | |||
| 68 | 68 | TypeManager.InitializeClass(this, cb, cb.type.Value); | |
| 69 | 69 | } | |
| 70 | 70 | ||
| 71 | - internal static NewReference CreateSubclass(ClassBase baseClass, | ||
| 71 | + internal static NewReference CreateSubclass(ClassBase baseClass, IList<Type> interfaces, | ||
| 72 | 72 | string name, string? assembly, string? ns, | |
| 73 | 73 | BorrowedReference dict) | |
| 74 | 74 | { | |
| 75 | 75 | try | |
| 76 | 76 | { | |
| 77 | 77 | Type subType = ClassDerivedObject.CreateDerivedType(name, | |
| 78 | 78 | baseClass.type.Value, | |
| 79 | + interfaces, | ||
| 79 | 80 | dict, | |
| 80 | 81 | ns, | |
| 81 | 82 | assembly); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,3 +1,4 @@ | |||
| 1 | + using System; | ||
| 1 | 2 | using System.Collections; | |
| 2 | 3 | ||
| 3 | 4 | namespace Python.Test | |
@@ -59,4 +60,15 @@ public ClassCtorTest2(string v) | |||
| 59 | 60 | internal class InternalClass | |
| 60 | 61 | { | |
| 61 | 62 | } | |
| 63 | + | ||
| 64 | + public class SimpleClass | ||
| 65 | + { | ||
| 66 | + public static void TestObject(object obj) | ||
| 67 | + { | ||
| 68 | + if ((!(obj is ISayHello1 && obj is SimpleClass))) | ||
| 69 | + { | ||
| 70 | + throw new Exception("Expected ISayHello and SimpleClass instance"); | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + } | ||
| 62 | 74 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,7 +9,7 @@ | |||
| 9 | 9 | import System | |
| 10 | 10 | import pytest | |
| 11 | 11 | from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest, | |
| 12 | - FunctionsTest, IGenericInterface, GenericVirtualMethodTest) | ||
| 12 | + FunctionsTest, IGenericInterface, GenericVirtualMethodTest, SimpleClass, ISayHello1) | ||
| 13 | 13 | from System.Collections.Generic import List | |
| 14 | 14 | ||
| 15 | 15 | ||
@@ -338,4 +338,9 @@ class OverloadingSubclass2(OverloadingSubclass): | |||
| 338 | 338 | obj = OverloadingSubclass2() | |
| 339 | 339 | assert obj.VirtMethod[int](5) == 5 | |
| 340 | 340 | ||
| 341 | - | ||
| 341 | + def test_implement_interface_and_class(): | ||
| 342 | + class DualSubClass0(ISayHello1, SimpleClass): | ||
| 343 | + __namespace__ = "Test" | ||
| 344 | + def SayHello(self): | ||
| 345 | + return "hello" | ||
| 346 | + obj = DualSubClass0() | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments