-
-
Couldn't load subscription status.
- Fork 1.2k
Fix false negative for used-before-assignment (ExceptHandler) #4791
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
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.
Thank you, amazing fix ! I don't know if the change to consume is the way to go but this is certainely the expected result. Regarding control flow, if I understand correctly we would have to refactor rhe variable checker in order to do it properly ? Or is that a limitation of the visitor pattern used by pylint and there's notre much we can do ?
| * Fix false negative for ``used-before-assignment`` when the variable is assigned | ||
| in an exception handler, but used outside of the handler. | ||
|
|
||
| Closes #626 |
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 think it also closes #4391?
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.
Unfortunately it doesn't---the changes don't handle shadowing:
def function():
try:
1 / 0
except ZeroDivisionError as error:
try:
1 / 0
except ZeroDivisionError as error:
raise Exception("") from errorYou can also see that there are tests in tests/functional/u/unused/unused_variable.py:108-158 that still have TODOs in them.
Both occurrences of the as error are part of the same consumer in "to_consume", and they're both removed when the error astroid.Name node in the inner block is visited. I can think of a couple of different ways of fixing this, but none that are particularly easy/elegant. I can point out that we get a similar issue with loop variable shadowing:
def function():
for i in range(10):
for i in range(20):
print(i)Pylint currently reports a redefined-outer-name error for the inner i loop variable, but does not report unused-variable for the outer i loop variable. The redefined-outer-name check is handled in a separate visit_for method; I could do something similar in visit_excepthandler if you want.
P.S. I'll reply to your general review comment later, need to grab lunch now!
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 your very clear answer !
|
@Pierre-Sassoulas so in terms of the overall approach: right now each My changes allow for some This allows for the exception variable If we don't want to change Regarding control flow, I think the main limitation is the fact that the variable checkers rely on def function():
x = 10
del x
print(x)
function()I don't know the history of the Variables checker, but it seems to me like pylint will keep running into issues around control flow as long as this basic approach is used. I wouldn't say that this is a limitation of the AST visitor pattern though. I think improvements could be made by doing any of (in order of increasing code complexity and running time, but also correctness IMO):
But all of these would be really significant changes so I'm not exactly advocating for them, just thought I'd mention. Personally I'm still very happy using pylint with the current functionality! [1] Example: code = """
def function():
x = 10
del x
print(x)
"""
import astroid
ast = astroid.parse(code)
n = [n for n in ast.nodes_of_class(astroid.Name) if n.name == "x"][0]
print(n.lookup('x')) # Correctly doesn't return any AssignNames |
|
Thank you for your insightful answer. If we want to implement control flow at some point we'll need that kind of insight and I can't attain it while tending to the constant fire of new issues and pull requests, so I appreciate that very much. Maybe I could create a "control flow" issue referencing the possibilities you gave so it become the reference when the issue of control flow comes up ? This is a problem that appear often and I have no solution but hacks, personally. |
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.
π
|
Sure, that sounds fine to me. (Honestly I feel like it's your and the other pylint maintainers' call about whether to pursue this or not!!) |
Type of Changes
Description
This deals with the same logical issue as the third point in pylint-dev/astroid#1111: handling the fact that exception variables in
ExceptHandlers are only in scope inside the handling block. The way I fixed this was to explicitly filter out theAssignNamenodes that were the exception variable in anexceptclause, inNamesConsumer.get_next_to_consume.β I also modified
NamesConsumer.mark_as_consumedto allow for a name to be "partially consumed"β, so that I could piggyback on the existing check forused-before-assignmentand still get anunused-variablefor the except variable, as shown below:But if you don't like that change, I could remove the changes to
mark_as_consumedtoo, and either try to write a special case to get theunused-variableerror, or just leave it without checking for that.As a comment: the main complication I encountered is that the methods currently only look at the first
AssignNamenode, e.g.https://github.com/PyCQA/pylint/blob/9ae559d202814091896cdd3b95068a12fa1bd94b/pylint/checkers/variables.py#L1027
So I use some new filtering to avoid introducing false positives, like in
That said, pylint's
VariablesCheckerdoesn't do much to handle control flow, so I could also revert the filtering and instead add a special case forExceptHandlerinvisit_namedirectly.Closes #626