-
Notifications
You must be signed in to change notification settings - Fork 179
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
track if typing.TYPE_CHECKING
to warn about non runtime bindings
#622
base: main
Are you sure you want to change the base?
track if typing.TYPE_CHECKING
to warn about non runtime bindings
#622
Conversation
#530 predates this and has similar goals / problems -- perhaps collaborate with that PR? |
ac90967
to
a795822
Compare
@asottile It looks like I actually ended up writing about the same code 😅 however there are a few things I have which the other one doesn't:
It also looks like the code was waiting review for almost a year. Is there something you wanted changed from that PR? I'm willing to work with @PetterS, but I didn't realize there was an PR that old for this already opened. |
ah shoot, that's probably on me -- I might've missed the last round of reviews on there and then it lapsed (and now conflicts) |
I can revive that PR if we agree that it it is likely to be merged. This is a problem that has been observed for real code, so would be good to fix. |
It should be pretty easy to xref this PR in order to see what might also need to change since I based my PR off the most recent release (I was not aware of your PR when starting). We're 99% inline with each other's implementations. |
Merged it with master again now. |
a795822
to
117d5de
Compare
117d5de
to
3bbd41a
Compare
3bbd41a
to
53c3070
Compare
53c3070
to
58506b3
Compare
58506b3
to
6dcf6a7
Compare
@asottile since the PR in comment above is not going to be updated by its author any chance you'd be willing to review this PR? As mentioned above #622 (comment) it had used negated logic compared to the other PR in order to minimize the number of changes to the code base, and I've continued to both use and update this PR for changes on the development branch. I can seek a 2nd approver if we can move forward with this PR. |
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 don't think the tests cover all the cases you intend to demonstrate here
ClassDefinition( | ||
node.name, node, runtime=not self._in_type_check_guard)) |
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.
if you're going to reindent things can you use this style? https://github.com/asottile/add-trailing-comma#multi-line-method-invocation-style----why
alternatively I could reformat the codebase and the you'd deal with a round of merge conflicts -- either way
nodeDepth = 0 | ||
offset = None | ||
_in_annotation = AnnotationState.NONE | ||
_in_type_check_guard = False |
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.
these are actually all incorrect -- should be assigned in __init__
since they are not class vars
if (not n.runtime and not ( | ||
self._in_type_check_guard | ||
or self._in_annotation)): | ||
self.report(messages.UndefinedName, node, name) |
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.
should we make a separate type for this (so the message can clarify "only something something type checking"?)
@@ -1073,12 +1078,18 @@ def handleNodeLoad(self, node, parent): | |||
self.report(messages.InvalidPrintSyntax, node) | |||
|
|||
try: |
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.
this try is now way too broad -- it originally only guarded the name lookup but now has a whole bunch of unrelated code in it
@@ -226,10 +226,11 @@ class Binding: | |||
the node that this binding was last used. | |||
""" | |||
|
|||
def __init__(self, name, source): | |||
def __init__(self, name, source, runtime=True): |
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.
this boolean-trap should be keyword only -- same for all the other places it is introduced
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.
see https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/
This should at least be , *, runtime=True
However as noted earlier in reviews, runtime
isnt a good name. I would prefer to see a more explicit in_type_checking
or similar, and have one name consistently used everywhere.
Also I suspect that this feature will be a lot cleaner if TYPE_CHECKING
is treated as a special scope, which automatically means items in it are not placed into the normal scope logic. Then items in that special scope can only be considered by explicitly including them, only in the contexts when they are useful.
# normalize body and orelse to a list | ||
body, orelse = ( | ||
i if isinstance(i, list) else [i] | ||
for i in (node.body, node.orelse)) |
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
is a bad name here -- i
should be for incrementing integers only
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.
also this is a very strange way to do this -- don't go through a generator for two assignments just do the two assignments
|
||
for n in orelse: | ||
self.handleNode(n, node) | ||
self._in_type_check_guard = orig |
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.
this should be a context manager or a try:
finally:
-- as it is right now it is not exception safe
|
||
# set the guard and handle the orelse | ||
if type_checking: | ||
self._in_type_check_guard = True if reverse else orig |
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.
True if reverse else orig
is better written as reverse or orig
-- any time True if
or False if
should set off alarm bells
# check if the body/orelse should be handled specially because it is | ||
# a if TYPE_CHECKING guard. | ||
test = node.test | ||
reverse = False | ||
if isinstance(test, ast.UnaryOp) and isinstance(test.op, ast.Not): | ||
test = test.operand | ||
reverse = True |
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'd rather see a big if statement than a bunch of locals introduced here
@@ -2038,12 +2089,15 @@ def TUPLE(self, node): | |||
LIST = TUPLE | |||
|
|||
def IMPORT(self, node): | |||
runtime = not self._in_type_check_guard |
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.
if we're going to invert this maybe it should just be _runtime
? but then that's not a great name so maybe runtime
is not a great name?
Thanks for the review, I'll see when I have time to address it. |
When importing or defining values in ``if typing.TYPE_CHECKING`` blocks the bound names will not be available at runtime and may cause errors when used in the following way:: import typing if typing.TYPE_CHECKING: from module import Type # some slow import or circular reference def method(value) -> Type: # the import is needed by the type checker assert isinstance(value, Type) # this is a runtime error This change allows pyflakes to track what names are bound for runtime use, and allows it to warn when a non runtime name is used in a runtime context.
6dcf6a7
to
8c4da4a
Compare
When importing or defining values in
if typing.TYPE_CHECKING
blocks the bound names will not be available at runtime and may cause errors when used in the following way:This change allows pyflakes to track what names are bound for runtime use, and allows it to warn when a non runtime name is used in a runtime context.