Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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.
  • Loading branch information
danabr committed Oct 1, 2020
commit 1dd36ae3c978c1105ceac892a142e6980b4a0640
7 changes: 7 additions & 0 deletions src/runtime/converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ internal static IntPtr ToPython(object value, Type type)
}
}

if (type.IsInterface)
{
var ifaceObj = (InterfaceObject)ClassManager.GetClass(type);
return CLRObject.GetInstHandle(value, ifaceObj.pyHandle);
}

// it the type is a python subclass of a managed type then return the
// underlying python object rather than construct a new wrapper object.
var pyderived = value as IPythonDerivedType;
Expand All @@ -182,6 +188,7 @@ internal static IntPtr ToPython(object value, Type type)
return ClassDerivedObject.ToPython(pyderived);
}


// hmm - from Python, we almost never care what the declared
// type is. we'd rather have the object bound to the actual
// implementing class.
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/methodbinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
Type pt = pi[i].ParameterType;
if (pi[i].IsOut || pt.IsByRef)
{
v = Converter.ToPython(binding.args[i], pt);
v = Converter.ToPython(binding.args[i], pt.GetElementType());
Runtime.PyTuple_SetItem(t, n, v);
n++;
}
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/typemanager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType)
// we want to do this after the slot stuff above in case the class itself implements a slot method
InitializeSlots(type, impl.GetType());

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


if (base_ != IntPtr.Zero)
{
Marshal.WriteIntPtr(type, TypeOffset.tp_base, base_);
Expand Down
17 changes: 16 additions & 1 deletion src/testing/interfacetest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ internal interface IInternalInterface
{
}


public interface ISayHello1
{
string SayHello();
Expand Down Expand Up @@ -43,6 +42,22 @@ string ISayHello2.SayHello()
return "hello 2";
}

public ISayHello1 GetISayHello1()
{
return this;
}

public void GetISayHello2(out ISayHello2 hello2)
{
hello2 = this;
}

public ISayHello1 GetNoSayHello(out ISayHello2 hello2)
{
hello2 = null;
return null;
}

public interface IPublic
{
}
Expand Down
17 changes: 14 additions & 3 deletions src/testing/subclasstest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,24 @@ public static string test_bar(IInterfaceTest x, string s, int i)
}

// test instances can be constructed in managed code
public static IInterfaceTest create_instance(Type t)
public static SubClassTest create_instance(Type t)
{
return (SubClassTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { });
}

public static IInterfaceTest create_instance_interface(Type t)
{
return (IInterfaceTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { });
}

// test instances pass through managed code unchanged
public static IInterfaceTest pass_through(IInterfaceTest s)
// test instances pass through managed code unchanged ...
public static SubClassTest pass_through(SubClassTest s)
{
return s;
}

// ... but the return type is an interface type, objects get wrapped
public static IInterfaceTest pass_through_interface(IInterfaceTest s)
{
return s;
}
Expand Down
5 changes: 3 additions & 2 deletions src/tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,9 +1288,10 @@ def test_special_array_creation():
assert value[1].__class__ == inst.__class__
assert value.Length == 2

iface_class = ISayHello1(inst).__class__
value = Array[ISayHello1]([inst, inst])
assert value[0].__class__ == inst.__class__
assert value[1].__class__ == inst.__class__
assert value[0].__class__ == iface_class
assert value[1].__class__ == iface_class
assert value.Length == 2

inst = System.Exception("badness")
Expand Down
7 changes: 4 additions & 3 deletions src/tests/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@ def test_generic_method_type_handling():
assert_generic_method_by_type(ShortEnum, ShortEnum.Zero)
assert_generic_method_by_type(System.Object, InterfaceTest())
assert_generic_method_by_type(InterfaceTest, InterfaceTest(), 1)
assert_generic_method_by_type(ISayHello1, InterfaceTest(), 1)


def test_correct_overload_selection():
Expand Down Expand Up @@ -548,10 +547,11 @@ 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__
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)])
Expand Down Expand Up @@ -726,11 +726,12 @@ 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__
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


Expand Down
32 changes: 32 additions & 0 deletions src/tests/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,35 @@ def test_explicit_cast_to_interface():
assert i2.SayHello() == 'hello 2'
assert hasattr(i2, 'SayHello')
assert not hasattr(i2, 'HelloProperty')


def test_interface_object_returned_through_method():
"""Test interface type is used if method return type is interface"""
from Python.Test import InterfaceTest

ob = InterfaceTest()
hello1 = ob.GetISayHello1()
assert type(hello1).__name__ == 'ISayHello1'

assert hello1.SayHello() == 'hello 1'


def test_interface_object_returned_through_out_param():
"""Test interface type is used for out parameters of interface types"""
from Python.Test import InterfaceTest

ob = InterfaceTest()
hello2 = ob.GetISayHello2(None)
assert type(hello2).__name__ == 'ISayHello2'

assert hello2.SayHello() == 'hello 2'


def test_null_interface_object_returned():
"""Test None is used also for methods with interface return types"""
from Python.Test import InterfaceTest

ob = InterfaceTest()
hello1, hello2 = ob.GetNoSayHello(None)
assert hello1 is None
assert hello2 is None
9 changes: 6 additions & 3 deletions src/tests/test_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,10 @@ def test_explicit_overload_selection():
value = MethodTest.Overloaded.__overloads__[InterfaceTest](inst)
assert value.__class__ == inst.__class__

iface_class = ISayHello1(InterfaceTest()).__class__
value = MethodTest.Overloaded.__overloads__[ISayHello1](inst)
assert value.__class__ == inst.__class__
assert value.__class__ != inst.__class__
assert value.__class__ == iface_class

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

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():
Expand Down
12 changes: 7 additions & 5 deletions src/tests/test_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ def test_interface():
assert ob.bar("bar", 2) == "bar/bar"
assert FunctionsTest.test_bar(ob, "bar", 2) == "bar/bar"

x = FunctionsTest.pass_through(ob)
assert id(x) == id(ob)
# 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)


def test_derived_class():
Expand Down Expand Up @@ -173,14 +175,14 @@ def test_create_instance():
assert id(x) == id(ob)

InterfaceTestClass = interface_test_class_fixture(test_create_instance.__name__)
ob2 = FunctionsTest.create_instance(InterfaceTestClass)
ob2 = FunctionsTest.create_instance_interface(InterfaceTestClass)
assert ob2.foo() == "InterfaceTestClass"
assert FunctionsTest.test_foo(ob2) == "InterfaceTestClass"
assert ob2.bar("bar", 2) == "bar/bar"
assert FunctionsTest.test_bar(ob2, "bar", 2) == "bar/bar"

y = FunctionsTest.pass_through(ob2)
assert id(y) == id(ob2)
y = FunctionsTest.pass_through_interface(ob2)
assert id(y) != id(ob2)


def test_events():
Expand Down