mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
Avoid propagating BindingKind::Global
and BindingKind::Nonlocal
(#5136)
## Summary This PR fixes a small quirk in the semantic model. Typically, when we see an import, like `import foo`, we create a `BindingKind::Importation` for it. However, if `foo` has been declared as a `global`, then we propagate the kind forward. So given: ```python global foo import foo ``` We'd create two bindings for `foo`, both with type `global`. This was originally borrowed from Pyflakes, and it exists to help avoid false-positives like: ```python def f(): global foo # Don't mark `foo` as "assigned but unused"! It's a global! foo = 1 ``` This PR removes that behavior, and instead tracks "Does this binding refer to a global?" as a flag. This is much cleaner, since it means we don't "lose" the identity of various bindings. As a very strange example of why this matters, consider: ```python def foo(): global Member from module import Member x: Member = 1 ``` `Member` is only used in a typing context, so we should flag it and say "move it to a `TYPE_CHECKING` block". However, when we go to analyze `from module import Member`, it has `BindingKind::Global`. So we don't even know that it's an import!
This commit is contained in:
parent
fd1dfc3bfa
commit
b3240dbfa2
10 changed files with 152 additions and 41 deletions
|
@ -57,6 +57,19 @@ impl<'a> Binding<'a> {
|
|||
self.flags.contains(BindingFlags::ALIAS)
|
||||
}
|
||||
|
||||
/// Return `true` if this [`Binding`] represents a `nonlocal`. A [`Binding`] is a `nonlocal`
|
||||
/// if it's declared by a `nonlocal` statement, or shadows a [`Binding`] declared by a
|
||||
/// `nonlocal` statement.
|
||||
pub const fn is_nonlocal(&self) -> bool {
|
||||
self.flags.contains(BindingFlags::NONLOCAL)
|
||||
}
|
||||
|
||||
/// Return `true` if this [`Binding`] represents a `global`. A [`Binding`] is a `global` if it's
|
||||
/// declared by a `global` statement, or shadows a [`Binding`] declared by a `global` statement.
|
||||
pub const fn is_global(&self) -> bool {
|
||||
self.flags.contains(BindingFlags::GLOBAL)
|
||||
}
|
||||
|
||||
/// Return `true` if this [`Binding`] represents an unbound variable
|
||||
/// (e.g., `x` in `x = 1; del x`).
|
||||
pub const fn is_unbound(&self) -> bool {
|
||||
|
@ -193,6 +206,28 @@ bitflags! {
|
|||
/// from fastapi import FastAPI as app
|
||||
/// ```
|
||||
const ALIAS = 1 << 2;
|
||||
|
||||
/// The binding is `nonlocal` to the declaring scope. This could be a binding created by
|
||||
/// a `nonlocal` statement, or a binding that shadows such a binding.
|
||||
///
|
||||
/// For example, both of the bindings in the following function are `nonlocal`:
|
||||
/// ```python
|
||||
/// def f():
|
||||
/// nonlocal x
|
||||
/// x = 1
|
||||
/// ```
|
||||
const NONLOCAL = 1 << 3;
|
||||
|
||||
/// The binding is `global`. This could be a binding created by a `global` statement, or a
|
||||
/// binding that shadows such a binding.
|
||||
///
|
||||
/// For example, both of the bindings in the following function are `global`:
|
||||
/// ```python
|
||||
/// def f():
|
||||
/// global x
|
||||
/// x = 1
|
||||
/// ```
|
||||
const GLOBAL = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue