Skip to content

Commit fabb22c

Browse files
committed
improved support for generic method overloading
Prior to this change if method had multiple generic overloads, only 1 of them could be matched (whichever one reflection would return first) Now MethodBinder.MatchParameters returns all matching generic overloads, not just the first one. fixes pythonnet#1522
1 parent c4238d9 commit fabb22c

File tree

5 files changed

+47
-22
lines changed

5 files changed

+47
-22
lines changed

src/runtime/methodbinder.cs

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,17 @@ internal void AddMethod(MethodBase m)
8686

8787
/// <summary>
8888
/// Given a sequence of MethodInfo and a sequence of type parameters,
89-
/// return the MethodInfo that represents the matching closed generic.
89+
/// return the MethodInfo(s) that represents the matching closed generic.
9090
/// If unsuccessful, returns null and may set a Python error.
9191
/// </summary>
92-
internal static MethodInfo? MatchParameters(MethodBase[] mi, Type[]? tp)
92+
internal static MethodInfo[] MatchParameters(MethodBase[] mi, Type[]? tp)
9393
{
9494
if (tp == null)
9595
{
96-
return null;
96+
return Array.Empty<MethodInfo>();
9797
}
9898
int count = tp.Length;
99+
var result = new List<MethodInfo>();
99100
foreach (MethodInfo t in mi)
100101
{
101102
if (!t.IsGenericMethodDefinition)
@@ -111,16 +112,14 @@ internal void AddMethod(MethodBase m)
111112
{
112113
// MakeGenericMethod can throw ArgumentException if the type parameters do not obey the constraints.
113114
MethodInfo method = t.MakeGenericMethod(tp);
114-
Exceptions.Clear();
115-
return method;
115+
result.Add(method);
116116
}
117-
catch (ArgumentException e)
117+
catch (ArgumentException)
118118
{
119-
Exceptions.SetError(e);
120119
// The error will remain set until cleared by a successful match.
121120
}
122121
}
123-
return null;
122+
return result.ToArray();
124123
}
125124

126125

@@ -381,9 +380,6 @@ public MismatchedMethod(Exception exception, MethodBase mb)
381380
}
382381
}
383382

384-
var pynargs = (int)Runtime.PyTuple_Size(args);
385-
var isGeneric = false;
386-
387383
MethodBase[] _methods;
388384
if (info != null)
389385
{
@@ -395,11 +391,19 @@ public MismatchedMethod(Exception exception, MethodBase mb)
395391
_methods = GetMethods();
396392
}
397393

398-
var argMatchedMethods = new List<MatchedMethod>(_methods.Length);
394+
return Bind(inst, args, kwargDict, _methods, matchGenerics: true);
395+
}
396+
397+
static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary<string, PyObject> kwargDict, MethodBase[] methods, bool matchGenerics)
398+
{
399+
var pynargs = (int)Runtime.PyTuple_Size(args);
400+
var isGeneric = false;
401+
402+
var argMatchedMethods = new List<MatchedMethod>(methods.Length);
399403
var mismatchedMethods = new List<MismatchedMethod>();
400404

401405
// TODO: Clean up
402-
foreach (MethodBase mi in _methods)
406+
foreach (MethodBase mi in methods)
403407
{
404408
if (mi.IsGenericMethod)
405409
{
@@ -535,17 +539,17 @@ public MismatchedMethod(Exception exception, MethodBase mb)
535539

536540
return new Binding(mi, target, margs, outs);
537541
}
538-
else if (isGeneric && info == null && methodinfo != null)
542+
else if (matchGenerics && isGeneric)
539543
{
540544
// We weren't able to find a matching method but at least one
541545
// is a generic method and info is null. That happens when a generic
542546
// method was not called using the [] syntax. Let's introspect the
543547
// type of the arguments and use it to construct the correct method.
544548
Type[]? types = Runtime.PythonArgsToTypeArray(args, true);
545-
MethodInfo? mi = MatchParameters(methodinfo, types);
546-
if (mi != null)
549+
MethodInfo[] overloads = MatchParameters(methods, types);
550+
if (overloads.Length != 0)
547551
{
548-
return Bind(inst, args, kw, mi, null);
552+
return Bind(inst, args, kwargDict, overloads, matchGenerics: false);
549553
}
550554
}
551555
if (mismatchedMethods.Count > 0)

src/runtime/methodbinding.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,18 @@ public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference
4343
return Exceptions.RaiseTypeError("type(s) expected");
4444
}
4545

46-
MethodBase? mi = self.m.IsInstanceConstructor
47-
? self.m.type.Value.GetConstructor(types)
46+
MethodBase[] overloads = self.m.IsInstanceConstructor
47+
? self.m.type.Value.GetConstructor(types) is { } ctor
48+
? new[] { ctor }
49+
: Array.Empty<MethodBase>()
4850
: MethodBinder.MatchParameters(self.m.info, types);
49-
if (mi == null)
51+
if (overloads.Length == 0)
5052
{
5153
return Exceptions.RaiseTypeError("No match found for given type params");
5254
}
5355

54-
var mb = new MethodBinding(self.m, self.target, self.targetType) { info = mi };
56+
MethodObject overloaded = self.m.WithOverloads(overloads);
57+
var mb = new MethodBinding(overloaded, self.target, self.targetType);
5558
return mb.Alloc();
5659
}
5760

src/runtime/methodobject.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal class MethodObject : ExtensionType
2727
internal PyString? doc;
2828
internal MaybeType type;
2929

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

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

50+
public MethodObject WithOverloads(MethodBase[] overloads)
51+
=> new(type, name, overloads, allow_threads: binder.allow_threads);
52+
5053
internal MethodBase[] info
5154
{
5255
get

src/testing/methodtest.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,9 @@ public static int Overloaded(int i, string s)
646646
return i;
647647
}
648648

649+
public virtual void OverloadedConstrainedGeneric<T>(T generic) where T : MethodTest { }
650+
public virtual void OverloadedConstrainedGeneric<T>(T generic, string str) where T: MethodTest { }
651+
649652
public static string CaseSensitive()
650653
{
651654
return "CaseSensitive";

tests/test_generic.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,18 @@ def test_missing_generic_type():
762762
with pytest.raises(TypeError):
763763
IList[bool]
764764

765+
# https://github.com/pythonnet/pythonnet/issues/1522
766+
def test_overload_generic_parameter():
767+
from Python.Test import MethodTest, MethodTestSub
768+
769+
inst = MethodTest()
770+
generic = MethodTestSub()
771+
inst.OverloadedConstrainedGeneric(generic)
772+
inst.OverloadedConstrainedGeneric[MethodTestSub](generic)
773+
774+
inst.OverloadedConstrainedGeneric[MethodTestSub](generic, '42')
775+
inst.OverloadedConstrainedGeneric[MethodTestSub](generic, System.String('42'))
776+
765777
def test_invalid_generic_type_parameter():
766778
from Python.Test import GenericTypeWithConstraint
767779
with pytest.raises(TypeError):

0 commit comments

Comments
 (0)