-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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 bugs in W0640 cell-var-from-loop checker #4827
Fix bugs in W0640 cell-var-from-loop checker #4827
Conversation
1. Handle cell var appearing in node inside function. 2. Handle cell var appearing in non-trivial default argument expression. 3. Use astroid's lookup method to account for variable shadowing.
Previously, this check would have many false negatives when both unused-variable and used-before-assignment were disabled.
Pull Request Test Coverage Report for Build 1122866118
π - Coveralls |
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.
Thank you for this MR ! Closes multiple issues, very well tested and also super clean, which is on par with that what you usually do π
@@ -455,6 +455,11 @@ def is_ancestor_name( | |||
return False | |||
|
|||
|
|||
def is_being_called(node: astroid.node_classes.NodeNG) -> bool: |
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.
I like this style of doing things better than having super big conditional without explanation. Even though I know this won't be re-used it's nice to have a function if only for clarity.
|
||
Special cases where we don't care about the error: | ||
1. When the node's function is immediately called, e.g. (lambda: i)() | ||
2. When the node's function is returned from within the loop, e.g. return lambda: i |
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.
π
x # [cell-var-from-loop] | ||
+ y) # [cell-var-from-loop] |
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.
x # [cell-var-from-loop] | |
+ y) # [cell-var-from-loop] | |
x+ y) # [cell-var-from-loop, cell-var-from-loop] |
I don't know if it's intentional or if you want to differentiate x and y to see that a message is send for each, but you could also do that.
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.
@Pierre-Sassoulas I actually wasn't aware you could test for multiple error occurrences in the same line, good to know! π
Since you've merged in this PR I'm assuming you don't actually need me to make this change, but I'll keep it in mind for the future.
Hi, I'm little confused by this warning which """cell-var-from-loop example."""
grouped_messages = {
"category_a": [
{"key_a": 3},
{"key_a": 1},
{"key_a": 2},
],
"category_b": [
{"key_b": 5},
{"key_b": 3},
{"key_b": 8},
],
}
sorting_keys = {
"category_a": "key_a",
"category_b": "key_b",
}
sorted_messages = {}
for category, messages in grouped_messages.items():
sorted_messages[category] = sorted(
(message for message in messages),
key=lambda msg: msg[sorting_keys[category]]
)
print(sorted_messages) $ pylint example.py
************* Module example
example.py:25:41: W0640: Cell variable category defined in loop (cell-var-from-loop)
------------------------------------------------------------------
Your code has been rated at 8.33/10 (previous run: 8.33/10, +0.00) I got rid of this warning by replacing python3 example.py
{'category_a': [{'key_a': 1}, {'key_a': 2}, {'key_a': 3}], 'category_b': [{'key_b': 3}, {'key_b': 5}, {'key_b': 8}]} |
This looks like a false positive to me. In any case, could you open a new issue for this? Continuing the discussion in closed PRs can lead to the discussion being lost (as they are not easily visible in the issue list). |
Type of Changes
Description
I reviewed some of the issues related to W0640, and since they were all related to the same method (
VariablesChecker._check_late_binding_closure
) I tackled them in one go, reviewing the existing code and adding some additional tests too.False positive for cell-var-from-loopΒ #3107. The current code checks for names which are used as default arguments as follows:
https://github.com/PyCQA/pylint/blob/b8021d33a4f34b410d73fa389621e2c4ae5e22c0/pylint/checkers/variables.py#L1820-L1821
This only works for names used as simple default arguments, e.g.
lambda x=i : x
, but not ones with a more complex node structure likelambda x=i[0] : x
. The obvious change is to useutils.is_default_argument
, but I realized that there are cases where a default argument is a cell var defined from a loop, it just requires two levels of function nesting, as in the following (new) test case:So I modified the algorithm a little to "jump up" a scope level when encountering a name in a default argument, rather than just returning right away.
cell-var-from-loop misses case with nested loopΒ #2846 The method currently uses the following to decide to skip the cell-var-from-loop check:
https://github.com/PyCQA/pylint/blob/b8021d33a4f34b410d73fa389621e2c4ae5e22c0/pylint/checkers/variables.py#L1817-L1819
This results in a false negative when the cell variable appears inside a comprehension (itself inside a function body), e.g.
My fix here was simply to use
node.frame()
rather thannode.scope()
, since the former skips comprehension scopes. A bit further below, I also added an explicitnode.name in node_scope.locals
to skip the remaining work if the node is known to not be a cell variable at all.I modified the algorithm so that instead of taking an
assignment_node
from the caller (which was the firstAssignName
in the "current consumer" inVariablesChecker.visit_name
), it instead uses astroid'slookup
method to find assignments associated with the name, taking into account basic control flow. This fixes a false negative like the following (also added as new test case):Previously, this method was called in the following location in
visit_name
:https://github.com/PyCQA/pylint/blob/b8021d33a4f34b410d73fa389621e2c4ae5e22c0/pylint/checkers/variables.py#L1054-L1057
This meant that if both
undefined-variable
andused-before-assignment
were disabled, there would be many false negatives forcell-var-from-loop
as well. I'm pretty sure this was unintentional. For example:I moved the check out of the if statement. (This is in a separate commit.)
I also did a little bit of code cleanup to use
checker.utils
a bit more, hope that's okay. But if not happy to revert some of those changes.Closes #2846
Closes #3107
Comment: Also, I think #2121 is fixed on main?