Repository navigation
fix: bind keyword arguments and defaults for module functions - #58
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
py2v's ordinary call emission appended keyword values in the order they were written, after the positional arguments and any defaults it filled in, so keyword arguments in a call to one of a file's own top-level functions could end up in the wrong parameters. It also didn't fill in a default left out of a call above the function's
def.came out as
area(3, 2, 1, 10), which V refuses with "expected 3 arguments, but got 4", andarea(2, 1, 5, 4). Now the arguments are matched to the parameters by name:area(3, 2, 10)andarea(4, 5, 1).This only applies when a single file is translated, to calls of functions defined at the top level of that file, and only under the conditions below. When a package is translated (
--package) nothing changes, because another file can call the function and this pass doesn't see that call. Any other call is emitted as before. A call is left alone when:*args,**kwargs, positional-only parameters, aselforclsparameter, is a generator, or changes one of its parameters in place (appends to a list parameter, for example);def, an assignment, a parameter, an import), or the module has a star import;*xsor**d), a keyword the function doesn't have, or runs at module level above the end of the function'sdef(the body ofif __name__ == "__main__":counts as module level, and so does adef main()with no parameters and no return annotation, which is how py2v represents that block);..., or a negative number);If any call to one of the file's top-level functions that uses a keyword or omits a default can't be matched this way, no call in that file is changed. The same goes when the file calls anything else by name that isn't one of the builtins py2v translates itself (a class, a nested function, a lambda, an imported name, a function held in a variable, and also a builtin's name that the file binds itself, such as its own class
Pathor its owndef sum), or passes keyword arguments to anything else (a method, a builtin such asprint(x, end="")orpow(exp=3.0, base=1.0)). So a file is only changed when every call by name in it uses the name of one of its top-level functions or of one of those builtins, and no other call has keywords. That is stricter than it looks like it needs to be, but on master two wrong calls in one file can cancel each other out, and fixing only one of them makes a program that printed the right thing print the wrong thing.tests/cases/call_binding_alias.pyis such a program:difference(b=2, a=5) + runner(b=5, a=2)withrunner = differenceprints 0 on master and still does.What this doesn't rule out is the keyword error cancelling something else py2v gets wrong in the same file. Two examples. py2v drops an optional argument of some builtins and string methods (the start of
sumandenumerate, the base ofint, the third argument ofpow, the maxsplit ofsplit), soprint(difference(b=5, a=2) + sum([0], 6))prints 3 on master, as Python does, only because both calls are wrong, and -3 with this change. Andprint(difference(b=2, a=5) + 2 * (4 % -3))prints -1 on master only because%with a negative divisor is also wrong there; with this change it prints 5. This PR doesn't try to exclude those, since each one needs its own fix, and the same would be true of a fix to any one wrong translation.Like #56, this uncovers other things py2v already gets wrong: 6 of the programs below failed in the V build because of the call and now build and print something other than what Python prints (an int passed to a float parameter prints
8.0,/on two ints divides as ints, and so on). For each of them the same call written positionally prints the same wrong output on master.Test plan
tests/cases/call_binding.pywith its expected V: keyword-only parameters, a skipped default in the middle, a negative default, a string default with a quote in it, a keyword in a recursive call, a list argument.tests/cases/call_binding_alias.pypins the alias case to what master emits.v tests/run_tests.vsh: 169 passed, 0 failed (167 before, plus the two new cases). No other expected file changes.v test py2v_test.vpasses.