Skip to content

Commit 46de53e

Browse files
committed
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 46de53e

File tree

10 files changed

+84
-46
lines changed

10 files changed

+84
-46
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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/Runtime.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,12 @@ internal static void SetNoSiteFlag()
18361836
return *Delegates.Py_NoSiteFlag;
18371837
});
18381838
}
1839+
1840+
internal static uint PyTuple_GetSize(BorrowedReference tuple)
1841+
{
1842+
IntPtr r = Delegates.PyTuple_Size(tuple);
1843+
return (uint)r.ToInt32();
1844+
}
18391845
}
18401846

18411847
internal class BadPythonDllException : MissingMethodException

src/runtime/StateSerialization/MaybeType.cs

Lines changed: 2 additions & 2 deletions
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

Lines changed: 7 additions & 12 deletions
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, IList<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,12 @@ 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+
var baseClass = py_base_type.FirstOrDefault();
419+
420+
return ReflectedClrType.CreateSubclass(baseClass, interfaces, name,
421+
ns: (string?)namespaceStr,
422+
assembly: (string?)assembly,
423+
dict: dictRef);
429424
}
430425

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

src/runtime/Types/ClassDerived.cs

Lines changed: 7 additions & 3 deletions
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

Lines changed: 38 additions & 26 deletions
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,49 +82,56 @@ 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>();
87+
var baseType = new List<ClassBase>();
8688

87-
if (Runtime.PyTuple_Size(bases) != 1)
89+
var cnt = Runtime.PyTuple_GetSize(bases);
90+
91+
for (uint i = 0; i < cnt; i++)
8892
{
89-
return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes");
93+
var base_type2 = Runtime.PyTuple_GetItem(bases, (int)i);
94+
var cb2 = (ClassBase) GetManagedObject(base_type2);
95+
if (cb2 != null)
96+
{
97+
if (cb2.type.Valid && cb2.type.Value.IsInterface)
98+
interfaces.Add(cb2.type.Value);
99+
else baseType.Add(cb2);
100+
}
90101
}
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))
102+
// if the base type count is 0, there might still be interfaces to implement.
103+
if (baseType.Count == 0)
96104
{
97-
return Exceptions.RaiseTypeError("invalid metatype");
105+
baseType.Add(new ClassBase(typeof(object)));
98106
}
99107

100-
// Ensure that the reflected type is appropriate for subclassing,
101-
// disallowing subclassing of delegates, enums and array types.
108+
// Multiple inheritance is not supported, unless the other types are interfaces
109+
if (baseType.Count > 1)
110+
{
111+
return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes");
112+
}
102113

103-
if (GetManagedObject(base_type) is ClassBase cb)
114+
var cb = baseType[0];
115+
try
104116
{
105-
try
106-
{
107-
if (!cb.CanSubclass())
108-
{
109-
return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed");
110-
}
111-
}
112-
catch (SerializationException)
117+
if (!cb.CanSubclass())
113118
{
114-
return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted");
119+
return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed");
115120
}
116121
}
122+
catch (SerializationException)
123+
{
124+
return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted");
125+
}
117126

118127
BorrowedReference slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__);
119128
if (slots != null)
120129
{
121130
return Exceptions.RaiseTypeError("subclasses of managed classes do not support __slots__");
122131
}
123132

124-
// If __assembly__ or __namespace__ are in the class dictionary then create
133+
// If the base class has a parameterless constructor, or
134+
// if __assembly__ or __namespace__ are in the class dictionary then create
125135
// a managed sub type.
126136
// This creates a new managed type that can be used from .net to call back
127137
// into python.
@@ -130,10 +140,12 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args,
130140
using var clsDict = new PyDict(dict);
131141
if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__"))
132142
{
133-
return TypeManager.CreateSubType(name, base_type, clsDict);
143+
return TypeManager.CreateSubType(name, baseType, interfaces, clsDict);
134144
}
135145
}
136146

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

src/runtime/Types/ReflectedClrType.cs

Lines changed: 2 additions & 1 deletion
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

Lines changed: 12 additions & 0 deletions
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

Lines changed: 7 additions & 2 deletions
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)