Skip to content

Commit

Permalink
Improve handling of __qualname__, __module__, and __class__
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed May 20, 2023
1 parent 9e21414 commit 928ed0d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 8 deletions.
7 changes: 0 additions & 7 deletions crates/ruff/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4841,13 +4841,6 @@ impl<'a> Checker<'a> {
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.
if self
.ctx
Expand Down
44 changes: 44 additions & 0 deletions crates/ruff/src/rules/pyflakes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,16 @@ mod tests {
"#,
&[Rule::UndefinedName],
);
flakes(
r#"
def f():
__qualname__ = 1
class Foo:
__qualname__
"#,
&[Rule::UnusedVariable],
);
}

#[test]
Expand Down Expand Up @@ -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>
Expand Down
32 changes: 31 additions & 1 deletion crates/ruff_python_semantic/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,19 @@ impl<'a> Context<'a> {
}
}

let mut seen_function = false;
let mut import_starred = false;
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
let scope = &self.scopes[scope_id];
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;
}
if index > 0 {
Expand Down Expand Up @@ -162,6 +170,28 @@ impl<'a> Context<'a> {
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();
}

Expand Down

0 comments on commit 928ed0d

Please sign in to comment.