Repository: pythonnet/pythonnet (language: python)

Issue:
Cannot implement an interface that extends another interface
### Environment

-   Pythonnet version: 3.0.0a2
-   Python version: 3.8.10
-   Operating System: Ubuntu 20.04.4 LTS (docker run --rm -it ubuntu:20.04)
-   .NET Runtime: .NET Core 6.0.202

### Details

-   Describe what you were trying to get done.

    Attempting to implement interfaces defined in .NET. Works fine for simple interfaces but I receive a weird error when the interface extends IDisposable

-   What commands did you run to trigger this issue? If you can provide a
    [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve)
    this will help us understand the issue.

```python
from clr_loader import get_coreclr
rt = get_coreclr("conf.json")
from pythonnet import set_runtime
set_runtime(rt)
import clr

import System
import System.Buffers


class MyDisposable(System.IDisposable):
    __namespace__ = "MyNamespace"

    def Dispose(self) -> None:
        print("Dispose")

md = MyDisposable()
md.Dispose()


class MyBuffer(System.Buffers.IMemoryOwner):
    __namespace__ = "MyNamespace"


    def Dispose(self) -> None:
        print("Dispose")


mb = MyBuffer()
mb.Dispose()    
```

conf.json:
```json
{
  "runtimeOptions": {
    "tfm": "net6.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "6.0.4"
    }
  }
}
```

-   If there was a crash, please include the traceback here.

```
(venv) root@ca20f043eef5:/t# python demo.py
Dispose
Traceback (most recent call last):
  File "demo.py", line 21, in <module>
    class MyBuffer(System.Buffers.IMemoryOwner):
TypeError: Method 'Dispose' in type 'MyNamespace.MyBuffer' from assembly 'Python.Runtime.Dynamic, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
```



Discussion / hints:
Can you try adding `IDisposable` explicitly to the list of `MyBuffer` bases? I wonder if it would work around the issue. (Still looks like a valid bug to me)
```python
class MyBuffer(System.Buffers.IMemoryOwner, System.IDisposable):
```

```bash
(venv) root@ca20f043eef5:/t# python demo.py
Dispose
Traceback (most recent call last):
  File "demo.py", line 21, in <module>
    class MyBuffer(System.Buffers.IMemoryOwner, System.IDisposable):
TypeError: cannot use multiple inheritance with managed classes
```
** Edited, I posted the wrong error originally

Thanks for the thought. Doesn't seem to be supported?
Tested this on Windows 10, Python 3.8.10, Pythonnet 3.0.0a2 using .NET Framework (removed the set_runtime() stuff, merely used `import clr`). Had to switch to System.ComponentModel.IComponent as System.Buffers.IMemoryOwner is not available there. I get the exact same error:

```python
>>> import clr
>>> import System
>>> import System.ComponentModel
>>> class Bla(System.ComponentModel.IComponent):
...   __namespace__ = "MyNamespace"
...
...   def Dispose(self) -> None:
...     print("Dispose")
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Method 'Dispose' in type 'MyNamespace.Bla' from assembly 'Python.Runtime.Dynamic, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
``` 
The error has nothing specifically to do with IDisposable. Any interface that extends a second interface shows this problem: 

```python
>>> import clr
>>> import System.Collections
>>> class Bla(System.Collections.IDictionary):
...   __namespace__ = "MyNamespace"
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Method 'CopyTo' in type 'MyNamespace.Bla' from assembly 'Python.Runtime.Dynamic, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
```
I do not believe this bug is a 3.0.0 blocker, as this probably never worked in 2.5 either. For 3.0.0 we should focus on regressions and breaking changes.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94a85e094..818d90c52 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
 -   Fixed error occuring when inheriting a class containing a virtual generic method.
 -   Make a second call to `pythonnet.load` a no-op, as it was intended.
 
+- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces.
+
 ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03
 
 ### Added
diff --git a/src/runtime/StateSerialization/MaybeType.cs b/src/runtime/StateSerialization/MaybeType.cs
index f3c96e369..884b7edb0 100644
--- a/src/runtime/StateSerialization/MaybeType.cs
+++ b/src/runtime/StateSerialization/MaybeType.cs
@@ -15,7 +15,7 @@ internal struct MaybeType : ISerializable
         const string SerializationName = "n";
         readonly string name;
         readonly Type type;
-        
+
         public string DeletedMessage
         {
             get
@@ -61,4 +61,4 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext
             serializationInfo.AddValue(SerializationName, name);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs
index e0a78ba49..559d5148e 100644
--- a/src/runtime/TypeManager.cs
+++ b/src/runtime/TypeManager.cs
@@ -374,7 +374,7 @@ static PyTuple GetBaseTypeTuple(Type clrType)
             return new PyTuple(bases);
         }
 
-        internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedReference py_base_type, BorrowedReference dictRef)
+        internal static NewReference CreateSubType(BorrowedReference py_name, ClassBase py_base_type, IList<Type> interfaces, BorrowedReference dictRef)
         {
             // Utility to create a subtype of a managed type with the ability for the
             // a python subtype able to override the managed implementation
@@ -415,17 +415,10 @@ internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedRe
             }
 
             // create the new managed type subclassing the base managed type
-            if (ManagedType.GetManagedObject(py_base_type) is ClassBase baseClass)
-            {
-                return ReflectedClrType.CreateSubclass(baseClass, name,
-                                                       ns: (string?)namespaceStr,
-                                                       assembly: (string?)assembly,
-                                                       dict: dictRef);
-            }
-            else
-            {
-                return Exceptions.RaiseTypeError("invalid base class, expected CLR class type");
-            }
+            return ReflectedClrType.CreateSubclass(py_base_type, interfaces, name,
+                ns: (string?)namespaceStr,
+                assembly: (string?)assembly,
+                dict: dictRef);
         }
 
         internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc)
diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs
index 61c602783..592eefd55 100644
--- a/src/runtime/Types/ClassDerived.cs
+++ b/src/runtime/Types/ClassDerived.cs
@@ -144,6 +144,7 @@ internal static NewReference ToPython(IPythonDerivedType obj)
         /// </summary>
         internal static Type CreateDerivedType(string name,
             Type baseType,
+            IList<Type> typeInterfaces,
             BorrowedReference py_dict,
             string? namespaceStr,
             string? assemblyName,
@@ -163,7 +164,9 @@ internal static Type CreateDerivedType(string name,
             ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName);
 
             Type baseClass = baseType;
-            var interfaces = new List<Type> { typeof(IPythonDerivedType) };
+            var interfaces = new HashSet<Type> { typeof(IPythonDerivedType) };
+            foreach(var interfaceType in typeInterfaces)
+                interfaces.Add(interfaceType);
 
             // if the base type is an interface then use System.Object as the base class
             // 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,
                 }
             }
 
-            // override any virtual methods not already overridden by the properties above
-            MethodInfo[] methods = baseType.GetMethods();
+            // override any virtual not already overridden by the properties above
+            // also override any interface method.
+            var methods = baseType.GetMethods().Concat(interfaces.SelectMany(x => x.GetMethods()));
             var virtualMethods = new HashSet<string>();
             foreach (MethodInfo method in methods)
             {
diff --git a/src/runtime/Types/MetaType.cs b/src/runtime/Types/MetaType.cs
index 5b59f5139..57fcaa232 100644
--- a/src/runtime/Types/MetaType.cs
+++ b/src/runtime/Types/MetaType.cs
@@ -1,5 +1,8 @@
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Runtime.Serialization;
 
@@ -79,41 +82,80 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args,
             BorrowedReference bases = Runtime.PyTuple_GetItem(args, 1);
             BorrowedReference dict = Runtime.PyTuple_GetItem(args, 2);
 
-            // We do not support multiple inheritance, so the bases argument
-            // should be a 1-item tuple containing the type we are subtyping.
-            // That type must itself have a managed implementation. We check
-            // that by making sure its metatype is the CLR metatype.
+            // Extract interface types and base class types.
+            var interfaces = new List<Type>();
 
-            if (Runtime.PyTuple_Size(bases) != 1)
-            {
-                return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes");
-            }
+            // More than one base type case be declared, but an exception will be thrown
+            // if more than one is a class/not an interface.
+            var baseTypes = new List<ClassBase>();
 
-            BorrowedReference base_type = Runtime.PyTuple_GetItem(bases, 0);
-            BorrowedReference mt = Runtime.PyObject_TYPE(base_type);
-
-            if (!(mt == PyCLRMetaType || mt == Runtime.PyTypeType))
+            var baseClassCount = Runtime.PyTuple_Size(bases);
+            if (baseClassCount == 0)
             {
-                return Exceptions.RaiseTypeError("invalid metatype");
+                return Exceptions.RaiseTypeError("zero base classes ");
             }
 
-            // Ensure that the reflected type is appropriate for subclassing,
-            // disallowing subclassing of delegates, enums and array types.
-
-            if (GetManagedObject(base_type) is ClassBase cb)
+            for (nint i = 0; i < baseClassCount; i++)
             {
-                try
+                var baseTypeIt = Runtime.PyTuple_GetItem(bases, (int)i);
+
+                if (GetManagedObject(baseTypeIt) is ClassBase classBaseIt)
                 {
-                    if (!cb.CanSubclass())
+                    if (!classBaseIt.type.Valid)
+                    {
+                        return Exceptions.RaiseTypeError("Invalid type used as a super type.");
+                    }
+                    if (classBaseIt.type.Value.IsInterface)
                     {
-                        return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed");
+                        interfaces.Add(classBaseIt.type.Value);
                     }
+                    else
+                    {
+                        baseTypes.Add(classBaseIt);
+                    }
+                }
+                else
+                {
+                    return Exceptions.RaiseTypeError("Non .NET type used as super class for meta type. This is not supported.");
                 }
-                catch (SerializationException)
+            }
+            // if the base type count is 0, there might still be interfaces to implement.
+            if (baseTypes.Count == 0)
+            {
+                baseTypes.Add(new ClassBase(typeof(object)));
+            }
+
+            // Multiple inheritance is not supported, unless the other types are interfaces
+            if (baseTypes.Count > 1)
+            {
+                var types = string.Join(", ", baseTypes.Select(baseType => baseType.type.Value));
+                return Exceptions.RaiseTypeError($"Multiple inheritance with managed classes cannot be used. Types: {types} ");
+            }
+
+            // check if the list of interfaces contains no duplicates.
+            if (interfaces.Distinct().Count() != interfaces.Count)
+            {
+                // generate a string containing the problematic types.
+                var duplicateTypes = interfaces.GroupBy(type => type)
+                    .Where(typeGroup => typeGroup.Count() > 1)
+                    .Select(typeGroup => typeGroup.Key);
+                var duplicateTypesString = string.Join(", ", duplicateTypes);
+
+                return Exceptions.RaiseTypeError($"An interface can only be implemented once. Duplicate types: {duplicateTypesString}");
+            }
+
+            var cb = baseTypes[0];
+            try
+            {
+                if (!cb.CanSubclass())
                 {
-                    return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted");
+                    return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed");
                 }
             }
+            catch (SerializationException)
+            {
+                return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted");
+            }
 
             BorrowedReference slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__);
             if (slots != null)
@@ -130,10 +172,12 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args,
                 using var clsDict = new PyDict(dict);
                 if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__"))
                 {
-                    return TypeManager.CreateSubType(name, base_type, clsDict);
+                    return TypeManager.CreateSubType(name, baseTypes[0], interfaces, clsDict);
                 }
             }
 
+            var base_type = Runtime.PyTuple_GetItem(bases, 0);
+
             // otherwise just create a basic type without reflecting back into the managed side.
             IntPtr func = Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new);
             NewReference type = NativeCall.Call_3(func, tp, args, kw);
diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs
index d3d89bdb8..3d0aa7e99 100644
--- a/src/runtime/Types/ReflectedClrType.cs
+++ b/src/runtime/Types/ReflectedClrType.cs
@@ -68,7 +68,7 @@ internal void Restore(ClassBase cb)
         TypeManager.InitializeClass(this, cb, cb.type.Value);
     }
 
-    internal static NewReference CreateSubclass(ClassBase baseClass,
+    internal static NewReference CreateSubclass(ClassBase baseClass, IList<Type> interfaces,
                                                 string name, string? assembly, string? ns,
                                                 BorrowedReference dict)
     {
@@ -76,6 +76,7 @@ internal static NewReference CreateSubclass(ClassBase baseClass,
         {
             Type subType = ClassDerivedObject.CreateDerivedType(name,
                 baseClass.type.Value,
+                interfaces,
                 dict,
                 ns,
                 assembly);


Test patch:
diff --git a/src/python_tests_runner/PythonTestRunner.cs b/src/python_tests_runner/PythonTestRunner.cs
index 05298997b..f97cc5aec 100644
--- a/src/python_tests_runner/PythonTestRunner.cs
+++ b/src/python_tests_runner/PythonTestRunner.cs
@@ -35,6 +35,7 @@ static IEnumerable<string[]> PythonTestCases()
             // Add the test that you want to debug here.
             yield return new[] { "test_indexer", "test_boolean_indexer" };
             yield return new[] { "test_delegate", "test_bool_delegate" };
+            yield return new[] { "test_subclass", "test_implement_interface_and_class" };
         }
 
         /// <summary>
diff --git a/src/testing/classtest.cs b/src/testing/classtest.cs
index 68c0d8c55..993afdfc9 100644
--- a/src/testing/classtest.cs
+++ b/src/testing/classtest.cs
@@ -1,3 +1,4 @@
+using System;
 using System.Collections;
 
 namespace Python.Test
@@ -59,4 +60,15 @@ public ClassCtorTest2(string v)
     internal class InternalClass
     {
     }
+
+    public class SimpleClass
+    {
+        public static void TestObject(object obj)
+        {
+            if ((!(obj is ISayHello1 && obj is SimpleClass)))
+            {
+                throw new Exception("Expected ISayHello and SimpleClass instance");
+            }
+        }
+    }
 }
diff --git a/tests/test_subclass.py b/tests/test_subclass.py
index 504b82548..c6ab7650f 100644
--- a/tests/test_subclass.py
+++ b/tests/test_subclass.py
@@ -9,7 +9,7 @@
 import System
 import pytest
 from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest,
-                         FunctionsTest, IGenericInterface, GenericVirtualMethodTest)
+                         FunctionsTest, IGenericInterface, GenericVirtualMethodTest, SimpleClass, ISayHello1)
 from System.Collections.Generic import List
 
 
@@ -338,4 +338,9 @@ class OverloadingSubclass2(OverloadingSubclass):
     obj = OverloadingSubclass2()
     assert obj.VirtMethod[int](5) == 5
 
-
+def test_implement_interface_and_class():
+    class DualSubClass0(ISayHello1, SimpleClass):
+        __namespace__ = "Test"
+        def SayHello(self):
+            return "hello"
+    obj = DualSubClass0()