Skip to content

Commit c81c3c3

Browse files
jmlidbetterfilmor
andauthored
Fix kwarg func resolution (#1136)
Fix function resolution when calling overloads with keyword arguments Co-authored-by: Benedikt Reinartz <filmor@gmail.com>
1 parent 7870a9f commit c81c3c3

File tree

4 files changed

+120
-3
lines changed

4 files changed

+120
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ details about the cause of the failure
2525

2626
- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash
2727
- Fix incorrect dereference in params array handling
28+
- Fixes issue with function resolution when calling overloaded function with keyword arguments from python ([#1097][i1097])
2829
- Fix `object[]` parameters taking precedence when should not in overload resolution
2930
- Fixed a bug where all .NET class instances were considered Iterable
3031
- Fix incorrect choice of method to invoke when using keyword arguments.

src/runtime/methodbinder.cs

+71-2
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,23 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info)
279279
return Bind(inst, args, kw, info, null);
280280
}
281281

282+
private readonly struct MatchedMethod {
283+
public MatchedMethod(int kwargsMatched, int defaultsNeeded, object[] margs, int outs, MethodBase mb)
284+
{
285+
KwargsMatched = kwargsMatched;
286+
DefaultsNeeded = defaultsNeeded;
287+
ManagedArgs = margs;
288+
Outs = outs;
289+
Method = mb;
290+
}
291+
292+
public int KwargsMatched { get; }
293+
public int DefaultsNeeded { get; }
294+
public object[] ManagedArgs { get; }
295+
public int Outs { get; }
296+
public MethodBase Method { get; }
297+
}
298+
282299
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo)
283300
{
284301
// loop to find match, return invoker w/ or /wo error
@@ -311,6 +328,8 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
311328
_methods = GetMethods();
312329
}
313330

331+
var argMatchedMethods = new List<MatchedMethod>(_methods.Length);
332+
314333
// TODO: Clean up
315334
foreach (MethodBase mi in _methods)
316335
{
@@ -321,8 +340,10 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
321340
ParameterInfo[] pi = mi.GetParameters();
322341
ArrayList defaultArgList;
323342
bool paramsArray;
343+
int kwargsMatched;
344+
int defaultsNeeded;
324345

325-
if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList))
346+
if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList, out kwargsMatched, out defaultsNeeded))
326347
{
327348
continue;
328349
}
@@ -336,6 +357,47 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
336357
continue;
337358
}
338359

360+
var matchedMethod = new MatchedMethod(kwargsMatched, defaultsNeeded, margs, outs, mi);
361+
argMatchedMethods.Add(matchedMethod);
362+
}
363+
if (argMatchedMethods.Count > 0)
364+
{
365+
var bestKwargMatchCount = argMatchedMethods.Max(x => x.KwargsMatched);
366+
var fewestDefaultsRequired = argMatchedMethods.Where(x => x.KwargsMatched == bestKwargMatchCount).Min(x => x.DefaultsNeeded);
367+
368+
int bestCount = 0;
369+
int bestMatchIndex = -1;
370+
371+
for (int index = 0; index < argMatchedMethods.Count; index++)
372+
{
373+
var testMatch = argMatchedMethods[index];
374+
if (testMatch.DefaultsNeeded == fewestDefaultsRequired && testMatch.KwargsMatched == bestKwargMatchCount)
375+
{
376+
bestCount++;
377+
if (bestMatchIndex == -1)
378+
bestMatchIndex = index;
379+
}
380+
}
381+
382+
if (bestCount > 1 && fewestDefaultsRequired > 0)
383+
{
384+
// Best effort for determining method to match on gives multiple possible
385+
// matches and we need at least one default argument - bail from this point
386+
return null;
387+
}
388+
389+
// If we're here either:
390+
// (a) There is only one best match
391+
// (b) There are multiple best matches but none of them require
392+
// default arguments
393+
// in the case of (a) we're done by default. For (b) regardless of which
394+
// method we choose, all arguments are specified _and_ can be converted
395+
// from python to C# so picking any will suffice
396+
MatchedMethod bestMatch = argMatchedMethods[bestMatchIndex];
397+
var margs = bestMatch.ManagedArgs;
398+
var outs = bestMatch.Outs;
399+
var mi = bestMatch.Method;
400+
339401
object target = null;
340402
if (!mi.IsStatic && inst != IntPtr.Zero)
341403
{
@@ -575,11 +637,16 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool
575637
static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters,
576638
Dictionary<string, IntPtr> kwargDict,
577639
out bool paramsArray,
578-
out ArrayList defaultArgList)
640+
out ArrayList defaultArgList,
641+
out int kwargsMatched,
642+
out int defaultsNeeded)
579643
{
580644
defaultArgList = null;
581645
var match = false;
582646
paramsArray = parameters.Length > 0 ? Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)) : false;
647+
var kwargCount = kwargDict.Count;
648+
kwargsMatched = 0;
649+
defaultsNeeded = 0;
583650

584651
if (positionalArgumentCount == parameters.Length && kwargDict.Count == 0)
585652
{
@@ -599,6 +666,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
599666
// no need to check for a default parameter, but put a null
600667
// placeholder in defaultArgList
601668
defaultArgList.Add(null);
669+
kwargsMatched++;
602670
}
603671
else if (parameters[v].IsOptional)
604672
{
@@ -607,6 +675,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
607675
// The GetDefaultValue() extension method will return the value
608676
// to be passed in as the parameter value
609677
defaultArgList.Add(parameters[v].GetDefaultValue());
678+
defaultsNeeded++;
610679
}
611680
else if(!paramsArray)
612681
{

src/testing/methodtest.cs

+19-1
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,25 @@ public static string OptionalAndDefaultParams2([Optional]int a, [Optional]int b,
684684
return string.Format("{0}{1}{2}{3}", a, b, c, d);
685685
}
686686

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

690708

src/tests/test_method.py

+29
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,35 @@ def test_optional_and_default_params():
11531153
res = MethodTest.OptionalAndDefaultParams2(b=2, c=3)
11541154
assert res == "0232"
11551155

1156+
def test_default_params_overloads():
1157+
res = MethodTest.DefaultParamsWithOverloading(1, 2)
1158+
assert res == "12"
1159+
1160+
res = MethodTest.DefaultParamsWithOverloading(b=5)
1161+
assert res == "25"
1162+
1163+
res = MethodTest.DefaultParamsWithOverloading("d")
1164+
assert res == "dbX"
1165+
1166+
res = MethodTest.DefaultParamsWithOverloading(b="c")
1167+
assert res == "acX"
1168+
1169+
res = MethodTest.DefaultParamsWithOverloading(c=3)
1170+
assert res == "013XX"
1171+
1172+
res = MethodTest.DefaultParamsWithOverloading(5, c=2)
1173+
assert res == "512XX"
1174+
1175+
res = MethodTest.DefaultParamsWithOverloading(c=0, d=1)
1176+
assert res == "5601XXX"
1177+
1178+
res = MethodTest.DefaultParamsWithOverloading(1, d=1)
1179+
assert res == "1671XXX"
1180+
1181+
def test_default_params_overloads_ambiguous_call():
1182+
with pytest.raises(TypeError):
1183+
MethodTest.DefaultParamsWithOverloading()
1184+
11561185
def test_keyword_arg_method_resolution():
11571186
from Python.Test import MethodArityTest
11581187

0 commit comments

Comments
 (0)