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

Skip to content

Fix kwarg func resolution #1136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 18, 2020

Conversation

jmlidbetter
Copy link
Contributor

What does this implement/fix? Explain your changes.

A first run at fixing issue #1097. Currently, the method for selecting which method to use is quite simple and just takes the first function it finds to match. The problem is that the current picking algorithm has no knowledge of kwargs and how many have been supplied or used, i.e. MatchesArgumentCount will return true even if we've supplied some kwargs but the current overload does not require them. Ultimately, this means the first matched method might not be the best.

This small patch aims to rectify this issue by collecting together all the methods for which MatchesArgumentCount returns true and then from these selecting the best match based on the following criteria

  • Maximizing the number of supplied kwargs that have been used
  • Minimizing the number of default arguments needed

It also has some logic to abort in the case where

  • The above criteria are met by more than one overload
  • We need at least one default argument to make the call

This stops users in python making ambiguous function calls - see the tests for an example.

Does this close any currently open issues?

#1097

Any other comments?

Feedback please!

Checklist

Check all those that are applicable and complete.

  • Make sure to include one or more tests for your change
  • If an enhancement PR, please create docs and at best an example
  • Add yourself to AUTHORS
  • Updated the CHANGELOG

@dnfclas
Copy link

dnfclas commented May 7, 2020

CLA assistant check
All CLA requirements met.

@DrNickClarke
Copy link

Just to note that we have been using this fix for some time and it does appear to work. It is a welcome change from needing to specify long list of args you don't care about.

Copy link
Member

@lostmsu lostmsu left a comment

Choose a reason for hiding this comment

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

Just FYI, there's a much better way to do method resolution (which I hope we will adopt at some point) but it requires lots of rework: C# Binder. I did a simple experiment here: losttech@605c576

We can still take this change in meanwhile.

// 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
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.

@jmlidbetter
Copy link
Contributor Author

Just FYI, there's a much better way to do method resolution (which I hope we will adopt at some point) but it requires lots of rework: C# Binder. I did a simple experiment here: losttech@605c576

We can still take this change in meanwhile.

Yes this wasn't supposed to change the world, simply just to get something that worked 😄

@jmlidbetter jmlidbetter force-pushed the fix_kwarg_func_resolution branch from ce07727 to 2b98872 Compare May 12, 2020 08:17
if (testMatch.DefaultsNeeded == fewestDefaultsRequired)
{
bestCount++;
if (bestMatchIndex == -1)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not having this check will cause tests to fail because a different overload is picked- e.g. MethodTest.TestOverloadedObjectTwo(5, 5) will get the (Object, Object) overload rather than the (Int, Int) overload

var matchedMethod = new MatchedMethod(kwargsMatched, defaultsNeeded, margs, outs, mi);
argMatchedMethods.Add(matchedMethod);
}
if (argMatchedMethods.Count() > 0)
Copy link
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.

Comment on lines +380 to +386
if (bestCount > 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;
}
Copy link
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.

@jmlidbetter jmlidbetter force-pushed the fix_kwarg_func_resolution branch from 643f7bc to 1dd8725 Compare May 13, 2020 06:33
Copy link
Member

@lostmsu lostmsu left a comment

Choose a reason for hiding this comment

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

Approved, but should wait for post 2.x branch due to the breaking change in behavior around overload resolution ordering.

@lostmsu lostmsu added this to the 3.0.0 milestone May 13, 2020
@lostmsu
Copy link
Member

lostmsu commented Jun 19, 2020

@jmlidbetter we have released 2.5 and master is now working on 3.x. Can you update this PR, so we could merge it?

@jmlidbetter
Copy link
Contributor Author

@jmlidbetter we have released 2.5 and master is now working on 3.x. Can you update this PR, so we could merge it?

Sure. I assume just rebasing on top of master is sufficient or is there something else you'd want?

@lostmsu
Copy link
Member

lostmsu commented Jun 20, 2020

@jmlidbetter it should be. I'll re-review just in case.

@jmlidbetter jmlidbetter force-pushed the fix_kwarg_func_resolution branch from 7caa7ed to d453159 Compare June 22, 2020 15:21
@jmlidbetter
Copy link
Contributor Author

@lostmsu Ok I have rebased, feel free to check and merge (as appropriate)

@lostmsu
Copy link
Member

lostmsu commented Jun 22, 2020

@jmlidbetter related tests seem to be failing.

@jmlidbetter
Copy link
Contributor Author

Hmm that is strange - when I ran the tests on my machine they all passed.... I will have a look tomorrow.

@jmlidbetter
Copy link
Contributor Author

jmlidbetter commented Jun 22, 2020

Seems like the Linux tests have passed ok but are broken on Windows - perhaps there is some slight difference in behaviour on windows. I have limited ability test on windows but I'll see what I can do.

@jmlidbetter
Copy link
Contributor Author

@lostmsu I have not been able to reproduce the test failures. Building on Windows I get the following output:

image

image

Are you able to repro this issue on windows?

@lostmsu
Copy link
Member

lostmsu commented Jun 23, 2020

I am not sure what causes the discrepancy, but judging by the code, it seems like the CI build messes up with named parameters. I will try to see if that is correct.

However, this also uncovers a bug: if Python passed a named parameter not defined in C#, the overload resolution behavior is incorrect. I suggest to handle that and add a test too.

void NoOverloads(int a = 0) {}
NoOverloads(c=1) # should fail, because c does not match a

@jmlidbetter
Copy link
Contributor Author

I believe I had originally implemented that behaviour (not submitted onto this branch) but it broke some other tests. I may be misremembering though, I will check tomorrow.

@jmlidbetter
Copy link
Contributor Author

jmlidbetter commented Jun 25, 2020

@lostmsu here is the breaking test if you fail when a kwarg is supplied that doesn't appear in the method signature:

____________________________________________ test_namespace_and_init _____________________________________________

    def test_namespace_and_init():
        calls = []
        class TestX(System.Object):
            __namespace__ = "test_clr_subclass_with_init_args"
            def __init__(self, *args, **kwargs):
                calls.append((args, kwargs))
>       t = TestX(1,2,3,foo="bar")
E       TypeError: No constructor matches given arguments: (<class 'int'>, <class 'int'>, <class 'int'>)

src/tests/test_subclass.py:229: TypeError

I haven't done any further investigation into this. I guess there are two ways to proceed:
(1) Log this as an issue and proceed as is
(2) Dig in and fix the broken test

Its up to you, but personally I'm in favour of (1) because (a) having working kwargs (except in the case where you supply extra ones ;) ) is quite a big improvement and (b) python is fairly loose anyway with ducktyping etc etc, in a way all C# methods would have an implicit **kwargs in their signature - it might be possible to throw a warning when extraneous kwargs are given, I don't know.

@jmlidbetter
Copy link
Contributor Author

jmlidbetter commented Oct 21, 2020

Are we good to merge this change once the tests have passed?

@filmor
Copy link
Member

filmor commented Nov 18, 2020

This looks good to me, @lostmsu are you also fine with merging this?

@codecov-io
Copy link

codecov-io commented Nov 18, 2020

Codecov Report

Merging #1136 (8d0fc2c) into master (7870a9f) will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1136   +/-   ##
=======================================
  Coverage   87.97%   87.97%           
=======================================
  Files           1        1           
  Lines         291      291           
=======================================
  Hits          256      256           
  Misses         35       35           
Flag Coverage Δ
setup_linux 65.29% <ø> (ø)
setup_windows 74.22% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7870a9f...8d0fc2c. Read the comment docs.

@lostmsu lostmsu merged commit c81c3c3 into pythonnet:master Nov 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants