Skip to content

Fix implementing a generic interface with a Python class #1998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
28 changes: 20 additions & 8 deletions src/runtime/Types/ClassDerived.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild
il.Emit(OpCodes.Ldloc_0);

il.Emit(OpCodes.Ldtoken, method);
il.Emit(OpCodes.Ldtoken, method.DeclaringType);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only
if (method.ReturnType == typeof(void))
{
Expand Down Expand Up @@ -505,6 +506,7 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde

il.DeclareLocal(typeof(object[]));
il.DeclareLocal(typeof(RuntimeMethodHandle));
il.DeclareLocal(typeof(RuntimeTypeHandle));

// this
il.Emit(OpCodes.Ldarg_0);
Expand Down Expand Up @@ -546,6 +548,11 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde
il.Emit(OpCodes.Ldloca_S, 1);
il.Emit(OpCodes.Initobj, typeof(RuntimeMethodHandle));
il.Emit(OpCodes.Ldloc_1);

// type handle is also not required
il.Emit(OpCodes.Ldloca_S, 2);
il.Emit(OpCodes.Initobj, typeof(RuntimeTypeHandle));
il.Emit(OpCodes.Ldloc_2);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only

// invoke the method
Expand Down Expand Up @@ -698,7 +705,7 @@ public class PythonDerivedType
/// class) it calls it, otherwise it calls the base method.
/// </summary>
public static T? InvokeMethod<T>(IPythonDerivedType obj, string methodName, string origMethodName,
object[] args, RuntimeMethodHandle methodHandle)
object[] args, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle)
{
var self = GetPyObj(obj);

Expand All @@ -724,7 +731,10 @@ public class PythonDerivedType
}

PyObject py_result = method.Invoke(pyargs);
PyTuple? result_tuple = MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 1);
var clrMethod = methodHandle != default
? MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle)
: null;
PyTuple? result_tuple = MarshalByRefsBack(args, clrMethod, py_result, outsOffset: 1);
return result_tuple is not null
? result_tuple[0].As<T>()
: py_result.As<T>();
Expand Down Expand Up @@ -754,7 +764,7 @@ public class PythonDerivedType
}

public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, string origMethodName,
object?[] args, RuntimeMethodHandle methodHandle)
object?[] args, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle)
{
var self = GetPyObj(obj);
if (null != self.Ref)
Expand All @@ -779,7 +789,10 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
}

PyObject py_result = method.Invoke(pyargs);
MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 0);
var clrMethod = methodHandle != default
? MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle)
: null;
MarshalByRefsBack(args, clrMethod, py_result, outsOffset: 0);
return;
}
}
Expand Down Expand Up @@ -811,12 +824,11 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
/// as a tuple of new values for those arguments, and updates corresponding
/// elements of <paramref name="args"/> array.
/// </summary>
private static PyTuple? MarshalByRefsBack(object?[] args, RuntimeMethodHandle methodHandle, PyObject pyResult, int outsOffset)
private static PyTuple? MarshalByRefsBack(object?[] args, MethodBase? method, PyObject pyResult, int outsOffset)
{
if (methodHandle == default) return null;
if (method is null) return null;

var originalMethod = MethodBase.GetMethodFromHandle(methodHandle);
var parameters = originalMethod.GetParameters();
var parameters = method.GetParameters();
PyTuple? outs = null;
int byrefIndex = 0;
for (int i = 0; i < parameters.Length; ++i)
Expand Down
23 changes: 22 additions & 1 deletion src/testing/interfacetest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private interface IPrivate
{
}
}

public interface IOutArg
{
string MyMethod_Out(string name, out int index);
Expand All @@ -93,4 +93,25 @@ public static int CallMyMethod_Out(IOutArg myInterface)
return index;
}
}

public interface IGenericInterface<T>
{
public T Get(T x);
}

public class SpecificInterfaceUser
{
public SpecificInterfaceUser(IGenericInterface<int> some, int x)
{
some.Get(x);
}
}

public class GenericInterfaceUser<T>
{
public GenericInterfaceUser(IGenericInterface<T> some, T x)
{
some.Get(x);
}
}
}
23 changes: 22 additions & 1 deletion tests/test_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import System
import pytest
from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest,
FunctionsTest)
FunctionsTest, IGenericInterface)
from System.Collections.Generic import List


Expand All @@ -29,6 +29,17 @@ def bar(self, x, i):
return InterfaceTestClass


def interface_generic_class_fixture(subnamespace):

class GenericInterfaceImpl(IGenericInterface[int]):
__namespace__ = "Python.Test." + subnamespace

def Get(self, x):
return x

return GenericInterfaceImpl


def derived_class_fixture(subnamespace):
"""Delay creation of class until test starts."""

Expand Down Expand Up @@ -306,3 +317,13 @@ class Derived(BaseClass):

import gc
gc.collect()

def test_generic_interface():
from System import Int32
from Python.Test import GenericInterfaceUser, SpecificInterfaceUser

GenericInterfaceImpl = interface_generic_class_fixture(test_generic_interface.__name__)

obj = GenericInterfaceImpl()
SpecificInterfaceUser(obj, Int32(0))
GenericInterfaceUser[Int32](obj, Int32(0))