1783 Implement Interface And Inherit Class (#2028) · pythonnet/pythonnet@a404d6e
11using System;
2+using System.Collections.Generic;
23using System.Diagnostics;
4+using System.Linq;
5+using System.Reflection;
36using System.Runtime.InteropServices;
47using System.Runtime.Serialization;
58@@ -79,41 +82,80 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args,
7982BorrowedReference bases = Runtime.PyTuple_GetItem(args, 1);
8083BorrowedReference dict = Runtime.PyTuple_GetItem(args, 2);
818482-// 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>();
868787-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>();
919192-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)
9694{
97-return Exceptions.RaiseTypeError("invalid metatype");
95+return Exceptions.RaiseTypeError("zero base classes ");
9896}
9997100-// 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++)
10499{
105-try
100+var baseTypeIt = Runtime.PyTuple_GetItem(bases, (int)i);
101+102+if (GetManagedObject(baseTypeIt) is ClassBase classBaseIt)
106103{
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)
108109{
109-return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed");
110+interfaces.Add(classBaseIt.type.Value);
110111}
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.");
111120}
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())
113151{
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");
115153}
116154}
155+catch (SerializationException)
156+{
157+return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted");
158+}
117159118160BorrowedReference slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__);
119161if (slots != null)
@@ -130,10 +172,12 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args,
130172using var clsDict = new PyDict(dict);
131173if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__"))
132174{
133-return TypeManager.CreateSubType(name, base_type, clsDict);
175+return TypeManager.CreateSubType(name, baseTypes[0], interfaces, clsDict);
134176}
135177}
136178179+var base_type = Runtime.PyTuple_GetItem(bases, 0);
180+137181// otherwise just create a basic type without reflecting back into the managed side.
138182IntPtr func = Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new);
139183NewReference type = NativeCall.Call_3(func, tp, args, kw);