From 46de53e22c90fa461e6b14705d62372a61d53b6d Mon Sep 17 00:00:00 2001 From: Rolf Madsen Date: Tue, 29 Nov 2022 15:06:55 +0100 Subject: [PATCH 1/3] 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. --- CHANGELOG.md | 2 + src/python_tests_runner/PythonTestRunner.cs | 1 + src/runtime/Runtime.cs | 6 ++ src/runtime/StateSerialization/MaybeType.cs | 4 +- src/runtime/TypeManager.cs | 19 +++--- src/runtime/Types/ClassDerived.cs | 10 +++- src/runtime/Types/MetaType.cs | 64 ++++++++++++--------- src/runtime/Types/ReflectedClrType.cs | 3 +- src/testing/classtest.cs | 12 ++++ tests/test_subclass.py | 9 ++- 10 files changed, 84 insertions(+), 46 deletions(-) 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/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 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" }; } /// diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index beb577e45..d5558084f 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -1836,6 +1836,12 @@ internal static void SetNoSiteFlag() return *Delegates.Py_NoSiteFlag; }); } + + internal static uint PyTuple_GetSize(BorrowedReference tuple) + { + IntPtr r = Delegates.PyTuple_Size(tuple); + return (uint)r.ToInt32(); + } } internal class BadPythonDllException : MissingMethodException 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..a23973919 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, IList py_base_type, IList 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,12 @@ 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"); - } + var baseClass = py_base_type.FirstOrDefault(); + + return ReflectedClrType.CreateSubclass(baseClass, 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) /// internal static Type CreateDerivedType(string name, Type baseType, + IList 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 { typeof(IPythonDerivedType) }; + var interfaces = new HashSet { 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(); foreach (MethodInfo method in methods) { diff --git a/src/runtime/Types/MetaType.cs b/src/runtime/Types/MetaType.cs index 5b59f5139..9ff30ed33 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,47 @@ 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(); + var baseType = new List(); - if (Runtime.PyTuple_Size(bases) != 1) + var cnt = Runtime.PyTuple_GetSize(bases); + + for (uint i = 0; i < cnt; i++) { - return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes"); + var base_type2 = Runtime.PyTuple_GetItem(bases, (int)i); + var cb2 = (ClassBase) GetManagedObject(base_type2); + if (cb2 != null) + { + if (cb2.type.Valid && cb2.type.Value.IsInterface) + interfaces.Add(cb2.type.Value); + else baseType.Add(cb2); + } } - - BorrowedReference base_type = Runtime.PyTuple_GetItem(bases, 0); - BorrowedReference mt = Runtime.PyObject_TYPE(base_type); - - if (!(mt == PyCLRMetaType || mt == Runtime.PyTypeType)) + // if the base type count is 0, there might still be interfaces to implement. + if (baseType.Count == 0) { - return Exceptions.RaiseTypeError("invalid metatype"); + baseType.Add(new ClassBase(typeof(object))); } - // Ensure that the reflected type is appropriate for subclassing, - // disallowing subclassing of delegates, enums and array types. + // Multiple inheritance is not supported, unless the other types are interfaces + if (baseType.Count > 1) + { + return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes"); + } - if (GetManagedObject(base_type) is ClassBase cb) + var cb = baseType[0]; + try { - try - { - if (!cb.CanSubclass()) - { - return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); - } - } - catch (SerializationException) + 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) @@ -121,7 +130,8 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, return Exceptions.RaiseTypeError("subclasses of managed classes do not support __slots__"); } - // If __assembly__ or __namespace__ are in the class dictionary then create + // If the base class has a parameterless constructor, or + // if __assembly__ or __namespace__ are in the class dictionary then create // a managed sub type. // This creates a new managed type that can be used from .net to call back // into python. @@ -130,10 +140,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, baseType, 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 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); 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() From f92a26b734bb090b98065eb40cf1046bfeaa07b3 Mon Sep 17 00:00:00 2001 From: romadsen-ks Date: Wed, 30 Nov 2022 08:52:44 +0100 Subject: [PATCH 2/3] Added a few cleanups based on feedback. --- src/runtime/Runtime.cs | 6 ------ src/runtime/TypeManager.cs | 6 ++---- src/runtime/Types/MetaType.cs | 31 +++++++++++++++++-------------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index d5558084f..beb577e45 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -1836,12 +1836,6 @@ internal static void SetNoSiteFlag() return *Delegates.Py_NoSiteFlag; }); } - - internal static uint PyTuple_GetSize(BorrowedReference tuple) - { - IntPtr r = Delegates.PyTuple_Size(tuple); - return (uint)r.ToInt32(); - } } internal class BadPythonDllException : MissingMethodException diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index a23973919..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, IList py_base_type, IList interfaces, BorrowedReference dictRef) + internal static NewReference CreateSubType(BorrowedReference py_name, ClassBase py_base_type, IList 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,9 +415,7 @@ internal static NewReference CreateSubType(BorrowedReference py_name, IList(); - var baseType = new List(); - var cnt = Runtime.PyTuple_GetSize(bases); + // 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(); - for (uint i = 0; i < cnt; i++) + var baseClassCount = Runtime.PyTuple_Size(bases); + + for (nint i = 0; i < baseClassCount; i++) { - var base_type2 = Runtime.PyTuple_GetItem(bases, (int)i); - var cb2 = (ClassBase) GetManagedObject(base_type2); - if (cb2 != null) + var baseTypeIt = Runtime.PyTuple_GetItem(bases, (int)i); + + if (GetManagedObject(baseTypeIt) is ClassBase classBaseIt) { - if (cb2.type.Valid && cb2.type.Value.IsInterface) - interfaces.Add(cb2.type.Value); - else baseType.Add(cb2); + if (classBaseIt.type.Valid && classBaseIt.type.Value.IsInterface) + interfaces.Add(classBaseIt.type.Value); + else baseTypes.Add(classBaseIt); } } // if the base type count is 0, there might still be interfaces to implement. - if (baseType.Count == 0) + if (baseTypes.Count == 0) { - baseType.Add(new ClassBase(typeof(object))); + baseTypes.Add(new ClassBase(typeof(object))); } // Multiple inheritance is not supported, unless the other types are interfaces - if (baseType.Count > 1) + if (baseTypes.Count > 1) { return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes"); } - var cb = baseType[0]; + var cb = baseTypes[0]; try { if (!cb.CanSubclass()) @@ -140,7 +143,7 @@ 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, baseType, interfaces, clsDict); + return TypeManager.CreateSubType(name, baseTypes[0], interfaces, clsDict); } } From 7acbb229e89c16983b60b761dc01e4c25bd17807 Mon Sep 17 00:00:00 2001 From: Rolf Madsen Date: Thu, 2 Feb 2023 21:41:28 +0100 Subject: [PATCH 3/3] 1783 Implement Interface And Inherit Class: Cleanup - Added exceptions during unintended use of superclasses. - Removed invalid comment. - Improved exceptions from MetaType slightly. --- src/runtime/Types/MetaType.cs | 39 ++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/runtime/Types/MetaType.cs b/src/runtime/Types/MetaType.cs index 2e61b6808..57fcaa232 100644 --- a/src/runtime/Types/MetaType.cs +++ b/src/runtime/Types/MetaType.cs @@ -90,6 +90,10 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, var baseTypes = new List(); var baseClassCount = Runtime.PyTuple_Size(bases); + if (baseClassCount == 0) + { + return Exceptions.RaiseTypeError("zero base classes "); + } for (nint i = 0; i < baseClassCount; i++) { @@ -97,9 +101,22 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, if (GetManagedObject(baseTypeIt) is ClassBase classBaseIt) { - if (classBaseIt.type.Valid && classBaseIt.type.Value.IsInterface) + if (!classBaseIt.type.Valid) + { + return Exceptions.RaiseTypeError("Invalid type used as a super type."); + } + if (classBaseIt.type.Value.IsInterface) + { interfaces.Add(classBaseIt.type.Value); - else baseTypes.Add(classBaseIt); + } + else + { + baseTypes.Add(classBaseIt); + } + } + else + { + return Exceptions.RaiseTypeError("Non .NET type used as super class for meta type. This is not supported."); } } // if the base type count is 0, there might still be interfaces to implement. @@ -111,7 +128,20 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, // Multiple inheritance is not supported, unless the other types are interfaces if (baseTypes.Count > 1) { - return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes"); + 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]; @@ -133,8 +163,7 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, return Exceptions.RaiseTypeError("subclasses of managed classes do not support __slots__"); } - // If the base class has a parameterless constructor, or - // if __assembly__ or __namespace__ are in the class dictionary then create + // If __assembly__ or __namespace__ are in the class dictionary then create // a managed sub type. // This creates a new managed type that can be used from .net to call back // into python. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy