Fix kwarg func resolution (#1136) · pythonnet/pythonnet@c81c3c3
@@ -279,6 +279,23 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info)
279279return Bind(inst, args, kw, info, null);
280280}
281281282+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+282299internal 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}
313330331+var argMatchedMethods = new List<MatchedMethod>(_methods.Length);
332+314333// TODO: Clean up
315334foreach (MethodBase mi in _methods)
316335{
@@ -321,8 +340,10 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
321340ParameterInfo[] pi = mi.GetParameters();
322341ArrayList defaultArgList;
323342bool paramsArray;
343+int kwargsMatched;
344+int defaultsNeeded;
324345325-if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList))
346+if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList, out kwargsMatched, out defaultsNeeded))
326347{
327348continue;
328349}
@@ -336,6 +357,47 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
336357continue;
337358}
338359360+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+339401object target = null;
340402if (!mi.IsStatic && inst != IntPtr.Zero)
341403{
@@ -575,11 +637,16 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool
575637static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters,
576638Dictionary<string, IntPtr> kwargDict,
577639out bool paramsArray,
578-out ArrayList defaultArgList)
640+out ArrayList defaultArgList,
641+out int kwargsMatched,
642+out int defaultsNeeded)
579643{
580644defaultArgList = null;
581645var match = false;
582646paramsArray = parameters.Length > 0 ? Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)) : false;
647+var kwargCount = kwargDict.Count;
648+kwargsMatched = 0;
649+defaultsNeeded = 0;
583650584651if (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
601668defaultArgList.Add(null);
669+kwargsMatched++;
602670}
603671else 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
609677defaultArgList.Add(parameters[v].GetDefaultValue());
678+defaultsNeeded++;
610679}
611680else if(!paramsArray)
612681{