-
Notifications
You must be signed in to change notification settings - Fork 16
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: Avoid TC201 false positives for top-level type checking decls #170
Conversation
This also adds TC2xx checks for the RHS of an explicit `TypeAlias`
It should behave like an annotation for the purposes of determining typing only imports.
Previously we performed a simple substring match, but this is prone to error for cases like `Foo` vs `FooBar` which are separate names, but would produce false negatives.
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.
Depending on how the new type alias syntax in 3.12 interacts with from future import annotations we could also split this off into separate error codes in the TC0xx range, since at least for the x: TypeAlias = ... case we know the RHS will behave like an annotation with or without the future import, i.e. the expression will be evaluated immediately, so it needs to be wrapped if the symbols do not exist at runtime.
Seems like we could re-use TC101 for the TC1XX error range, but we might need to add a TC203 since users opt into just one of them.
So just so I'm on the same page here. What we're trying to achieve here is to add assignments to type aliases that happen inside a TYPE_CHECKING
block, to our list of non-runtime scoped annotations. This way we can raise valid error codes for uses of type aliases. Correct, or am I missing one or more details?
Did a very quick drive-by review, so left a few initial comments/questions, but will return to review properly once you consider the PR done-done 👍 Just ping me
flake8_type_checking/checker.py
Outdated
# mark all the top-level statements | ||
for stmt in node.body: | ||
setattr(stmt, TOP_LEVEL_PROPERTY, 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.
How about we name it TOP_LEVEL_IF_PROPERTY
?
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.
That reads to me like the if
is top level rather than the statement. I tried to come up with a more descriptive name, but it ends up being too long. Most unambiguous would be something like TYPE_CHECKING_SCOPED_TOP_LEVEL_PROPERTY
. If you don't mind it being that long-winded I'm happy to change it.
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 realize now that there are a bunch of corner-cases still here, it's quite common to have an if TYPE_CHECKING
block inside a class body, so declarations should remain scoped to the class body. It's probably more robust if we mark all the statements in global scope with GLOBAL_PROPERTY
. This should be fairly straightforward, although we would need to add some recursion for control flow in global scope, since control flow does not open a new scope, while function/class definitions do.
Then we could combine GLOBAL_PROPERTY
and in_type_checking_block
to accomplish the same check or add a second marker, like we were doing here, but only if the if
is also marked global.
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 ended up changing the implementation accordingly and added an extra statement to one of the inverse regression tests to ensure this works.
We may want to consider ignoring imports that aren't in global scope in the same way, although less common it is possible that there's an import inside a function definition to resolve a cyclic dependency, which should probably not count towards the imports we check.
True, but then we wouldn't have a matching error code for the opposite case where you do actually do need to quote it. If we end up having to add new errors for type aliases anyways it might make sense to separate type aliases out into their own two lists and generate new errors for them, but that does to some degree depend on how the new type alias syntax behaves. If it behaves like a regular annotation, then we would only need to special case
That's one of the two goals, it just happened to align with the other goal of remembering the top-level declarations inside But yes, I also wanted to treat the RHS of an explicit alias like an annotation (not just the ones inside type checking blocks), to the degree it is possible, so we can improve the accuracy of the errors. Otherwise defining |
Adds new error codes for type alias specific errors.
Fixes unnecessary filtering out of new-style aliases
@sondrelg Alright I think this should be ready for review. I separated out the type alias related errors into their own error codes TC007 and TC008, while the former is only relevant for old-style type aliases, the new syntax can only emit a TC007 (basically unconditionally when it is wrapped at all, since it always will be a ForwardRef, regardless of future import) |
Sorry for the delay here. A bit busy this week, but will hopefully get to it in a day or two. End of the weekend at the latest 👍 |
No worries, I ended up catching a potential problem with the implementation because of it. There's never any harm in letting your subconscious work on the problem for a few extra days. |
@sondrelg Just pinging you again in case this slipped through the cracks. |
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.
Sorry for the delay @Daverball. Really solid work 💪
TC100 = "TC100 Add 'from __future__ import annotations' import" | ||
TC101 = "TC101 Annotation '{annotation}' does not need to be a string literal" | ||
TC200 = "TC200 Annotation '{annotation}' needs to be made into a string literal" | ||
TC201 = "TC201 Annotation '{annotation}' does not need to be a string" | ||
TC201 = "TC201 Annotation '{annotation}' does not need to be a string literal" |
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.
We should update the readme for this too, if needed 👍
Fixes #168
This also adds checks and new error codes for the RHS of an explicit
TypeAlias
.This also improves accuracy of matching against string annotations by extracting the names from the annotation.