Improve handling of __qualname__, __module__, and __class__ (#4512)

This commit is contained in:
Charlie Marsh 2023-05-19 23:03:45 -04:00 committed by GitHub
parent 9e21414294
commit 6aa9900c03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 8 deletions

View file

@ -4841,13 +4841,6 @@ impl<'a> Checker<'a> {
return; return;
} }
// Allow "__module__" and "__qualname__" in class scopes.
if (id == "__module__" || id == "__qualname__")
&& matches!(self.ctx.scope().kind, ScopeKind::Class(..))
{
return;
}
// Avoid flagging if `NameError` is handled. // Avoid flagging if `NameError` is handled.
if self if self
.ctx .ctx

View file

@ -473,6 +473,16 @@ mod tests {
"#, "#,
&[Rule::UndefinedName], &[Rule::UndefinedName],
); );
flakes(
r#"
def f():
__qualname__ = 1
class Foo:
__qualname__
"#,
&[Rule::UnusedVariable],
);
} }
#[test] #[test]
@ -1151,6 +1161,40 @@ mod tests {
"#, "#,
&[], &[],
); );
flakes(
r#"
class Test(object):
print(__class__.__name__)
def __init__(self):
self.x = 1
t = Test()
"#,
&[Rule::UndefinedName],
);
flakes(
r#"
class Test(object):
X = [__class__ for _ in range(10)]
def __init__(self):
self.x = 1
t = Test()
"#,
&[Rule::UndefinedName],
);
flakes(
r#"
def f(self):
print(__class__.__name__)
self.x = 1
f()
"#,
&[Rule::UndefinedName],
);
} }
/// See: <https://github.com/PyCQA/pyflakes/blob/04ecb0c324ef3b61124e2f80f9e1af6c3a4c7b26/pyflakes/test/test_imports.py> /// See: <https://github.com/PyCQA/pyflakes/blob/04ecb0c324ef3b61124e2f80f9e1af6c3a4c7b26/pyflakes/test/test_imports.py>

View file

@ -126,11 +126,19 @@ impl<'a> Context<'a> {
} }
} }
let mut seen_function = false;
let mut import_starred = false; let mut import_starred = false;
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() { for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
let scope = &self.scopes[scope_id]; let scope = &self.scopes[scope_id];
if scope.kind.is_class() { if scope.kind.is_class() {
if symbol == "__class__" { // Allow usages of `__class__` within methods, e.g.:
//
// ```python
// class Foo:
// def __init__(self):
// print(__class__)
// ```
if seen_function && matches!(symbol, "__class__") {
return ResolvedReference::ImplicitGlobal; return ResolvedReference::ImplicitGlobal;
} }
if index > 0 { if index > 0 {
@ -162,6 +170,28 @@ impl<'a> Context<'a> {
return ResolvedReference::Resolved(scope_id, *binding_id); return ResolvedReference::Resolved(scope_id, *binding_id);
} }
// Allow usages of `__module__` and `__qualname__` within class scopes, e.g.:
//
// ```python
// class Foo:
// print(__qualname__)
// ```
//
// Intentionally defer this check to _after_ the standard `scope.get` logic, so that
// we properly attribute reads to overridden class members, e.g.:
//
// ```python
// class Foo:
// __qualname__ = "Bar"
// print(__qualname__)
// ```
if index == 0 && scope.kind.is_class() {
if matches!(symbol, "__module__" | "__qualname__") {
return ResolvedReference::ImplicitGlobal;
}
}
seen_function |= scope.kind.is_function();
import_starred = import_starred || scope.uses_star_imports(); import_starred = import_starred || scope.uses_star_imports();
} }