Skip to content

Commit 50d947f

Browse files
authored
Merge pull request #1240 from danabr/auto-cast-ret-val-to-interface
Wrap returned objects in interface if method return type is interface
2 parents d44f1da + c46ab75 commit 50d947f

14 files changed

+189
-22
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1515
details about the cause of the failure
1616
- `clr.AddReference` no longer adds ".dll" implicitly
1717
- `PyIter(PyObject)` constructor replaced with static `PyIter.GetIter(PyObject)` method
18+
- Return values from .NET methods that return an interface are now automatically
19+
wrapped in that interface. This is a breaking change for users that rely on being
20+
able to access members that are part of the implementation class, but not the
21+
interface. Use the new __implementation__ or __raw_implementation__ properties to
22+
if you need to "downcast" to the implementation class.
1823

1924
### Fixed
2025

src/runtime/arrayobject.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
4343
public static IntPtr mp_subscript(IntPtr ob, IntPtr idx)
4444
{
4545
var obj = (CLRObject)GetManagedObject(ob);
46+
var arrObj = (ArrayObject)GetManagedObjectType(ob);
4647
var items = obj.inst as Array;
47-
Type itemType = obj.inst.GetType().GetElementType();
48+
Type itemType = arrObj.type.GetElementType();
4849
int rank = items.Rank;
4950
int index;
5051
object value;

src/runtime/converter.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,21 @@ 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 ifaceObj.WrapObject(value);
180+
}
181+
182+
// We need to special case interface array handling to ensure we
183+
// produce the correct type. Value may be an array of some concrete
184+
// type (FooImpl[]), but we want access to go via the interface type
185+
// (IFoo[]).
186+
if (type.IsArray && type.GetElementType().IsInterface)
187+
{
188+
return CLRObject.GetInstHandle(value, type);
189+
}
190+
176191
// it the type is a python subclass of a managed type then return the
177192
// underlying python object rather than construct a new wrapper object.
178193
var pyderived = value as IPythonDerivedType;

src/runtime/interfaceobject.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,43 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
7171
return IntPtr.Zero;
7272
}
7373

74-
return CLRObject.GetInstHandle(obj, self.pyHandle);
74+
return self.WrapObject(obj);
75+
}
76+
77+
/// <summary>
78+
/// Wrap the given object in an interface object, so that only methods
79+
/// of the interface are available.
80+
/// </summary>
81+
public IntPtr WrapObject(object impl)
82+
{
83+
var objPtr = CLRObject.GetInstHandle(impl, pyHandle);
84+
return objPtr;
85+
}
86+
87+
/// <summary>
88+
/// Expose the wrapped implementation through attributes in both
89+
/// converted/encoded (__implementation__) and raw (__raw_implementation__) form.
90+
/// </summary>
91+
public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
92+
{
93+
var clrObj = (CLRObject)GetManagedObject(ob);
94+
95+
if (!Runtime.PyString_Check(key))
96+
{
97+
return Exceptions.RaiseTypeError("string expected");
98+
}
99+
100+
string name = Runtime.GetManagedString(key);
101+
if (name == "__implementation__")
102+
{
103+
return Converter.ToPython(clrObj.inst);
104+
}
105+
else if (name == "__raw_implementation__")
106+
{
107+
return CLRObject.GetInstHandle(clrObj.inst);
108+
}
109+
110+
return Runtime.PyObject_GenericGetAttr(ob, key);
75111
}
76112
}
77113
}

src/runtime/managedtype.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ internal static ManagedType GetManagedObject(IntPtr ob)
4545
return null;
4646
}
4747

48+
/// <summary>
49+
/// Given a Python object, return the associated managed object type or null.
50+
/// </summary>
51+
internal static ManagedType GetManagedObjectType(IntPtr ob)
52+
{
53+
if (ob != IntPtr.Zero)
54+
{
55+
IntPtr tp = Runtime.PyObject_TYPE(ob);
56+
var flags = Util.ReadCLong(tp, TypeOffset.tp_flags);
57+
if ((flags & TypeFlags.Managed) != 0)
58+
{
59+
tp = Marshal.ReadIntPtr(tp, TypeOffset.magic());
60+
var gc = (GCHandle)tp;
61+
return (ManagedType)gc.Target;
62+
}
63+
}
64+
return null;
65+
}
66+
4867

4968
internal static ManagedType GetManagedObjectErr(IntPtr ob)
5069
{

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: 21 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,27 @@ 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+
61+
public ISayHello1 [] GetISayHello1Array()
62+
{
63+
return new[] { this };
64+
}
65+
4666
public interface IPublic
4767
{
4868
}

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")

0 commit comments

Comments
 (0)