Skip to content

Commit a404d6e

Browse files
authored
1783 Implement Interface And Inherit Class (#2028)
* 1783 Implement Interface And Inherit Class Added support for multiple inheritance when inheriting from one base class and/or multiple interfaces. Added a unit test verifying that it works with a simple class and interface. This unit test would previously have failed since multiple types are in the class super class list.
1 parent c2fa467 commit a404d6e

File tree

9 files changed

+105
-43
lines changed

9 files changed

+105
-43
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1616
- Fixed error occuring when inheriting a class containing a virtual generic method.
1717
- Make a second call to `pythonnet.load` a no-op, as it was intended.
1818

19+
- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces.
20+
1921
## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03
2022

2123
### Added

src/python_tests_runner/PythonTestRunner.cs

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ static IEnumerable<string[]> PythonTestCases()
3535
// Add the test that you want to debug here.
3636
yield return new[] { "test_indexer", "test_boolean_indexer" };
3737
yield return new[] { "test_delegate", "test_bool_delegate" };
38+
yield return new[] { "test_subclass", "test_implement_interface_and_class" };
3839
}
3940

4041
/// <summary>

src/runtime/StateSerialization/MaybeType.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal struct MaybeType : ISerializable
1515
const string SerializationName = "n";
1616
readonly string name;
1717
readonly Type type;
18-
18+
1919
public string DeletedMessage
2020
{
2121
get
@@ -61,4 +61,4 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext
6161
serializationInfo.AddValue(SerializationName, name);
6262
}
6363
}
64-
}
64+
}

src/runtime/TypeManager.cs

+5-12
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ static PyTuple GetBaseTypeTuple(Type clrType)
374374
return new PyTuple(bases);
375375
}
376376

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)
378378
{
379379
// Utility to create a subtype of a managed type with the ability for the
380380
// a python subtype able to override the managed implementation
@@ -415,17 +415,10 @@ internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedRe
415415
}
416416

417417
// 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);
429422
}
430423

431424
internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc)

src/runtime/Types/ClassDerived.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ internal static NewReference ToPython(IPythonDerivedType obj)
144144
/// </summary>
145145
internal static Type CreateDerivedType(string name,
146146
Type baseType,
147+
IList<Type> typeInterfaces,
147148
BorrowedReference py_dict,
148149
string? namespaceStr,
149150
string? assemblyName,
@@ -163,7 +164,9 @@ internal static Type CreateDerivedType(string name,
163164
ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName);
164165

165166
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);
167170

168171
// if the base type is an interface then use System.Object as the base class
169172
// 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,
214217
}
215218
}
216219

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()));
219223
var virtualMethods = new HashSet<string>();
220224
foreach (MethodInfo method in methods)
221225
{

src/runtime/Types/MetaType.cs

+67-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Reflection;
36
using System.Runtime.InteropServices;
47
using System.Runtime.Serialization;
58

@@ -79,41 +82,80 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args,
7982
BorrowedReference bases = Runtime.PyTuple_GetItem(args, 1);
8083
BorrowedReference dict = Runtime.PyTuple_GetItem(args, 2);
8184

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>();
8687

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>();
9191

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)
9694
{
97-
return Exceptions.RaiseTypeError("invalid metatype");
95+
return Exceptions.RaiseTypeError("zero base classes ");
9896
}
9997

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++)
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+
}
117159

118160
BorrowedReference slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__);
119161
if (slots != null)
@@ -130,10 +172,12 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args,
130172
using var clsDict = new PyDict(dict);
131173
if (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
}
136178

179+
var base_type = Runtime.PyTuple_GetItem(bases, 0);
180+
137181
// otherwise just create a basic type without reflecting back into the managed side.
138182
IntPtr func = Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new);
139183
NewReference type = NativeCall.Call_3(func, tp, args, kw);

src/runtime/Types/ReflectedClrType.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,15 @@ internal void Restore(ClassBase cb)
6868
TypeManager.InitializeClass(this, cb, cb.type.Value);
6969
}
7070

71-
internal static NewReference CreateSubclass(ClassBase baseClass,
71+
internal static NewReference CreateSubclass(ClassBase baseClass, IList<Type> interfaces,
7272
string name, string? assembly, string? ns,
7373
BorrowedReference dict)
7474
{
7575
try
7676
{
7777
Type subType = ClassDerivedObject.CreateDerivedType(name,
7878
baseClass.type.Value,
79+
interfaces,
7980
dict,
8081
ns,
8182
assembly);

src/testing/classtest.cs

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections;
23

34
namespace Python.Test
@@ -59,4 +60,15 @@ public ClassCtorTest2(string v)
5960
internal class InternalClass
6061
{
6162
}
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+
}
6274
}

tests/test_subclass.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import System
1010
import pytest
1111
from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest,
12-
FunctionsTest, IGenericInterface, GenericVirtualMethodTest)
12+
FunctionsTest, IGenericInterface, GenericVirtualMethodTest, SimpleClass, ISayHello1)
1313
from System.Collections.Generic import List
1414

1515

@@ -338,4 +338,9 @@ class OverloadingSubclass2(OverloadingSubclass):
338338
obj = OverloadingSubclass2()
339339
assert obj.VirtMethod[int](5) == 5
340340

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()

0 commit comments

Comments
 (0)