Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Fix function resolution when calling overloads with keyword arguments
  • Loading branch information
jmlidbetter committed Jun 22, 2020
commit 9fb753a789e52188061dc5a3194ba92d8e45ca18
69 changes: 67 additions & 2 deletions src/runtime/methodbinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
_methods = GetMethods();
}

// List of tuples containing methods that have matched based on arguments.
// Format of tuple is: Number of kwargs matched, defaults needed, margs, outs, method base for matched method
List<Tuple<int, int, object[], int, MethodBase>> argMatchedMethods =
new List<Tuple<int, int, object[], int, MethodBase>>(_methods.Length);

// TODO: Clean up
foreach (MethodBase mi in _methods)
{
Expand All @@ -320,8 +325,10 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
ParameterInfo[] pi = mi.GetParameters();
ArrayList defaultArgList;
bool paramsArray;
int kwargsMatched;
int defaultsNeeded;

if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList))
if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList, out kwargsMatched, out defaultsNeeded))
{
continue;
}
Expand All @@ -335,6 +342,57 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
continue;
}

var matchedMethodTuple = Tuple.Create(kwargsMatched, defaultsNeeded, margs, outs, mi);
argMatchedMethods.Add(matchedMethodTuple);
}
if (argMatchedMethods.Count() > 0)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Count property. Count() method might require a run through the entire collection.

{
// Order matched methods by number of kwargs matched and get the max possible number
// of kwargs matched
var bestKwargMatchCount = argMatchedMethods.OrderBy((x) => x.Item1).Reverse().ToArray()[0].Item1;

List<Tuple<int, int, object[], int, MethodBase>> bestKwargMatches =
new List<Tuple<int, int, object[], int, MethodBase>>(argMatchedMethods.Count());
foreach (Tuple<int, int, object[], int, MethodBase> argMatchedTuple in argMatchedMethods)
{
if (argMatchedTuple.Item1 == bestKwargMatchCount)
{
bestKwargMatches.Add(argMatchedTuple);
}
}

// Order by the number of defaults required and find the smallest
var fewestDefaultsRequired = bestKwargMatches.OrderBy((x) => x.Item2).ToArray()[0].Item2;

List<Tuple<int, int, object[], int, MethodBase>> bestDefaultsMatches =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make named structs from the tuples? It's hard to read with Item1-like references.

new List<Tuple<int, int, object[], int, MethodBase>>(bestKwargMatches.Count());
foreach (Tuple<int, int, object[], int, MethodBase> testTuple in bestKwargMatches)
{
if (testTuple.Item2 == fewestDefaultsRequired)
{
bestDefaultsMatches.Add(testTuple);
}
}

if (bestDefaultsMatches.Count() > 1 && fewestDefaultsRequired > 0)
{
// Best effort for determining method to match on gives multiple possible
// matches and we need at least one default argument - bail from this point
return null;
}
Comment on lines +382 to +387
Copy link
Copy Markdown
Member

@lostmsu lostmsu May 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes the change breaking for Python.NET 2.x branch. Currently the first match is returned irrespective if there are other matches. No need to fix it, just means might have to be merged later.


// If we're here either:
// (a) There is only one best match
// (b) There are multiple best matches but none of them require
// default arguments
// in the case of (a) we're done by default. For (b) regardless of which
// method we choose, all arguments are specified _and_ can be converted
// from python to C# so picking any will suffice
Tuple<int, int, object[], int, MethodBase> bestMatch = bestDefaultsMatches.ToArray()[0];
var margs = bestMatch.Item3;
var outs = bestMatch.Item4;
var mi = bestMatch.Item5;

object target = null;
if (!mi.IsStatic && inst != IntPtr.Zero)
{
Expand Down Expand Up @@ -574,11 +632,16 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool
static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters,
Dictionary<string, IntPtr> kwargDict,
out bool paramsArray,
out ArrayList defaultArgList)
out ArrayList defaultArgList,
out int kwargsMatched,
out int defaultsNeeded)
{
defaultArgList = null;
var match = false;
paramsArray = parameters.Length > 0 ? Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)) : false;
var kwargCount = kwargDict.Count();
Comment thread
lostmsu marked this conversation as resolved.
Outdated
kwargsMatched = 0;
defaultsNeeded = 0;

if (positionalArgumentCount == parameters.Length)
{
Expand All @@ -598,6 +661,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
// no need to check for a default parameter, but put a null
// placeholder in defaultArgList
defaultArgList.Add(null);
kwargsMatched++;
}
else if (parameters[v].IsOptional)
{
Expand All @@ -606,6 +670,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa
// The GetDefaultValue() extension method will return the value
// to be passed in as the parameter value
defaultArgList.Add(parameters[v].GetDefaultValue());
defaultsNeeded++;
}
else if(!paramsArray)
{
Expand Down
20 changes: 19 additions & 1 deletion src/testing/methodtest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,25 @@ public static string OptionalAndDefaultParams2([Optional]int a, [Optional]int b,
return string.Format("{0}{1}{2}{3}", a, b, c, d);
}


public static string DefaultParamsWithOverloading(int a = 2, int b = 1)
{
return $"{a}{b}";
}

public static string DefaultParamsWithOverloading(string a = "a", string b = "b")
{
return $"{a}{b}X";
}

public static string DefaultParamsWithOverloading(int a = 0, int b = 1, int c = 2)
{
return $"{a}{b}{c}XX";
}

public static string DefaultParamsWithOverloading(int a = 5, int b = 6, int c = 7, int d = 8)
{
return $"{a}{b}{c}{d}XXX";
}
}


Expand Down
30 changes: 30 additions & 0 deletions src/tests/test_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -1146,3 +1146,33 @@ def test_optional_and_default_params():

res = MethodTest.OptionalAndDefaultParams2(b=2, c=3)
assert res == "0232"

def test_default_params_overloads():

res = MethodTest.DefaultParamsWithOverloading(1, 2)
assert res == "12"

res = MethodTest.DefaultParamsWithOverloading(b=5)
assert res == "25"

res = MethodTest.DefaultParamsWithOverloading("d")
assert res == "dbX"

res = MethodTest.DefaultParamsWithOverloading(b="c")
assert res == "acX"

res = MethodTest.DefaultParamsWithOverloading(c=3)
assert res == "013XX"

res = MethodTest.DefaultParamsWithOverloading(5, c=2)
assert res == "512XX"

res = MethodTest.DefaultParamsWithOverloading(c=0, d=1)
assert res == "5601XXX"

res = MethodTest.DefaultParamsWithOverloading(1, d=1)
assert res == "1671XXX"

def test_default_params_overloads_ambiguous_call():
with pytest.raises(TypeError):
MethodTest.DefaultParamsWithOverloading()