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 false negative for used-before-assignment (ExceptHandler) #4791
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 error
You 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
ExceptHandler
s are only in scope inside the handling block. The way I fixed this was to explicitly filter out theAssignName
nodes that were the exception variable in anexcept
clause, inNamesConsumer.get_next_to_consume
.β I also modified
NamesConsumer.mark_as_consumed
to allow for a name to be "partially consumed"β, so that I could piggyback on the existing check forused-before-assignment
and still get anunused-variable
for the except variable, as shown below:But if you don't like that change, I could remove the changes to
mark_as_consumed
too, and either try to write a special case to get theunused-variable
error, 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
AssignName
node, 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
VariablesChecker
doesn't do much to handle control flow, so I could also revert the filtering and instead add a special case forExceptHandler
invisit_name
directly.Closes #626