From a32cc19f6cab48b49bb2865620d592c2b2f6bf1b Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Thu, 8 Jan 2026 13:40:39 -0500 Subject: [PATCH 01/10] return runtime concrete type for types implementing .net interfaces --- Directory.Build.props | 4 ++-- src/runtime/Converter.cs | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index e45c16f6a..b3d933914 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,9 +16,9 @@ all runtime; build; native; contentfiles; analyzers - + diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index c7154ce36..dfac772f4 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -140,10 +140,28 @@ internal static NewReference ToPython(object? value, Type type) } } + // If the declared type is an interface, check if the actual runtime type + // is a concrete class. If so, prefer the concrete type to preserve access + // to all members. Only wrap as interface if the runtime type is also an + // interface or if we need explicit interface behavior. if (type.IsInterface) { - var ifaceObj = (InterfaceObject)ClassManager.GetClassImpl(type); - return ifaceObj.TryWrapObject(value); + Type actualType = value.GetType(); + // Prefer concrete types over interface types to preserve full member access. + // This fixes issues where methods return concrete types that implement + // interfaces (e.g., IDisposable) but should be exposed as their + // concrete type for proper member access and Python 'with' statement support. + if (!actualType.IsInterface) + { + // Use the actual concrete type instead of the interface + type = actualType; + } + else + { + // Runtime type is also an interface (rare: proxy/dynamic case), wrap as interface + var ifaceObj = (InterfaceObject)ClassManager.GetClassImpl(type); + return ifaceObj.TryWrapObject(value); + } } if (type.IsArray || type.IsEnum) From a4d6082a6cfd4c222bd2cae66bc1f40dd4f4f66e Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Mon, 12 Jan 2026 10:00:05 -0500 Subject: [PATCH 02/10] add tests --- src/embed_tests/TestConverter.cs | 124 +++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index a59b9c97b..a14908516 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -209,6 +209,90 @@ class PyGetListImpl(test.GetListImpl): List result = inst.GetList(); CollectionAssert.AreEqual(new[] { "testing" }, result); } + + /// + /// Test that when a method returns a concrete type implementing IDisposable, + /// the object is wrapped as the concrete type (not IDisposable interface), + /// preserving access to concrete type members and supporting 'with' statements. + /// + [Test] + public void ConcreteTypeImplementingIDisposable_IsWrappedAsConcreteType() + { + using var scope = Py.CreateScope(); + scope.Import(typeof(ConcreteDisposableResource).Namespace, asname: "test"); + + // Reset static state + ConcreteDisposableResource.IsDisposed = false; + ConcreteDisposableResource.InstanceCount = 0; + + // Test that a method returning IDisposable but actually returning a concrete type + // wraps the object as the concrete type, not the interface + scope.Exec(@" +import clr +clr.AddReference('Python.EmbeddingTest') +from Python.EmbeddingTest import ConcreteDisposableResource + +# Get a resource through a method that declares IDisposable return type +resource = ConcreteDisposableResource.GetResource() + +# Verify it's wrapped as the concrete type, not IDisposable +# The concrete type has a GetValue() method that IDisposable doesn't have +value = resource.GetValue() +assert value == 42, f'Expected 42, got {value}' + +# Verify the concrete type name is accessible +type_name = resource.GetType().Name +assert type_name == 'ConcreteDisposableResource', f'Expected ConcreteDisposableResource, got {type_name}' + +# Verify 'with' statement still works (IDisposable support) +with resource: + inside_value = resource.GetValue() + assert inside_value == 42 + assert ConcreteDisposableResource.IsDisposed == False + +# After 'with' block, should be disposed +assert ConcreteDisposableResource.IsDisposed == True +"); + + // Verify the resource was actually disposed + Assert.IsTrue(ConcreteDisposableResource.IsDisposed, "Resource should be disposed after 'with' statement"); + } + + /// + /// Test that Converter.ToPython wraps concrete types implementing interfaces + /// as the concrete type, not the interface, when the declared type is an interface. + /// + [Test] + public void Converter_ToPython_ConcreteTypeOverInterface() + { + using (Py.GIL()) + { + // Create a concrete type that implements IDisposable + var concreteResource = new ConcreteDisposableResource(100); + + // Convert using IDisposable as the declared type (simulating method return type) + var pyObject = Converter.ToPython(concreteResource, typeof(IDisposable)); + + // Verify it's wrapped as the concrete type, not IDisposable + var wrappedObject = ManagedType.GetManagedObject(pyObject.BorrowOrThrow()); + Assert.IsInstanceOf(wrappedObject); + + var clrObject = (CLRObject)wrappedObject; + var wrappedType = clrObject.inst.GetType(); + + // Should be the concrete type, not IDisposable + Assert.AreEqual(typeof(ConcreteDisposableResource), wrappedType); + Assert.AreNotEqual(typeof(IDisposable), wrappedType); + + // Verify we can access concrete type members from Python + using var scope = Py.CreateScope(); + scope.Set("resource", pyObject); + var result = scope.Eval("resource.GetValue()"); + Assert.AreEqual(100, result.As()); + + pyObject.Dispose(); + } + } } public interface IGetList @@ -220,4 +304,44 @@ public class GetListImpl : IGetList { public List GetList() => new() { "testing" }; } + + /// + /// A concrete class implementing IDisposable with additional members. + /// Used to test that methods returning IDisposable but actually returning + /// concrete types are wrapped as the concrete type, not the interface. + /// + public class ConcreteDisposableResource : IDisposable + { + public static bool IsDisposed { get; set; } + public static int InstanceCount { get; set; } + + private readonly int _value; + + public ConcreteDisposableResource(int value = 42) + { + _value = value; + InstanceCount++; + IsDisposed = false; + } + + /// + /// A method that exists only on the concrete type, not on IDisposable. + /// This verifies that the object is wrapped as the concrete type. + /// + public int GetValue() => _value; + + public void Dispose() + { + IsDisposed = true; + } + + /// + /// A method that declares IDisposable return type but actually returns + /// the concrete type. This is the scenario we're testing. + /// + public static IDisposable GetResource() + { + return new ConcreteDisposableResource(); + } + } } From 36d3746f29cf4e53ab4a975dc903090474b94826 Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Mon, 12 Jan 2026 23:59:50 -0500 Subject: [PATCH 03/10] fix build errors --- src/embed_tests/Python.EmbeddingTest.csproj | 2 +- src/embed_tests/TestConverter.cs | 2 +- src/module_tests/Python.ModuleTest.csproj | 2 +- src/perf_tests/Python.PerformanceTests.csproj | 8 +------- src/python_tests_runner/Python.PythonTestsRunner.csproj | 2 +- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 4993994d3..4ecb689d7 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -24,7 +24,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + 1.0.0 all diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index a14908516..83540a16a 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -286,7 +286,7 @@ public void Converter_ToPython_ConcreteTypeOverInterface() // Verify we can access concrete type members from Python using var scope = Py.CreateScope(); - scope.Set("resource", pyObject); + scope.Set("resource", pyObject.MoveToPyObject()); var result = scope.Eval("resource.GetValue()"); Assert.AreEqual(100, result.As()); diff --git a/src/module_tests/Python.ModuleTest.csproj b/src/module_tests/Python.ModuleTest.csproj index 7a8aa9ac3..081b8d871 100644 --- a/src/module_tests/Python.ModuleTest.csproj +++ b/src/module_tests/Python.ModuleTest.csproj @@ -20,7 +20,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + 1.0.0 all diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index b526183cc..5a5ab5455 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -5,16 +5,10 @@ false x64 x64 - - - - ..\..\pythonnet\runtime\Python.Runtime.dll - true - + - diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index 63981c424..d5cf106d1 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 7201ca086005aa97acac33f86a2fc96e1d12fd22 Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Tue, 13 Jan 2026 11:17:12 -0500 Subject: [PATCH 04/10] update test --- src/embed_tests/TestConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 83540a16a..6915c82a2 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -191,7 +191,7 @@ public void GenericToPython() int i = 42; var pyObject = i.ToPythonAs(); var type = pyObject.GetPythonType(); - Assert.AreEqual(nameof(IConvertible), type.Name); + Assert.AreEqual(typeof(int).Name, type.Name); } // regression for https://github.com/pythonnet/pythonnet/issues/451 From d176161248ff57f42d2e8be299d72ca7964a219e Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Tue, 13 Jan 2026 11:30:05 -0500 Subject: [PATCH 05/10] update test --- src/embed_tests/TestConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 6915c82a2..3f55e5697 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -191,7 +191,7 @@ public void GenericToPython() int i = 42; var pyObject = i.ToPythonAs(); var type = pyObject.GetPythonType(); - Assert.AreEqual(typeof(int).Name, type.Name); + Assert.AreEqual("int", type.Name); } // regression for https://github.com/pythonnet/pythonnet/issues/451 From 8cf4991255d9c668cf9c7b40b487a99cb89391f5 Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Tue, 13 Jan 2026 12:02:48 -0500 Subject: [PATCH 06/10] update tests per new behavior --- tests/test_array.py | 9 ++++++--- tests/test_generic.py | 10 ++++++---- tests/test_interface.py | 32 ++++++++++++++++++++++---------- tests/test_method.py | 13 +++++++------ 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index d207a36fb..296b393fa 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1335,10 +1335,13 @@ def test_special_array_creation(): assert value[1].__class__ == inst.__class__ assert value.Length == 2 - iface_class = ISayHello1(inst).__class__ + # When creating Array[ISayHello1], elements are wrapped as their concrete type + # (not the interface) to preserve access to all concrete type members. + # This is the correct behavior - the array type is ISayHello1[], but elements + # are wrapped as concrete types for full member access. value = Array[ISayHello1]([inst, inst]) - assert value[0].__class__ == iface_class - assert value[1].__class__ == iface_class + assert value[0].__class__ == inst.__class__ + assert value[1].__class__ == inst.__class__ assert value.Length == 2 inst = System.Exception("badness") diff --git a/tests/test_generic.py b/tests/test_generic.py index 99f4db477..01db410f5 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -558,11 +558,12 @@ def test_method_overload_selection_with_generic_types(): value = MethodTest.Overloaded.__overloads__[vtype](input_) assert value.value.__class__ == inst.__class__ - iface_class = ISayHello1(inst).__class__ + # When generic wrapper contains interface type, the value is wrapped as concrete type + # (not interface) to preserve access to all concrete type members vtype = GenericWrapper[ISayHello1] input_ = vtype(inst) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value.value.__class__ == iface_class + assert value.value.__class__ == inst.__class__ vtype = System.Array[GenericWrapper[int]] input_ = vtype([GenericWrapper[int](0), GenericWrapper[int](1)]) @@ -737,12 +738,13 @@ def test_overload_selection_with_arrays_of_generic_types(): assert value[0].value.__class__ == inst.__class__ assert value.Length == 2 - iface_class = ISayHello1(inst).__class__ + # When creating array of generic wrappers with interface type, values are wrapped + # as concrete types (not interfaces) to preserve access to all concrete type members gtype = GenericWrapper[ISayHello1] vtype = System.Array[gtype] input_ = vtype([gtype(inst), gtype(inst)]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].value.__class__ == iface_class + assert value[0].value.__class__ == inst.__class__ assert value.Length == 2 diff --git a/tests/test_interface.py b/tests/test_interface.py index 969968c55..5b2c7cae4 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -79,25 +79,31 @@ def test_call_inherited_interface_method(): assert hello1.SayHi() == 'hi' def test_interface_object_returned_through_method(): - """Test interface type is used if method return type is interface""" + """Test that method return types use concrete types (not interfaces) to preserve member access. + + When a method declares an interface return type but returns a concrete type, + the concrete type is used to preserve access to all members. Interface methods + are still accessible through the concrete type. + """ from Python.Test import InterfaceTest ob = InterfaceTest() hello1 = ob.GetISayHello1() - assert type(hello1).__name__ == 'ISayHello1' - assert hello1.__implementation__.__class__.__name__ == "InterfaceTest" - + # Method returns concrete type (not interface) to preserve member access + assert type(hello1).__name__ == 'InterfaceTest' + # Interface methods are still accessible assert hello1.SayHello() == 'hello 1' def test_interface_object_returned_through_out_param(): - """Test interface type is used for out parameters of interface types""" + """Test that out parameters of interface types use concrete types to preserve member access.""" from Python.Test import InterfaceTest ob = InterfaceTest() hello2 = ob.GetISayHello2(None) - assert type(hello2).__name__ == 'ISayHello2' - + # Out parameters return concrete types (not interfaces) to preserve member access + assert type(hello2).__name__ == 'InterfaceTest' + # Interface methods are still accessible assert hello2.SayHello() == 'hello 2' def test_interface_out_param_python_impl(): @@ -125,13 +131,19 @@ def test_null_interface_object_returned(): assert hello2 is None def test_interface_array_returned(): - """Test interface type used for methods returning interface arrays""" + """Test that methods returning interface arrays use concrete types for elements. + + Array elements are wrapped as concrete types (not interfaces) to preserve + member access. The array type is still ISayHello1[], but elements are concrete. + """ from Python.Test import InterfaceTest ob = InterfaceTest() hellos = ob.GetISayHello1Array() - assert type(hellos[0]).__name__ == 'ISayHello1' - assert hellos[0].__implementation__.__class__.__name__ == "InterfaceTest" + # Elements are wrapped as concrete types to preserve member access + assert type(hellos[0]).__name__ == 'InterfaceTest' + # Interface methods are still accessible + assert hellos[0].SayHello() == 'hello 1' def test_implementation_access(): """Test the __implementation__ and __raw_implementation__ properties""" diff --git a/tests/test_method.py b/tests/test_method.py index b86bbd6b4..5c938cbfb 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -583,10 +583,10 @@ def test_explicit_overload_selection(): value = MethodTest.Overloaded.__overloads__[InterfaceTest](inst) assert value.__class__ == inst.__class__ - iface_class = ISayHello1(InterfaceTest()).__class__ + # When method declares interface return type but returns concrete type, + # the concrete type is used to preserve member access value = MethodTest.Overloaded.__overloads__[ISayHello1](inst) - assert value.__class__ != inst.__class__ - assert value.__class__ == iface_class + assert value.__class__ == inst.__class__ atype = Array[System.Object] value = MethodTest.Overloaded.__overloads__[str, int, atype]( @@ -739,12 +739,13 @@ def test_overload_selection_with_array_types(): assert value[0].__class__ == inst.__class__ assert value[1].__class__ == inst.__class__ - iface_class = ISayHello1(inst).__class__ + # When creating Array[ISayHello1], elements are wrapped as concrete types + # (not interfaces) to preserve access to all concrete type members vtype = Array[ISayHello1] input_ = vtype([inst, inst]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].__class__ == iface_class - assert value[1].__class__ == iface_class + assert value[0].__class__ == inst.__class__ + assert value[1].__class__ == inst.__class__ def test_explicit_overload_selection_failure(): From 31e4e3c73289a2183d72486cb746c23d5ea54d84 Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Tue, 13 Jan 2026 23:11:48 -0500 Subject: [PATCH 07/10] add support for explicit interfaces --- src/runtime/Converter.cs | 112 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index dfac772f4..f093298b8 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -118,6 +118,105 @@ private static Func GetIsTransparentProxy() internal static NewReference ToPythonDetectType(object? value) => value is null ? new NewReference(Runtime.PyNone) : ToPython(value, value.GetType()); + + /// + /// Checks if a concrete type has explicit interface implementations for the given interface + /// or any of its base interfaces. Explicit implementations are only accessible through + /// the interface type, so we need to wrap as the interface to preserve access. + /// + private static bool HasExplicitInterfaceImplementations(Type concreteType, Type interfaceType) + { + // Get all interfaces to check: the declared interface and all its base interfaces + var interfacesToCheck = new HashSet { interfaceType }; + foreach (var baseInterface in interfaceType.GetInterfaces()) + { + interfacesToCheck.Add(baseInterface); + } + + foreach (var iface in interfacesToCheck) + { + // Skip if the concrete type doesn't implement this interface + if (!iface.IsAssignableFrom(concreteType)) + { + continue; + } + + try + { + // Get the interface mapping to see how members are implemented + var interfaceMap = concreteType.GetInterfaceMap(iface); + + // Check each interface method/property to see if it's explicitly implemented + for (int i = 0; i < interfaceMap.InterfaceMethods.Length; i++) + { + var interfaceMethod = interfaceMap.InterfaceMethods[i]; + var targetMethod = interfaceMap.TargetMethods[i]; + + // Explicit interface implementations have names like "InterfaceName.MethodName" + // and are not directly accessible on the concrete type + if (targetMethod.Name.Contains(".")) + { + // This is an explicit interface implementation + // Also verify it's not directly accessible on the concrete type + var directMethod = concreteType.GetMethod( + interfaceMethod.Name, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, + interfaceMethod.GetParameters().Select(p => p.ParameterType).ToArray(), + null); + + if (directMethod == null) + { + // Method is explicitly implemented and not directly accessible + return true; + } + } + } + + // Check properties as well + foreach (var prop in iface.GetProperties()) + { + var getter = prop.GetGetMethod(); + var setter = prop.GetSetMethod(); + + if (getter != null) + { + var concreteGetter = concreteType.GetProperty( + prop.Name, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + + if (concreteGetter == null) + { + // Property getter is explicitly implemented + return true; + } + } + + if (setter != null) + { + var concreteSetter = concreteType.GetProperty( + prop.Name, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + + if (concreteSetter == null) + { + // Property setter is explicitly implemented + return true; + } + } + } + } + catch + { + // If GetInterfaceMap fails (e.g., for generic interfaces), assume no explicit implementations + // and prefer concrete type + continue; + } + } + + return false; + } + internal static NewReference ToPython(object? value, Type type) { if (value is PyObject pyObj) @@ -153,7 +252,18 @@ internal static NewReference ToPython(object? value, Type type) // concrete type for proper member access and Python 'with' statement support. if (!actualType.IsInterface) { - // Use the actual concrete type instead of the interface + // Check if the declared interface (or any of its base interfaces) has + // members that are explicitly implemented in the concrete type. + // If so, we need to wrap as the interface to preserve access to those members. + if (HasExplicitInterfaceImplementations(actualType, type)) + { + // Wrap as interface to preserve access to explicit interface implementations + var ifaceObj = (InterfaceObject)ClassManager.GetClassImpl(type); + return ifaceObj.TryWrapObject(value); + } + + // No explicit interface implementations, use the actual concrete type + // to preserve full member access (e.g., for IDisposable with 'with' statement) type = actualType; } else From 338e97cdec3299434196313cd53baf1d1652deab Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Tue, 13 Jan 2026 23:30:10 -0500 Subject: [PATCH 08/10] revert one test update --- src/embed_tests/TestConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 3f55e5697..83540a16a 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -191,7 +191,7 @@ public void GenericToPython() int i = 42; var pyObject = i.ToPythonAs(); var type = pyObject.GetPythonType(); - Assert.AreEqual("int", type.Name); + Assert.AreEqual(nameof(IConvertible), type.Name); } // regression for https://github.com/pythonnet/pythonnet/issues/451 From 2f891d638e043f15110c2cf84c20e7efe28c9098 Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Wed, 14 Jan 2026 12:42:02 -0500 Subject: [PATCH 09/10] update tests --- tests/test_array.py | 9 +++------ tests/test_generic.py | 10 ++++------ tests/test_interface.py | 33 +++++++++++---------------------- tests/test_method.py | 12 +++++------- tests/test_subclass.py | 4 ++-- 5 files changed, 25 insertions(+), 43 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index 296b393fa..e3686147a 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1335,13 +1335,10 @@ def test_special_array_creation(): assert value[1].__class__ == inst.__class__ assert value.Length == 2 - # When creating Array[ISayHello1], elements are wrapped as their concrete type - # (not the interface) to preserve access to all concrete type members. - # This is the correct behavior - the array type is ISayHello1[], but elements - # are wrapped as concrete types for full member access. value = Array[ISayHello1]([inst, inst]) - assert value[0].__class__ == inst.__class__ - assert value[1].__class__ == inst.__class__ + iface_class = ISayHello1(inst).__class__ + assert value[0].__class__ == iface_class + assert value[1].__class__ == iface_class assert value.Length == 2 inst = System.Exception("badness") diff --git a/tests/test_generic.py b/tests/test_generic.py index 01db410f5..99f4db477 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -558,12 +558,11 @@ def test_method_overload_selection_with_generic_types(): value = MethodTest.Overloaded.__overloads__[vtype](input_) assert value.value.__class__ == inst.__class__ - # When generic wrapper contains interface type, the value is wrapped as concrete type - # (not interface) to preserve access to all concrete type members + iface_class = ISayHello1(inst).__class__ vtype = GenericWrapper[ISayHello1] input_ = vtype(inst) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value.value.__class__ == inst.__class__ + assert value.value.__class__ == iface_class vtype = System.Array[GenericWrapper[int]] input_ = vtype([GenericWrapper[int](0), GenericWrapper[int](1)]) @@ -738,13 +737,12 @@ def test_overload_selection_with_arrays_of_generic_types(): assert value[0].value.__class__ == inst.__class__ assert value.Length == 2 - # When creating array of generic wrappers with interface type, values are wrapped - # as concrete types (not interfaces) to preserve access to all concrete type members + iface_class = ISayHello1(inst).__class__ gtype = GenericWrapper[ISayHello1] vtype = System.Array[gtype] input_ = vtype([gtype(inst), gtype(inst)]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].value.__class__ == inst.__class__ + assert value[0].value.__class__ == iface_class assert value.Length == 2 diff --git a/tests/test_interface.py b/tests/test_interface.py index 5b2c7cae4..fc9a217ff 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -79,31 +79,24 @@ def test_call_inherited_interface_method(): assert hello1.SayHi() == 'hi' def test_interface_object_returned_through_method(): - """Test that method return types use concrete types (not interfaces) to preserve member access. - - When a method declares an interface return type but returns a concrete type, - the concrete type is used to preserve access to all members. Interface methods - are still accessible through the concrete type. - """ + """Test interface type is used if method return type is interface and contains explicit interface implementation.""" from Python.Test import InterfaceTest ob = InterfaceTest() hello1 = ob.GetISayHello1() - # Method returns concrete type (not interface) to preserve member access - assert type(hello1).__name__ == 'InterfaceTest' - # Interface methods are still accessible + assert type(hello1).__name__ == 'ISayHello1' + assert hello1.__implementation__.__class__.__name__ == "InterfaceTest" + assert hello1.SayHello() == 'hello 1' def test_interface_object_returned_through_out_param(): - """Test that out parameters of interface types use concrete types to preserve member access.""" + """Test interface type is used for out parameters of interface types and contains explicit interface implementation.""" from Python.Test import InterfaceTest ob = InterfaceTest() hello2 = ob.GetISayHello2(None) - # Out parameters return concrete types (not interfaces) to preserve member access - assert type(hello2).__name__ == 'InterfaceTest' - # Interface methods are still accessible + assert type(hello2).__name__ == 'ISayHello2' assert hello2.SayHello() == 'hello 2' def test_interface_out_param_python_impl(): @@ -131,18 +124,14 @@ def test_null_interface_object_returned(): assert hello2 is None def test_interface_array_returned(): - """Test that methods returning interface arrays use concrete types for elements. - - Array elements are wrapped as concrete types (not interfaces) to preserve - member access. The array type is still ISayHello1[], but elements are concrete. - """ + """Test interface type is used for methods returning interface arrays and contains explicit interface implementation.""" from Python.Test import InterfaceTest ob = InterfaceTest() hellos = ob.GetISayHello1Array() - # Elements are wrapped as concrete types to preserve member access - assert type(hellos[0]).__name__ == 'InterfaceTest' - # Interface methods are still accessible + assert type(hellos[0]).__name__ == 'ISayHello1' + assert hellos[0].__implementation__.__class__.__name__ == "InterfaceTest" + assert hellos[0].SayHello() == 'hello 1' def test_implementation_access(): @@ -163,7 +152,7 @@ def test_interface_collection_iteration(): typed_list = List[System.IComparable]() typed_list.Add(elem) for e in typed_list: - assert type(e).__name__ == "IComparable" + assert type(e).__name__ == "int" untyped_list = System.Collections.ArrayList() untyped_list.Add(elem) diff --git a/tests/test_method.py b/tests/test_method.py index 5c938cbfb..013038414 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -583,10 +583,9 @@ def test_explicit_overload_selection(): value = MethodTest.Overloaded.__overloads__[InterfaceTest](inst) assert value.__class__ == inst.__class__ - # When method declares interface return type but returns concrete type, - # the concrete type is used to preserve member access + iface_class = ISayHello1(InterfaceTest()).__class__ value = MethodTest.Overloaded.__overloads__[ISayHello1](inst) - assert value.__class__ == inst.__class__ + assert value.__class__ == iface_class atype = Array[System.Object] value = MethodTest.Overloaded.__overloads__[str, int, atype]( @@ -739,13 +738,12 @@ def test_overload_selection_with_array_types(): assert value[0].__class__ == inst.__class__ assert value[1].__class__ == inst.__class__ - # When creating Array[ISayHello1], elements are wrapped as concrete types - # (not interfaces) to preserve access to all concrete type members + iface_class = ISayHello1(inst).__class__ vtype = Array[ISayHello1] input_ = vtype([inst, inst]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].__class__ == inst.__class__ - assert value[1].__class__ == inst.__class__ + assert value[0].__class__ == iface_class + assert value[1].__class__ == iface_class def test_explicit_overload_selection_failure(): diff --git a/tests/test_subclass.py b/tests/test_subclass.py index 13a164dfa..87b7913bd 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -203,7 +203,7 @@ def test_interface(): # pass_through will convert from InterfaceTestClass -> IInterfaceTest, # causing a new wrapper object to be created. Hence id will differ. x = FunctionsTest.pass_through_interface(ob) - assert id(x) != id(ob) + assert id(x) == id(ob) def test_derived_class(): @@ -283,7 +283,7 @@ def test_create_instance(): assert FunctionsTest.test_bar(ob2, "bar", 2) == "bar/bar" y = FunctionsTest.pass_through_interface(ob2) - assert id(y) != id(ob2) + assert id(y) == id(ob2) def test_events(): From 9bfc36d5f92ab01b91bb0ce930fce37d22f4305f Mon Sep 17 00:00:00 2001 From: Aparajit Pratap Date: Wed, 14 Jan 2026 12:43:01 -0500 Subject: [PATCH 10/10] update test comment --- tests/test_subclass.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_subclass.py b/tests/test_subclass.py index 87b7913bd..9a5206586 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -200,8 +200,6 @@ def test_interface(): assert ob.bar("bar", 2) == "bar/bar" assert FunctionsTest.test_bar(ob, "bar", 2) == "bar/bar" - # pass_through will convert from InterfaceTestClass -> IInterfaceTest, - # causing a new wrapper object to be created. Hence id will differ. x = FunctionsTest.pass_through_interface(ob) assert id(x) == id(ob)