diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c48ecb90..60494e67a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,11 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
details about the cause of the failure
- `clr.AddReference` no longer adds ".dll" implicitly
- `PyIter(PyObject)` constructor replaced with static `PyIter.GetIter(PyObject)` method
+- Return values from .NET methods that return an interface are now automatically
+ wrapped in that interface. This is a breaking change for users that rely on being
+ able to access members that are part of the implementation class, but not the
+ interface. Use the new __implementation__ or __raw_implementation__ properties to
+ if you need to "downcast" to the implementation class.
### Fixed
diff --git a/src/runtime/arrayobject.cs b/src/runtime/arrayobject.cs
index 1ef318473..2d50b3a1d 100644
--- a/src/runtime/arrayobject.cs
+++ b/src/runtime/arrayobject.cs
@@ -43,8 +43,9 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
public static IntPtr mp_subscript(IntPtr ob, IntPtr idx)
{
var obj = (CLRObject)GetManagedObject(ob);
+ var arrObj = (ArrayObject)GetManagedObjectType(ob);
var items = obj.inst as Array;
- Type itemType = obj.inst.GetType().GetElementType();
+ Type itemType = arrObj.type.GetElementType();
int rank = items.Rank;
int index;
object value;
diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs
index 58b372fa1..98fe99141 100644
--- a/src/runtime/converter.cs
+++ b/src/runtime/converter.cs
@@ -173,6 +173,21 @@ internal static IntPtr ToPython(object value, Type type)
}
}
+ if (type.IsInterface)
+ {
+ var ifaceObj = (InterfaceObject)ClassManager.GetClass(type);
+ return ifaceObj.WrapObject(value);
+ }
+
+ // We need to special case interface array handling to ensure we
+ // produce the correct type. Value may be an array of some concrete
+ // type (FooImpl[]), but we want access to go via the interface type
+ // (IFoo[]).
+ if (type.IsArray && type.GetElementType().IsInterface)
+ {
+ return CLRObject.GetInstHandle(value, type);
+ }
+
// 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;
diff --git a/src/runtime/interfaceobject.cs b/src/runtime/interfaceobject.cs
index 616ced6bd..74396f50c 100644
--- a/src/runtime/interfaceobject.cs
+++ b/src/runtime/interfaceobject.cs
@@ -71,7 +71,43 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
return IntPtr.Zero;
}
- return CLRObject.GetInstHandle(obj, self.pyHandle);
+ return self.WrapObject(obj);
+ }
+
+ ///
+ /// Wrap the given object in an interface object, so that only methods
+ /// of the interface are available.
+ ///
+ public IntPtr WrapObject(object impl)
+ {
+ var objPtr = CLRObject.GetInstHandle(impl, pyHandle);
+ return objPtr;
+ }
+
+ ///
+ /// Expose the wrapped implementation through attributes in both
+ /// converted/encoded (__implementation__) and raw (__raw_implementation__) form.
+ ///
+ public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
+ {
+ var clrObj = (CLRObject)GetManagedObject(ob);
+
+ if (!Runtime.PyString_Check(key))
+ {
+ return Exceptions.RaiseTypeError("string expected");
+ }
+
+ string name = Runtime.GetManagedString(key);
+ if (name == "__implementation__")
+ {
+ return Converter.ToPython(clrObj.inst);
+ }
+ else if (name == "__raw_implementation__")
+ {
+ return CLRObject.GetInstHandle(clrObj.inst);
+ }
+
+ return Runtime.PyObject_GenericGetAttr(ob, key);
}
}
}
diff --git a/src/runtime/managedtype.cs b/src/runtime/managedtype.cs
index 23f5898d1..afa3bf2d7 100644
--- a/src/runtime/managedtype.cs
+++ b/src/runtime/managedtype.cs
@@ -45,6 +45,25 @@ internal static ManagedType GetManagedObject(IntPtr ob)
return null;
}
+ ///
+ /// Given a Python object, return the associated managed object type or null.
+ ///
+ internal static ManagedType GetManagedObjectType(IntPtr ob)
+ {
+ if (ob != IntPtr.Zero)
+ {
+ IntPtr tp = Runtime.PyObject_TYPE(ob);
+ var flags = Util.ReadCLong(tp, TypeOffset.tp_flags);
+ if ((flags & TypeFlags.Managed) != 0)
+ {
+ tp = Marshal.ReadIntPtr(tp, TypeOffset.magic());
+ var gc = (GCHandle)tp;
+ return (ManagedType)gc.Target;
+ }
+ }
+ return null;
+ }
+
internal static ManagedType GetManagedObjectErr(IntPtr ob)
{
diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs
index df8579ca4..e2d8581b3 100644
--- a/src/runtime/methodbinder.cs
+++ b/src/runtime/methodbinder.cs
@@ -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++;
}
diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs
index be3ad1b88..985f7f19a 100644
--- a/src/runtime/typemanager.cs
+++ b/src/runtime/typemanager.cs
@@ -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_);
diff --git a/src/testing/interfacetest.cs b/src/testing/interfacetest.cs
index 2c24596bc..0158d64da 100644
--- a/src/testing/interfacetest.cs
+++ b/src/testing/interfacetest.cs
@@ -11,7 +11,6 @@ internal interface IInternalInterface
{
}
-
public interface ISayHello1
{
string SayHello();
@@ -43,6 +42,27 @@ 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 ISayHello1 [] GetISayHello1Array()
+ {
+ return new[] { this };
+ }
+
public interface IPublic
{
}
diff --git a/src/testing/subclasstest.cs b/src/testing/subclasstest.cs
index 9817d865e..ab0b73368 100644
--- a/src/testing/subclasstest.cs
+++ b/src/testing/subclasstest.cs
@@ -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;
}
diff --git a/src/tests/test_array.py b/src/tests/test_array.py
index 427958ec7..990af550a 100644
--- a/src/tests/test_array.py
+++ b/src/tests/test_array.py
@@ -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")
diff --git a/src/tests/test_generic.py b/src/tests/test_generic.py
index 9c410271d..c7e5a8d4d 100644
--- a/src/tests/test_generic.py
+++ b/src/tests/test_generic.py
@@ -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():
@@ -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)])
@@ -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
diff --git a/src/tests/test_interface.py b/src/tests/test_interface.py
index 6b72c1a12..e6c6ba64b 100644
--- a/src/tests/test_interface.py
+++ b/src/tests/test_interface.py
@@ -61,9 +61,62 @@ def test_explicit_cast_to_interface():
assert hasattr(i1, 'SayHello')
assert i1.SayHello() == 'hello 1'
assert not hasattr(i1, 'HelloProperty')
+ assert i1.__implementation__ == ob
+ assert i1.__raw_implementation__ == ob
i2 = Test.ISayHello2(ob)
assert type(i2).__name__ == 'ISayHello2'
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.__implementation__.__class__.__name__ == "InterfaceTest"
+
+ 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
+
+def test_interface_array_returned():
+ """Test interface type used for methods returning interface arrays"""
+ from Python.Test import InterfaceTest
+
+ ob = InterfaceTest()
+ hellos = ob.GetISayHello1Array()
+ assert type(hellos[0]).__name__ == 'ISayHello1'
+ assert hellos[0].__implementation__.__class__.__name__ == "InterfaceTest"
+
+def test_implementation_access():
+ """Test the __implementation__ and __raw_implementation__ properties"""
+ import System
+ clrVal = System.Int32(100)
+ i = System.IComparable(clrVal)
+ assert 100 == i.__implementation__
+ assert clrVal == i.__raw_implementation__
+ assert i.__implementation__ != i.__raw_implementation__
diff --git a/src/tests/test_method.py b/src/tests/test_method.py
index a358025a5..15327cb3d 100644
--- a/src/tests/test_method.py
+++ b/src/tests/test_method.py
@@ -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](
@@ -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():
diff --git a/src/tests/test_subclass.py b/src/tests/test_subclass.py
index 07eaf7f82..968f6a788 100644
--- a/src/tests/test_subclass.py
+++ b/src/tests/test_subclass.py
@@ -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():
@@ -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():