Skip to content

Conversation

terencehonles
Copy link
Contributor

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.

@asottile
Copy link
Member

#530 predates this and has similar goals / problems -- perhaps collaborate with that PR?

@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch from ac90967 to a795822 Compare March 23, 2021 22:18
@terencehonles
Copy link
Contributor Author

terencehonles commented Mar 23, 2021

#530 predates this and has similar goals / problems -- perhaps collaborate with that PR?

@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:

  1. The logic handles the non runtime bindings in either the body or the else of the if/else expression
  2. The runtime attribute defaults to True (it generally will be) which needs less changes in other parts of the codebase.
  3. The lines https://github.com/PyCQA/pyflakes/pull/530/files#diff-5df44d79406311387e0056faae7a8092d1ce9cc85c7d15420186e45a0c712283R1178-R1181 continues looking up the stack for a runtime value, but it's more likely that the non runtime value will be declared at a higher scope and therefore the code I wrote warns and aborts immediately.

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.

@asottile
Copy link
Member

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)

@PetterS
Copy link
Contributor

PetterS commented Mar 24, 2021

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.

@terencehonles
Copy link
Contributor Author

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.

@PetterS
Copy link
Contributor

PetterS commented Mar 25, 2021

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)

Merged it with master again now.

@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch from a795822 to 117d5de Compare April 12, 2021 23:21
@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch from 117d5de to 3bbd41a Compare October 18, 2021 14:43
@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch from 3bbd41a to 53c3070 Compare August 11, 2022 11:12
@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch from 53c3070 to 58506b3 Compare March 31, 2023 10:58
@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch from 58506b3 to 6dcf6a7 Compare September 27, 2023 11:36
@terencehonles
Copy link
Contributor Author

#530 predates this and has similar goals / problems -- perhaps collaborate with that PR?

@asottile since the PR in comment above is not going to be updated by its author

#530 (comment)

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.

Copy link
Member

@asottile asottile left a 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

Comment on lines 732 to 730
nodeDepth = 0
offset = None
_in_annotation = AnnotationState.NONE
_in_type_check_guard = False
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty common pattern, and as you can see it already existed here. I've moved all of these into the __init__ to match your request.

isinstance(parent.op, ast.RShift)):
self.report(messages.InvalidPrintSyntax, node)

try:
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only added a little bit here, and the exception was already broader than it needed to be. I didn't realize this was a problem, and I moved all the unrelated code (including the existing code) out of the exception to satisfy this request.

LIST = TUPLE

def IMPORT(self, node):
runtime = not self._in_type_check_guard
Copy link
Member

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?

@terencehonles
Copy link
Contributor Author

Thanks for the review, I'll see when I have time to address it.

@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch from 6dcf6a7 to 8c4da4a Compare February 9, 2024 09:38
@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch 3 times, most recently from 0480c35 to 0039d2b Compare September 23, 2025 15:31
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.
@terencehonles terencehonles force-pushed the warn-about-using-type-checking-imports-at-runtime branch from 0039d2b to faab0f8 Compare September 23, 2025 15:33
@terencehonles
Copy link
Contributor Author

It has taken me awhile to revisit this PR in order to address the comments (rather than just to update it), but I have addressed most of the comments.

Responding to @jayvdb's comment

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.

I was avoiding calling it in_type_checking since I thought there might end up being other reasons why a binding wouldn't be in the "runtime" scope and I wouldn't want to add additional flags.

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.

I agree this might make sense, but it would require a bigger re-work and I believed I was following how _in_annotation was implemented.

I hope these changes are satisfactory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants