-
-
Notifications
You must be signed in to change notification settings - Fork 280
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
Improve logic of variable lookup (LookupMixin._filter_stmts) #1111
Improve logic of variable lookup (LookupMixin._filter_stmts) #1111
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, thank you for the very detailed merge request. I tried to include that in pylint and this does fix 3711 (which was broken before see the tests suite in pylint-dev/pylint#4777. ), but regarding pylint's 626 I don't think it does the result stay the same. Or maybe I did not understand something ?
I get the following:
____________________________________________________________________________________ test_functional[unused_variable] _____________________________________________________________________________________
self = <pylint.testutils.lint_module_test.LintModuleTest object at 0x7fe67f24d640>
def runTest(self):
> self._runTest()
E AssertionError: Wrong results for file "unused_variable":
E
E Expected in testdata:
E 166: unused-variable
E 168: undefined-variable
No @Pierre-Sassoulas that's totally my bad. I got so excited about the change that I must have messed up testing with pylint. After double-checking just now: The change in the third commit does fix an astroid error in how exceptions are handled, and in particular the test Anyway, I see two different options:
In all cases, I can rebase and force push, or make new commits on top of this branch, or create a separate branch/PR. Not sure what workflow you'd prefer. Let me know what you'd like me to do, and sorry again for the inconvenience! [1] Note: I just made a small commit beba399 clarifying the test that fails on master; there are a few others as well. (Edit: squashed the commit with a force push, but the tests reflect this comment.) |
Thank you for the explanation. I think we can keep the third commit, and only remove the "Closes 626" from the changelog. Could you provide the use case the third commit fixes so I can add it in #4777, please ? :) |
Updated to remove the reference to issue 626. Here's an example of an improvement to pylint: def main(lst):
try:
raise ValueError
except ValueError as e:
pass
for e in lst:
pass
# e will be undefined if lst is empty
print(e)
main([]) # Raises UnboundLocalError Currently pylint doesn't report any errors with the above code, but after this change, it correctly reports $ python -m pylint test.py
************* Module test
test.py:11:10: W0631: Using possibly undefined loop variable 'e' (undefined-loop-variable) |
Thank for the example ! pylint did not raise anything before (Old result available on the pylint MR, I updated the tests suite with your example) and now rightly raise an |
Regarding the conflict I had during merge, I think a change was introduced independentely and I took your version. Do not hesitate to rebase and fix the conflict yourself if I did that wrong :) |
db662bf
to
35beea0
Compare
@Pierre-Sassoulas thank you! Regarding the error, I think def main(lst):
for e in lst:
pass
# e will be undefined if lst is empty
print(e) # also undefined-loop-variable
main([]) # Raises UnboundLocalError My understanding is that's the whole point of this message, so I don't think it's a problem. Regarding the merge conflict, there was a conflict because #1097 changed the code right above my change in |
Make sense, thank you for clearing that up :) ! And thank you for the huge work on this, this is going to be the main feature of 2.6.6 ! |
Steps
Description
My interest in astroid's lookup algorithm was piqued so I found some outstanding issues to investigate. This PR contains three logical changes, but I decided to submit just one PR because my fixes are all in the same function,
LookupMixin._filter_stmts
, which is the main helper method for determining which assignment/delete statements are "relevant" for a given variable.To help review, I split up the PR into three different commits, one for each issue. If you'd like, I can also make three separate PRs from them, no big deal. I also added a bunch of test cases for each one---including some test cases for existing correct behaviour, as I wasn't sure whether these cases for
lookup
were being tested indirectly elsewhere in the test suite. Happy to delete some tests I added if they're redundant.False positive undefined-loop-variable with pylint 2.5.3 pylint#3711. The issue here was that an assignment statement to a variable in one branch of an if statement overrode statements associated with a use of that variable in another branch:
Fixed it by explicitly ignoring statements that "
are_exclusive
" with the variable in_filter_stmts
. Note that there had previously been a check for this, I just moved the check up a bit.Scope lookup problem when using closures #180. The issue here was that
AssignName
nodes representing function parameters didn't have a "_parent_stmts
" value consistent with the other nodes in the function's body: doingnode.statement().parent
returns the parent of theFunctionDef
, which won't result in a "match" at this check in_filter_stmts
:https://github.com/PyCQA/astroid/blob/22e0cdcfcdff3ea80142ba6b90758b42a85ed8e0/astroid/node_classes.py#L1178-L1179
This results in the following error:
One subtlety: because of this check,
https://github.com/PyCQA/astroid/blob/22e0cdcfcdff3ea80142ba6b90758b42a85ed8e0/astroid/node_classes.py#L1222-L1225
the bug only occurs when the variable being used does not have the same parent as the assignment statement overwriting the function parameter, which is why I included the strange
if True:
above. (Comment: I wonder if this check was actually an attempt to fix this same problem?)The fix I added was to have a special case for
Arguments
nodes when updating_stmt_parents
. Not a big fan of having special cases like this, but not sure if there's a cleaner way.(Edit: the change is inspired by, but does not fix, the issue) pylint doesn't understand the scope of except clause variables pylint#626. The issue here is pretty straightforward,
ExceptHandler
s have their own local scope for the exception variable, as of Python 3. From the Python spec:Here's an example:
My fix here was a bit clunky, adding a special case to reset the accumulated assignments in
_filter_stmts
if the variable being was was inside theExceptHandler
that bound that variable, and otherwise to skip thatAssignName
. I think it would be possible (preferred?) to makeExceptHandler
its own scope (and subclassLocalsDictNodeNG
), but this was a bigger change and so didn't want to make that change without discussion.Type of Changes
Related Issue
Closes pylint-dev/pylint#3711
Closes #180
ClosesRef pylint-dev/pylint#626. The issue still occurs in pylint, see #1111 (comment).