Skip to content

Commit 5bad923

Browse files
committed
Fix function resolution when calling overloads with keyword arguments
1 parent 2e0874a commit 5bad923

File tree

3 files changed

+116
-3
lines changed

3 files changed

+116
-3
lines changed

src/runtime/methodbinder.cs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
310310
_methods = GetMethods();
311311
}
312312

313+
// List of tuples containing methods that have matched based on arguments.
314+
// Format of tuple is: Number of kwargs matched, defaults needed, margs, outs, method base for matched method
315+
List<Tuple<int, int, object[], int, MethodBase>> argMatchedMethods =
316+
new List<Tuple<int, int, object[], int, MethodBase>>(_methods.Length);
317+
313318
// TODO: Clean up
314319
foreach (MethodBase mi in _methods)
315320
{
@@ -320,8 +325,10 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
320325
ParameterInfo[] pi = mi.GetParameters();
321326
ArrayList defaultArgList;
322327
bool paramsArray;
328+
int kwargsMatched;
329+
int defaultsNeeded;
323330

324-
if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList))
331+
if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList, out kwargsMatched, out defaultsNeeded))
325332
{
326333
continue;
327334
}
@@ -335,6 +342,57 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
335342
continue;
336343
}
337344

345+
var matchedMethodTuple = Tuple.Create(kwargsMatched, defaultsNeeded, margs, outs, mi);
346+
argMatchedMethods.Add(matchedMethodTuple);
347+
}
348+
if (argMatchedMethods.Count() > 0)
349+
{
350+
// Order matched methods by number of kwargs matched and get the max possible number
351+
// of kwargs matched
352+
var bestKwargMatchCount = argMatchedMethods.OrderBy((x) => x.Item1).Reverse().ToArray()[0].Item1;
353+
354+
List<Tuple<int, int, object[], int, MethodBase>> bestKwargMatches =
355+
new List<Tuple<int, int, object[], int, MethodBase>>(argMatchedMethods.Count());
356+
foreach (Tuple<int, int, object[], int, MethodBase> argMatchedTuple in argMatchedMethods)
357+
{
358+
if (argMatchedTuple.Item1 == bestKwargMatchCount)
359+
{
360+
bestKwargMatches.Add(argMatchedTuple);
361+
}
362+
}
363+
364+
// Order by the number of defaults required and find the smallest
365+
var fewestDefaultsRequired = bestKwargMatches.OrderBy((x) => x.Item2).ToArray()[0].Item2;
366+
367+
List<Tuple<int, int, object[], int, MethodBase>> bestDefaultsMatches =
368+
new List<Tuple<int, int, object[], int, MethodBase>>(bestKwargMatches.Count());
369+
foreach (Tuple<int, int, object[], int, MethodBase> testTuple in bestKwargMatches)
370+
{
371+
if (testTuple.Item2 == fewestDefaultsRequired)
372+
{
373+
bestDefaultsMatches.Add(testTuple);
374+
}
375+
}
376+
377+
if (bestDefaultsMatches.Count() > 1 && fewestDefaultsRequired > 0)
378+
{
379+
// Best effort for determining method to match on gives multiple possible
380+
// matches and we need at least one default argument - bail from this point
381+
return null;
382+
}
383+
384+
// If we're here either:
385+
// (a) There is only one best match
386+
// (b) There are multiple best matches but none of them require
387+
// default arguments
388+
// in the case of (a) we're done by default. For (b) regardless of which
389+
// method we choose, all arguments are specified _and_ can be converted
390+
// from python to C# so picking any will suffice
391+
Tuple<int, int, object[], int, MethodBase> bestMatch = bestDefaultsMatches.ToArray()[0];
392+
var margs = bestMatch.Item3;
393+
var outs = bestMatch.Item4;
394+
var mi = bestMatch.Item5;
395+
338396
object target = null;
339397
if (!mi.IsStatic && inst != IntPtr.Zero)
340398
{
@@ -539,11 +597,16 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool
539597
static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters,
540598
Dictionary<string, IntPtr> kwargDict,
541599
out bool paramsArray,
542-
out ArrayList defaultArgList)
600+
out ArrayList defaultArgList,
601+
out int kwargsMatched,
602+
out int defaultsNeeded)
543603
{
544604
defaultArgList = null;
545605
var match = false;
546606
paramsArray = false;
607+
var kwargCount = kwargDict.Count();
608+
kwargsMatched = 0;
609+
defaultsNeeded = 0;
547610

548611
if (positionalArgumentCount == parameters.Length)
549612
{
@@ -563,6 +626,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
563626
// no need to check for a default parameter, but put a null
564627
// placeholder in defaultArgList
565628
defaultArgList.Add(null);
629+
kwargsMatched++;
566630
}
567631
else if (parameters[v].IsOptional)
568632
{
@@ -571,6 +635,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
571635
// The GetDefaultValue() extension method will return the value
572636
// to be passed in as the parameter value
573637
defaultArgList.Add(parameters[v].GetDefaultValue());
638+
defaultsNeeded++;
574639
}
575640
else
576641
{

src/testing/methodtest.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,25 @@ public static string OptionalAndDefaultParams2([Optional]int a, [Optional]int b,
683683
return string.Format("{0}{1}{2}{3}", a, b, c, d);
684684
}
685685

686-
686+
public static string DefaultParamsWithOverloading(int a = 2, int b = 1)
687+
{
688+
return $"{a}{b}";
689+
}
690+
691+
public static string DefaultParamsWithOverloading(string a = "a", string b = "b")
692+
{
693+
return $"{a}{b}X";
694+
}
695+
696+
public static string DefaultParamsWithOverloading(int a = 0, int b = 1, int c = 2)
697+
{
698+
return $"{a}{b}{c}XX";
699+
}
700+
701+
public static string DefaultParamsWithOverloading(int a = 5, int b = 6, int c = 7, int d = 8)
702+
{
703+
return $"{a}{b}{c}{d}XXX";
704+
}
687705
}
688706

689707

src/tests/test_method.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,3 +1128,33 @@ def test_optional_and_default_params():
11281128

11291129
res = MethodTest.OptionalAndDefaultParams2(b=2, c=3)
11301130
assert res == "0232"
1131+
1132+
def test_default_params_overloads():
1133+
1134+
res = MethodTest.DefaultParamsWithOverloading(1, 2)
1135+
assert res == "12"
1136+
1137+
res = MethodTest.DefaultParamsWithOverloading(b=5)
1138+
assert res == "25"
1139+
1140+
res = MethodTest.DefaultParamsWithOverloading("d")
1141+
assert res == "dbX"
1142+
1143+
res = MethodTest.DefaultParamsWithOverloading(b="c")
1144+
assert res == "acX"
1145+
1146+
res = MethodTest.DefaultParamsWithOverloading(c=3)
1147+
assert res == "013XX"
1148+
1149+
res = MethodTest.DefaultParamsWithOverloading(5, c=2)
1150+
assert res == "512XX"
1151+
1152+
res = MethodTest.DefaultParamsWithOverloading(c=0, d=1)
1153+
assert res == "5601XXX"
1154+
1155+
res = MethodTest.DefaultParamsWithOverloading(1, d=1)
1156+
assert res == "1671XXX"
1157+
1158+
def test_default_params_overloads_ambiguous_call():
1159+
with pytest.raises(TypeError):
1160+
MethodTest.DefaultParamsWithOverloading()

0 commit comments

Comments
 (0)