mirror of https://github.com/astral-sh/ruff
[`pyflakes`] Fix false positive for `__class__` in lambda expressions within class definitions (`F821`) (#20564)
## Summary Fixes #20562 --------- Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
parent
7576669297
commit
3e8685d2ec
|
|
@ -0,0 +1,21 @@
|
|||
class C:
|
||||
f = lambda self: __class__
|
||||
|
||||
|
||||
print(C().f().__name__)
|
||||
|
||||
# Test: nested lambda
|
||||
class D:
|
||||
g = lambda self: (lambda: __class__)
|
||||
|
||||
|
||||
print(D().g()().__name__)
|
||||
|
||||
# Test: lambda outside class (should still fail)
|
||||
h = lambda: __class__
|
||||
|
||||
# Test: lambda referencing module-level variable (should not be flagged as F821)
|
||||
import uuid
|
||||
|
||||
class E:
|
||||
uuid = lambda: str(uuid.uuid4())
|
||||
|
|
@ -2116,7 +2116,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
|||
| Expr::DictComp(_)
|
||||
| Expr::SetComp(_) => {
|
||||
self.analyze.scopes.push(self.semantic.scope_id);
|
||||
self.semantic.pop_scope();
|
||||
self.semantic.pop_scope(); // Lambda/Generator/Comprehension scope
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -3041,7 +3041,35 @@ impl<'a> Checker<'a> {
|
|||
if let Some(parameters) = parameters {
|
||||
self.visit_parameters(parameters);
|
||||
}
|
||||
|
||||
// Here we add the implicit scope surrounding a lambda which allows code in the
|
||||
// lambda to access `__class__` at runtime when the lambda is defined within a class.
|
||||
// See the `ScopeKind::DunderClassCell` docs for more information.
|
||||
let added_dunder_class_scope = if self
|
||||
.semantic
|
||||
.current_scopes()
|
||||
.any(|scope| scope.kind.is_class())
|
||||
{
|
||||
self.semantic.push_scope(ScopeKind::DunderClassCell);
|
||||
let binding_id = self.semantic.push_binding(
|
||||
TextRange::default(),
|
||||
BindingKind::DunderClassCell,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
self.semantic
|
||||
.current_scope_mut()
|
||||
.add("__class__", binding_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
self.visit_expr(body);
|
||||
|
||||
// Pop the DunderClassCell scope if it was added
|
||||
if added_dunder_class_scope {
|
||||
self.semantic.pop_scope();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ mod tests {
|
|||
#[test_case(Rule::UndefinedName, Path::new("F821_30.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_31.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_32.pyi"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_33.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.pyi"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F821 Undefined name `__class__`
|
||||
--> F821_33.py:15:13
|
||||
|
|
||||
14 | # Test: lambda outside class (should still fail)
|
||||
15 | h = lambda: __class__
|
||||
| ^^^^^^^^^
|
||||
16 |
|
||||
17 | # Test: lambda referencing module-level variable (should not be flagged as F821)
|
||||
|
|
||||
Loading…
Reference in New Issue