Skip to content

Improved support for generic method overloading #1657

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 1 commit into from
Jan 5, 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
38 changes: 21 additions & 17 deletions src/runtime/methodbinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,17 @@ internal void AddMethod(MethodBase m)

/// <summary>
/// Given a sequence of MethodInfo and a sequence of type parameters,
/// return the MethodInfo that represents the matching closed generic.
/// return the MethodInfo(s) that represents the matching closed generic.
/// If unsuccessful, returns null and may set a Python error.
/// </summary>
internal static MethodInfo? MatchParameters(MethodBase[] mi, Type[]? tp)
internal static MethodInfo[] MatchParameters(MethodBase[] mi, Type[]? tp)
{
if (tp == null)
{
return null;
return Array.Empty<MethodInfo>();
}
int count = tp.Length;
var result = new List<MethodInfo>();
foreach (MethodInfo t in mi)
{
if (!t.IsGenericMethodDefinition)
Expand All @@ -111,16 +112,14 @@ internal void AddMethod(MethodBase m)
{
// MakeGenericMethod can throw ArgumentException if the type parameters do not obey the constraints.
MethodInfo method = t.MakeGenericMethod(tp);
Exceptions.Clear();
return method;
result.Add(method);
}
catch (ArgumentException e)
catch (ArgumentException)
{
Exceptions.SetError(e);
// The error will remain set until cleared by a successful match.
}
}
return null;
return result.ToArray();
}


Expand Down Expand Up @@ -381,9 +380,6 @@ public MismatchedMethod(Exception exception, MethodBase mb)
}
}

var pynargs = (int)Runtime.PyTuple_Size(args);
var isGeneric = false;

MethodBase[] _methods;
if (info != null)
{
Expand All @@ -395,11 +391,19 @@ public MismatchedMethod(Exception exception, MethodBase mb)
_methods = GetMethods();
}

var argMatchedMethods = new List<MatchedMethod>(_methods.Length);
return Bind(inst, args, kwargDict, _methods, matchGenerics: true);
}

static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary<string, PyObject> kwargDict, MethodBase[] methods, bool matchGenerics)
{
var pynargs = (int)Runtime.PyTuple_Size(args);
var isGeneric = false;

var argMatchedMethods = new List<MatchedMethod>(methods.Length);
var mismatchedMethods = new List<MismatchedMethod>();

// TODO: Clean up
foreach (MethodBase mi in _methods)
foreach (MethodBase mi in methods)
{
if (mi.IsGenericMethod)
{
Expand Down Expand Up @@ -535,17 +539,17 @@ public MismatchedMethod(Exception exception, MethodBase mb)

return new Binding(mi, target, margs, outs);
}
else if (isGeneric && info == null && methodinfo != null)
else if (matchGenerics && isGeneric)
{
// We weren't able to find a matching method but at least one
// is a generic method and info is null. That happens when a generic
// method was not called using the [] syntax. Let's introspect the
// type of the arguments and use it to construct the correct method.
Type[]? types = Runtime.PythonArgsToTypeArray(args, true);
MethodInfo? mi = MatchParameters(methodinfo, types);
if (mi != null)
MethodInfo[] overloads = MatchParameters(methods, types);
if (overloads.Length != 0)
{
return Bind(inst, args, kw, mi, null);
return Bind(inst, args, kwargDict, overloads, matchGenerics: false);
}
}
if (mismatchedMethods.Count > 0)
Expand Down
11 changes: 7 additions & 4 deletions src/runtime/methodbinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,18 @@ public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference
return Exceptions.RaiseTypeError("type(s) expected");
}

MethodBase? mi = self.m.IsInstanceConstructor
? self.m.type.Value.GetConstructor(types)
MethodBase[] overloads = self.m.IsInstanceConstructor
? self.m.type.Value.GetConstructor(types) is { } ctor
? new[] { ctor }
: Array.Empty<MethodBase>()
: MethodBinder.MatchParameters(self.m.info, types);
if (mi == null)
if (overloads.Length == 0)
{
return Exceptions.RaiseTypeError("No match found for given type params");
}

var mb = new MethodBinding(self.m, self.target, self.targetType) { info = mi };
MethodObject overloaded = self.m.WithOverloads(overloads);
var mb = new MethodBinding(overloaded, self.target, self.targetType);
return mb.Alloc();
}

Expand Down
5 changes: 4 additions & 1 deletion src/runtime/methodobject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class MethodObject : ExtensionType
internal PyString? doc;
internal MaybeType type;

public MethodObject(Type type, string name, MethodBase[] info, bool allow_threads = MethodBinder.DefaultAllowThreads)
public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads = MethodBinder.DefaultAllowThreads)
{
this.type = type;
this.name = name;
Expand All @@ -47,6 +47,9 @@ public MethodObject(Type type, string name, MethodBase[] info, bool allow_thread

public bool IsInstanceConstructor => name == "__init__";

public MethodObject WithOverloads(MethodBase[] overloads)
=> new(type, name, overloads, allow_threads: binder.allow_threads);

internal MethodBase[] info
{
get
Expand Down
3 changes: 3 additions & 0 deletions src/testing/methodtest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,9 @@ public static int Overloaded(int i, string s)
return i;
}

public virtual void OverloadedConstrainedGeneric<T>(T generic) where T : MethodTest { }
public virtual void OverloadedConstrainedGeneric<T>(T generic, string str) where T: MethodTest { }

public static string CaseSensitive()
{
return "CaseSensitive";
Expand Down
12 changes: 12 additions & 0 deletions tests/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,18 @@ def test_missing_generic_type():
with pytest.raises(TypeError):
IList[bool]

# https://github.com/pythonnet/pythonnet/issues/1522
def test_overload_generic_parameter():
from Python.Test import MethodTest, MethodTestSub

inst = MethodTest()
generic = MethodTestSub()
inst.OverloadedConstrainedGeneric(generic)
inst.OverloadedConstrainedGeneric[MethodTestSub](generic)

inst.OverloadedConstrainedGeneric[MethodTestSub](generic, '42')
inst.OverloadedConstrainedGeneric[MethodTestSub](generic, System.String('42'))

def test_invalid_generic_type_parameter():
from Python.Test import GenericTypeWithConstraint
with pytest.raises(TypeError):
Expand Down