Skip to content

Commit 1dd36ae

Browse files
committed
Wrap returned objects in interface if method return type is interface
This allows callers to call all methods of an interface, regardless of whether the method was implemented implicitly or explicitly. Before this change, you had to make an explicit cast to the interface to be able to call the explicitly implemented method. Consider the following code: ```C# namespace Python.Test { public interface ITestInterface { void Foo(); void Bar(); } public class TestImpl : ITestInterface { public void Foo() { }; public void ITestInterface.Bar() { }; public void Baz() { }; public static ITestInterface GetInterface() { return new TestImpl(); } } } ``` And the following Python code, demonstrating the behavior before this change: ```python from Python.Test import TestImpl, ITestInterface test = TestImpl.GetInterface() test.Foo() # works test.Bar() # AttributeError: 'TestImpl' object has no attribute 'Bar' test.Baz() # works! - baz ``` After this change, the behavior is as follows: ``` test = TestImpl.GetInterface() test.Foo() # works test.Bar() # works test.Baz() # AttributeError: 'ITestInterface' object has no attribute 'Baz' ``` This is a breaking change due to that `Baz` is no longer visible in Python.
1 parent d44f1da commit 1dd36ae

File tree

10 files changed

+92
-20
lines changed

10 files changed

+92
-20
lines changed

src/runtime/converter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ internal static IntPtr ToPython(object value, Type type)
173173
}
174174
}
175175

176+
if (type.IsInterface)
177+
{
178+
var ifaceObj = (InterfaceObject)ClassManager.GetClass(type);
179+
return CLRObject.GetInstHandle(value, ifaceObj.pyHandle);
180+
}
181+
176182
// it the type is a python subclass of a managed type then return the
177183
// underlying python object rather than construct a new wrapper object.
178184
var pyderived = value as IPythonDerivedType;
@@ -182,6 +188,7 @@ internal static IntPtr ToPython(object value, Type type)
182188
return ClassDerivedObject.ToPython(pyderived);
183189
}
184190

191+
185192
// hmm - from Python, we almost never care what the declared
186193
// type is. we'd rather have the object bound to the actual
187194
// implementing class.

src/runtime/methodbinder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
744744
Type pt = pi[i].ParameterType;
745745
if (pi[i].IsOut || pt.IsByRef)
746746
{
747-
v = Converter.ToPython(binding.args[i], pt);
747+
v = Converter.ToPython(binding.args[i], pt.GetElementType());
748748
Runtime.PyTuple_SetItem(t, n, v);
749749
n++;
750750
}

src/runtime/typemanager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,13 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType)
164164
// we want to do this after the slot stuff above in case the class itself implements a slot method
165165
InitializeSlots(type, impl.GetType());
166166

167-
if (!clrType.GetInterfaces().Any(ifc => ifc == typeof(IEnumerable) || ifc == typeof(IEnumerator)))
167+
if (!typeof(IEnumerable).IsAssignableFrom(clrType) &&
168+
!typeof(IEnumerator).IsAssignableFrom(clrType))
168169
{
169170
// The tp_iter slot should only be set for enumerable types.
170171
Marshal.WriteIntPtr(type, TypeOffset.tp_iter, IntPtr.Zero);
171172
}
172173

173-
174174
if (base_ != IntPtr.Zero)
175175
{
176176
Marshal.WriteIntPtr(type, TypeOffset.tp_base, base_);

src/testing/interfacetest.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ internal interface IInternalInterface
1111
{
1212
}
1313

14-
1514
public interface ISayHello1
1615
{
1716
string SayHello();
@@ -43,6 +42,22 @@ string ISayHello2.SayHello()
4342
return "hello 2";
4443
}
4544

45+
public ISayHello1 GetISayHello1()
46+
{
47+
return this;
48+
}
49+
50+
public void GetISayHello2(out ISayHello2 hello2)
51+
{
52+
hello2 = this;
53+
}
54+
55+
public ISayHello1 GetNoSayHello(out ISayHello2 hello2)
56+
{
57+
hello2 = null;
58+
return null;
59+
}
60+
4661
public interface IPublic
4762
{
4863
}

src/testing/subclasstest.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,24 @@ public static string test_bar(IInterfaceTest x, string s, int i)
8989
}
9090

9191
// test instances can be constructed in managed code
92-
public static IInterfaceTest create_instance(Type t)
92+
public static SubClassTest create_instance(Type t)
93+
{
94+
return (SubClassTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { });
95+
}
96+
97+
public static IInterfaceTest create_instance_interface(Type t)
9398
{
9499
return (IInterfaceTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { });
95100
}
96101

97-
// test instances pass through managed code unchanged
98-
public static IInterfaceTest pass_through(IInterfaceTest s)
102+
// test instances pass through managed code unchanged ...
103+
public static SubClassTest pass_through(SubClassTest s)
104+
{
105+
return s;
106+
}
107+
108+
// ... but the return type is an interface type, objects get wrapped
109+
public static IInterfaceTest pass_through_interface(IInterfaceTest s)
99110
{
100111
return s;
101112
}

src/tests/test_array.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,9 +1288,10 @@ def test_special_array_creation():
12881288
assert value[1].__class__ == inst.__class__
12891289
assert value.Length == 2
12901290

1291+
iface_class = ISayHello1(inst).__class__
12911292
value = Array[ISayHello1]([inst, inst])
1292-
assert value[0].__class__ == inst.__class__
1293-
assert value[1].__class__ == inst.__class__
1293+
assert value[0].__class__ == iface_class
1294+
assert value[1].__class__ == iface_class
12941295
assert value.Length == 2
12951296

12961297
inst = System.Exception("badness")

src/tests/test_generic.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ def test_generic_method_type_handling():
319319
assert_generic_method_by_type(ShortEnum, ShortEnum.Zero)
320320
assert_generic_method_by_type(System.Object, InterfaceTest())
321321
assert_generic_method_by_type(InterfaceTest, InterfaceTest(), 1)
322-
assert_generic_method_by_type(ISayHello1, InterfaceTest(), 1)
323322

324323

325324
def test_correct_overload_selection():
@@ -548,10 +547,11 @@ def test_method_overload_selection_with_generic_types():
548547
value = MethodTest.Overloaded.__overloads__[vtype](input_)
549548
assert value.value.__class__ == inst.__class__
550549

550+
iface_class = ISayHello1(inst).__class__
551551
vtype = GenericWrapper[ISayHello1]
552552
input_ = vtype(inst)
553553
value = MethodTest.Overloaded.__overloads__[vtype](input_)
554-
assert value.value.__class__ == inst.__class__
554+
assert value.value.__class__ == iface_class
555555

556556
vtype = System.Array[GenericWrapper[int]]
557557
input_ = vtype([GenericWrapper[int](0), GenericWrapper[int](1)])
@@ -726,11 +726,12 @@ def test_overload_selection_with_arrays_of_generic_types():
726726
assert value[0].value.__class__ == inst.__class__
727727
assert value.Length == 2
728728

729+
iface_class = ISayHello1(inst).__class__
729730
gtype = GenericWrapper[ISayHello1]
730731
vtype = System.Array[gtype]
731732
input_ = vtype([gtype(inst), gtype(inst)])
732733
value = MethodTest.Overloaded.__overloads__[vtype](input_)
733-
assert value[0].value.__class__ == inst.__class__
734+
assert value[0].value.__class__ == iface_class
734735
assert value.Length == 2
735736

736737

src/tests/test_interface.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,35 @@ def test_explicit_cast_to_interface():
6767
assert i2.SayHello() == 'hello 2'
6868
assert hasattr(i2, 'SayHello')
6969
assert not hasattr(i2, 'HelloProperty')
70+
71+
72+
def test_interface_object_returned_through_method():
73+
"""Test interface type is used if method return type is interface"""
74+
from Python.Test import InterfaceTest
75+
76+
ob = InterfaceTest()
77+
hello1 = ob.GetISayHello1()
78+
assert type(hello1).__name__ == 'ISayHello1'
79+
80+
assert hello1.SayHello() == 'hello 1'
81+
82+
83+
def test_interface_object_returned_through_out_param():
84+
"""Test interface type is used for out parameters of interface types"""
85+
from Python.Test import InterfaceTest
86+
87+
ob = InterfaceTest()
88+
hello2 = ob.GetISayHello2(None)
89+
assert type(hello2).__name__ == 'ISayHello2'
90+
91+
assert hello2.SayHello() == 'hello 2'
92+
93+
94+
def test_null_interface_object_returned():
95+
"""Test None is used also for methods with interface return types"""
96+
from Python.Test import InterfaceTest
97+
98+
ob = InterfaceTest()
99+
hello1, hello2 = ob.GetNoSayHello(None)
100+
assert hello1 is None
101+
assert hello2 is None

src/tests/test_method.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,10 @@ def test_explicit_overload_selection():
564564
value = MethodTest.Overloaded.__overloads__[InterfaceTest](inst)
565565
assert value.__class__ == inst.__class__
566566

567+
iface_class = ISayHello1(InterfaceTest()).__class__
567568
value = MethodTest.Overloaded.__overloads__[ISayHello1](inst)
568-
assert value.__class__ == inst.__class__
569+
assert value.__class__ != inst.__class__
570+
assert value.__class__ == iface_class
569571

570572
atype = Array[System.Object]
571573
value = MethodTest.Overloaded.__overloads__[str, int, atype](
@@ -718,11 +720,12 @@ def test_overload_selection_with_array_types():
718720
assert value[0].__class__ == inst.__class__
719721
assert value[1].__class__ == inst.__class__
720722

723+
iface_class = ISayHello1(inst).__class__
721724
vtype = Array[ISayHello1]
722725
input_ = vtype([inst, inst])
723726
value = MethodTest.Overloaded.__overloads__[vtype](input_)
724-
assert value[0].__class__ == inst.__class__
725-
assert value[1].__class__ == inst.__class__
727+
assert value[0].__class__ == iface_class
728+
assert value[1].__class__ == iface_class
726729

727730

728731
def test_explicit_overload_selection_failure():

src/tests/test_subclass.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ def test_interface():
104104
assert ob.bar("bar", 2) == "bar/bar"
105105
assert FunctionsTest.test_bar(ob, "bar", 2) == "bar/bar"
106106

107-
x = FunctionsTest.pass_through(ob)
108-
assert id(x) == id(ob)
107+
# pass_through will convert from InterfaceTestClass -> IInterfaceTest,
108+
# causing a new wrapper object to be created. Hence id will differ.
109+
x = FunctionsTest.pass_through_interface(ob)
110+
assert id(x) != id(ob)
109111

110112

111113
def test_derived_class():
@@ -173,14 +175,14 @@ def test_create_instance():
173175
assert id(x) == id(ob)
174176

175177
InterfaceTestClass = interface_test_class_fixture(test_create_instance.__name__)
176-
ob2 = FunctionsTest.create_instance(InterfaceTestClass)
178+
ob2 = FunctionsTest.create_instance_interface(InterfaceTestClass)
177179
assert ob2.foo() == "InterfaceTestClass"
178180
assert FunctionsTest.test_foo(ob2) == "InterfaceTestClass"
179181
assert ob2.bar("bar", 2) == "bar/bar"
180182
assert FunctionsTest.test_bar(ob2, "bar", 2) == "bar/bar"
181183

182-
y = FunctionsTest.pass_through(ob2)
183-
assert id(y) == id(ob2)
184+
y = FunctionsTest.pass_through_interface(ob2)
185+
assert id(y) != id(ob2)
184186

185187

186188
def test_events():

0 commit comments

Comments
 (0)