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::DictComp(_)
|
||||||
| Expr::SetComp(_) => {
|
| Expr::SetComp(_) => {
|
||||||
self.analyze.scopes.push(self.semantic.scope_id);
|
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 {
|
if let Some(parameters) = parameters {
|
||||||
self.visit_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);
|
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);
|
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_30.py"))]
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_31.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_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.py"))]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.pyi"))]
|
#[test_case(Rule::UndefinedExport, Path::new("F822_0.pyi"))]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
#[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