Add autofix for Set-to-AbstractSet rewrite using reference tracking (#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.
This commit is contained in:
@@ -44,6 +44,18 @@ impl<'a> Binding<'a> {
|
||||
self.flags.contains(BindingFlags::EXPLICIT_EXPORT)
|
||||
}
|
||||
|
||||
/// Return `true` if this [`Binding`] represents an external symbol
|
||||
/// (e.g., `FastAPI` in `from fastapi import FastAPI`).
|
||||
pub const fn is_external(&self) -> bool {
|
||||
self.flags.contains(BindingFlags::EXTERNAL)
|
||||
}
|
||||
|
||||
/// Return `true` if this [`Binding`] represents an aliased symbol
|
||||
/// (e.g., `app` in `from fastapi import FastAPI as app`).
|
||||
pub const fn is_alias(&self) -> bool {
|
||||
self.flags.contains(BindingFlags::ALIAS)
|
||||
}
|
||||
|
||||
/// Return `true` if this [`Binding`] represents an unbound variable
|
||||
/// (e.g., `x` in `x = 1; del x`).
|
||||
pub const fn is_unbound(&self) -> bool {
|
||||
@@ -161,9 +173,25 @@ bitflags! {
|
||||
///
|
||||
/// For example, the binding could be `FastAPI` in:
|
||||
/// ```python
|
||||
/// import FastAPI as FastAPI
|
||||
/// from fastapi import FastAPI as FastAPI
|
||||
/// ```
|
||||
const EXPLICIT_EXPORT = 1 << 0;
|
||||
|
||||
/// The binding represents an external symbol, like an import or a builtin.
|
||||
///
|
||||
/// For example, the binding could be `FastAPI` in:
|
||||
/// ```python
|
||||
/// from fastapi import FastAPI
|
||||
/// ```
|
||||
const EXTERNAL = 1 << 1;
|
||||
|
||||
/// The binding is an aliased symbol.
|
||||
///
|
||||
/// For example, the binding could be `app` in:
|
||||
/// ```python
|
||||
/// from fastapi import FastAPI as app
|
||||
/// ```
|
||||
const ALIAS = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user