-
Notifications
You must be signed in to change notification settings - Fork 1k
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 "delayed" annotations in the semantic model #5070
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PR Check ResultsEcosystem✅ ecosystem check detected no changes. BenchmarkLinux
Windows
|
charliermarsh
force-pushed
the
charlie/annotations
branch
from
June 14, 2023 17:43
0dde195
to
3630380
Compare
charliermarsh
added a commit
that referenced
this pull request
Jun 16, 2023
…ng (#5074) ## Summary This PR enables autofix behavior for the `flake8-pyi` rule that asks you to alias `Set` to `AbstractSet` when importing `collections.abc.Set`. It's not the most important rule, but it's a good isolated test-case for local symbol renaming. The renaming algorithm is outlined in-detail in the `renamer.rs` module. But to demonstrate the behavior, here's the diff when running this fix over a complex file that exercises a few edge cases: ```diff --- a/foo.pyi +++ b/foo.pyi @@ -1,16 +1,16 @@ if True: - from collections.abc import Set + from collections.abc import Set as AbstractSet else: - Set = 1 + AbstractSet = 1 -x: Set = set() +x: AbstractSet = set() -x: Set +x: AbstractSet -del Set +del AbstractSet def f(): - print(Set) + print(AbstractSet) def Set(): pass ``` Making this work required resolving a bunch of edge cases in the semantic model that were causing us to "lose track" of references. For example, the above wasn't possible with our previous approach to handling deletions (#5071). Similarly, the `x: Set` "delayed annotation" tracking was enabled via #5070. And many of these edits would've failed if we hadn't changed `BindingKind` to always match the identifier range (#5090). So it's really the culmination of a bunch of changes over the course of the week. The main outstanding TODO is that this doesn't support `global` or `nonlocal` usages. I'm going to take a look at that tonight, but I'm comfortable merging this as-is. Closes #1106. Closes #5091.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR tackles a corner case that we'll need to support local symbol renaming. It relates to a nuance in how we want handle annotations (i.e.,
AnnAssign
statements with no value, likex: int
in a function body).When we see a statement like:
We create a
BindingKind::Annotation
forx
. This is a specialBindingKind
that the resolver isn't allowed to return. For example, given:The second line will yield an
undefined-name
error.So why does this
BindingKind
exist at all? In Pyflakes, to support theunused-annotation
lint:If we don't track
BindingKind::Annotation
, we can't lint for unused variables that are only "defined" via annotations.There are a few other wrinkles to
BindingKind::Annotation
. One is that, if a binding already exists in the scope, we actually just discard theBindingKind
. So in this case:When we go to create the
BindingKind::Annotation
for the second statement, we notice that (1) we're creating an annotation but (2) the scope already has binding for the name -- so we just drop the binding on the floor. This has the nice property that annotations aren't considered to "shadow" another binding, which is important in a bunch of places (e.g., if we haveimport os; os: int
, we still consideros
to be an import, as we should). But it also means that these "delayed" annotations are one of the few remaining references that we don't track anywhere in the semantic model.This PR adds explicit support for these via a new
delayed_annotations
attribute on the semantic model. These should be extremely rare, but we do need to track them if we want to support local symbol renaming.This isn't the right way to model this
This isn't the right way to model this.
Here's an alternative:
BindingKind::Annotation
, and treat annotations as their own, separate concept.BindingId
on eachScope
, store a map from name to...SymbolId
.Symbol
abstraction, where a symbol can point to a current binding, and a list of annotations, like:If we did this, we could appropriately model the semantics described above. When we go to resolve a binding, we ignore annotations (always). When we try to find unused variables, we look through the list of symbols, and have sufficient information to discriminate between annotations and bound variables. Etc.
The main downside of this
Symbol
-based approach is that it's going to take a lot more work to implement, and it'll be less performant (we'll be storing more data per symbol, and our binding lookups will have an added layer of indirection).