Skip to content
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

Fix Arguments.arguments so it actually returns all arguments #2240

Merged
merged 5 commits into from
Jul 20, 2023

Conversation

crazybolillo
Copy link
Contributor

Closes #2213.

Arguments.arguments() has been modified so that it returns all arguments as it should (according to its own doc). A test case was also added to verify this.

Type of Changes

Type
🐛 Bug fix

@crazybolillo
Copy link
Contributor Author

Started out writing a test to verify the behavior, apparently I broke everything else with the changes, will look into fixing that.

@crazybolillo crazybolillo force-pushed the 2213-crazybolillo branch 2 times, most recently from c4a3452 to 08ace25 Compare July 7, 2023 18:43
@crazybolillo
Copy link
Contributor Author

crazybolillo commented Jul 7, 2023

Not so destructive now, with only 13 tests failing. At least some of them seem to fail because of this:

if funcnode.args.vararg == name:
# It wants all the args that were passed into
# the call site.
if self.has_invalid_arguments():
raise InferenceError(
"Inference failed to find values for all positional "
"arguments to {func!r}: {unpacked_args!r} doesn't "
"correspond to {positional_arguments!r}.",
positional_arguments=self.positional_arguments,
unpacked_args=self._unpacked_args,
call_site=self,
func=funcnode,
arg=name,
context=context,
)
args = nodes.Tuple(
lineno=funcnode.args.lineno,
col_offset=funcnode.args.col_offset,
parent=funcnode.args,
)
args.postinit(vararg)
return iter((args,))

Previously the code would get up to that point (if funcnode.args.vararg == name) but with the new behavior for arguments it is exiting here:

return self.positional_arguments[argindex].infer(context)

The specific test that goes through that code is:

def test_args(self) -> None:

@DanielNoord
Copy link
Collaborator

Seems like we need to change how we determine argindex. Do you need help with that?

@crazybolillo
Copy link
Contributor Author

Yes, seems like it. I stopped there for the day and haven't had the chance to look into it, but any insights or tips are appreciated. Probably I will be able to work on this tomorrow.

@crazybolillo
Copy link
Contributor Author

Fixed (I think?) the inference stuff related to arg index and also some other mechanics caused by the original fix, like considering vararg and kwarg for obtaining default values (I excluded them from the computation). Just two tests failing now, light at the end of the tunnel seems closer.

@crazybolillo crazybolillo force-pushed the 2213-crazybolillo branch 2 times, most recently from 6a4b8f7 to a9e0dce Compare July 12, 2023 00:20
@codecov
Copy link

codecov bot commented Jul 12, 2023

Codecov Report

Merging #2240 (b0d2153) into main (cf8763a) will decrease coverage by 0.01%.
The diff coverage is 100.00%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2240      +/-   ##
==========================================
- Coverage   92.78%   92.78%   -0.01%     
==========================================
  Files          94       94              
  Lines       10937    10947      +10     
==========================================
+ Hits        10148    10157       +9     
- Misses        789      790       +1     
Flag Coverage Δ
linux 92.59% <100.00%> (-0.01%) ⬇️
pypy 90.89% <100.00%> (-0.01%) ⬇️
windows 92.37% <100.00%> (-0.01%) ⬇️

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

Impacted Files Coverage Δ
astroid/nodes/scoped_nodes/scoped_nodes.py 92.48% <ø> (-0.06%) ⬇️
astroid/arguments.py 99.24% <100.00%> (+0.01%) ⬆️
astroid/nodes/node_classes.py 94.80% <100.00%> (-0.02%) ⬇️
astroid/protocols.py 90.21% <100.00%> (+0.02%) ⬆️
astroid/rebuilder.py 98.45% <100.00%> (+<0.01%) ⬆️

@crazybolillo crazybolillo changed the title Draft: Fix Arguments.arguments so it actually returns all arguments Fix Arguments.arguments so it actually returns all arguments Jul 12, 2023
@crazybolillo
Copy link
Contributor Author

All tests working now, so at least we have a functioning implementation. Can you review?

Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

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

Thanks!

I found a case that breaks and should be added, potentially in a new test method.

setup

from astroid import extract_node
node = extract_node("def fruit(eat=True, *, peel=False, trim=False, **kwargs): ...")

main

>>> node.args.default_value('eat')
<Const.bool l.1 at 0x1054a9730>

pr

>>> node.args.default_value('eat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/<user>/astroid/astroid/nodes/node_classes.py", line 934, in default_value
    raise NoDefault(func=self.parent, name=argname)
astroid.exceptions.NoDefault: <FunctionDef.fruit l.1 at 0x10f5c2510> has no default for 'eat'.

main

astroid/nodes/node_classes.py Outdated Show resolved Hide resolved
astroid/nodes/node_classes.py Outdated Show resolved Hide resolved
astroid/nodes/node_classes.py Outdated Show resolved Hide resolved
astroid/nodes/node_classes.py Outdated Show resolved Hide resolved
astroid/nodes/node_classes.py Show resolved Hide resolved
@jacobtylerwalls jacobtylerwalls added this to the 3.0.0a8 milestone Jul 15, 2023
@jacobtylerwalls jacobtylerwalls added the Enhancement ✨ Improvement to a component label Jul 15, 2023
@Pierre-Sassoulas Pierre-Sassoulas modified the milestones: 3.0.0a8, 3.0.0a9 Jul 15, 2023
@crazybolillo
Copy link
Contributor Author

Will first try to improve default_value and then change Name to AssignName so I only break one thing at a time hehe.

@crazybolillo
Copy link
Contributor Author

I changed the PR a little bit to provide more info on the arguments since before they were just a name wrapped in AssignName, now they have their lineno and col offsets set. What do you think?

Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

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

Looking good!

astroid/nodes/node_classes.py Outdated Show resolved Hide resolved
astroid/nodes/node_classes.py Outdated Show resolved Hide resolved
astroid/nodes/node_classes.py Outdated Show resolved Hide resolved
@jacobtylerwalls
Copy link
Member

Oh, and don't forget to add a changelog.

Closes pylint-dev#2213.

Arguments.arguments() has been modified so that it returns all arguments
as it should (according to its own doc). A test case was also added to
verify this.

Methods which counted on arguments() not containing vararg and kwonlyargs
have also been modified so they work with this new change.
* All arguments returned by arguments() are of type AssignName.
* default_value() was revered to the old way (substraction to index)
  with improvement suggested by Jacob.
* List comprehension used instead of list(filter...)
If available, these nodes (accessed through arguments()) now contain
lineno and col offset information.
@crazybolillo
Copy link
Contributor Author

crazybolillo commented Jul 20, 2023

I made all the suggested changes and added the change log. If you want me to rebase squash this or have any other suggestions let me know 🤝

@jacobtylerwalls jacobtylerwalls self-requested a review July 20, 2023 17:12
ChangeLog Outdated Show resolved Hide resolved
* find_argname now works when requesting vararg or kwargs.
* Fixed some annotations in code and documentation.
* Added changelog.
Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

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

Thank you for this helpful contribution!

@jacobtylerwalls jacobtylerwalls merged commit 4d03bec into pylint-dev:main Jul 20, 2023
18 checks passed
@cdce8p
Copy link
Member

cdce8p commented Jul 21, 2023

This causes a new error with pylint. A code sample is below. The pylint functional test suite does also fail.

class Sensor:
    def __init__(self, *, description):
        self._id = description.key
...
  File "/.../astroid/astroid/decorators.py", line 49, in wrapped
    for res in _func(node, context, **kwargs):
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.../astroid/astroid/nodes/node_classes.py", line 457, in _infer
    stmts = list(self.assigned_stmts(context=context))
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.../astroid/astroid/protocols.py", line 406, in _arguments_infer_argname
    yield from self.default_value(name).infer(context)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/.../astroid/astroid/nodes/node_classes.py", line 957, in default_value
    return self.defaults[idx]
           ~~~~~~~~~~~~~^^^^^
IndexError: list index out of range
A more complete example (although not necessary to reproduce the failure)
from dataclasses import dataclass

@dataclass
class EntityDescription:
    key: str

class Sensor:
    def __init__(self, *, description: EntityDescription) -> None:
        self._id = description.key

@jacobtylerwalls
Copy link
Member

Yes, I expected changes to be needed in pylint. I'm eager for a new alpha of astroid soon.

@jacobtylerwalls
Copy link
Member

But maybe some of the fixes will involve astroid changes?

@jacobtylerwalls
Copy link
Member

@crazybolillo Would you like to take the lead on the necessary follow-ups?

@crazybolillo
Copy link
Contributor Author

@crazybolillo Would you like to take the lead on the necessary follow-ups?

Sure, I will try to reproduce and add it to astroid's test suite.

@jacobtylerwalls
Copy link
Member

Thank you!

@crazybolillo
Copy link
Contributor Author

It seems like this was a scare. I fixed it with the following diff:

diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py
index 014955cf..6e78a7b1 100644
--- a/astroid/nodes/node_classes.py
+++ b/astroid/nodes/node_classes.py
@@ -947,8 +947,11 @@ class Arguments(
         ]
 
         index = _find_arg(argname, self.kwonlyargs)[0]
-        if index is not None and self.kw_defaults[index] is not None:
-            return self.kw_defaults[index]
+        if (index is not None) and (len(self.kw_defaults) > index):
+            if self.kw_defaults[index] is not None:
+                return self.kw_defaults[index]
+            else:
+                raise NoDefault(func=self.parent, name=argname)
 
         index = _find_arg(argname, args)[0]
         if index is not None:

Should I make a new PR and reference the same issue?

@jacobtylerwalls
Copy link
Member

Yes, and I'll take a look. No change log will be necessary.

crazybolillo added a commit to crazybolillo/astroid that referenced this pull request Jul 22, 2023
Refs pylint-dev#2213.

Requesting the default value for a kwonlyarg with no default would fail with
an IndexError, it now correctly raises a NoDefault exception. This issue
arised after changes in PR pylint-dev#2240.
crazybolillo added a commit to crazybolillo/astroid that referenced this pull request Jul 22, 2023
Refs pylint-dev#2213.

Requesting the default value for a kwonlyarg with no default would fail with
an IndexError, it now correctly raises a NoDefault exception. This issue
arised after changes in PR pylint-dev#2240.
jacobtylerwalls pushed a commit that referenced this pull request Jul 22, 2023
Refs #2213.

Requesting the default value for a kwonlyarg with no default would fail with
an IndexError, it now correctly raises a NoDefault exception. This issue
arose after changes in PR #2240.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement ✨ Improvement to a component
Projects
None yet
Development

Successfully merging this pull request may close these issues.

.arguments property ignores keyword-only args, *args, and **kwargs
5 participants