Respect parent-scoping rules for NamedExpr assignments (#4145)

This commit is contained in:
Charlie Marsh 2023-04-29 18:45:30 -04:00 committed by GitHub
parent 8d64747d34
commit 64b7280eb8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 8 deletions

View file

@ -132,3 +132,8 @@ def in_ipython_notebook() -> bool:
except NameError: except NameError:
return False # not in notebook return False # not in notebook
return True return True
def named_expr():
if any((key := (value := x)) for x in ["ok"]):
print(key)

View file

@ -121,3 +121,8 @@ def f(x: int):
print("A") print("A")
case y: case y:
pass pass
def f():
if any((key := (value := x)) for x in ["ok"]):
print(key)

View file

@ -4266,7 +4266,20 @@ impl<'a> Checker<'a> {
} }
} }
let scope = self.ctx.scope(); // Per [PEP 572](https://peps.python.org/pep-0572/#scope-of-the-target), named
// expressions in generators and comprehensions bind to the scope that contains the
// outermost comprehension.
let scope_id = if binding.kind.is_named_expr_assignment() {
self.ctx
.scopes
.ancestor_ids(self.ctx.scope_id)
.find_or_last(|scope_id| !self.ctx.scopes[*scope_id].kind.is_generator())
.unwrap_or(self.ctx.scope_id)
} else {
self.ctx.scope_id
};
let scope = &mut self.ctx.scopes[scope_id];
let binding = if let Some(index) = scope.get(name) { let binding = if let Some(index) = scope.get(name) {
let existing = &self.ctx.bindings[*index]; let existing = &self.ctx.bindings[*index];
match &existing.kind { match &existing.kind {
@ -4298,11 +4311,15 @@ impl<'a> Checker<'a> {
// Don't treat annotations as assignments if there is an existing value // Don't treat annotations as assignments if there is an existing value
// in scope. // in scope.
let scope = self.ctx.scope_mut(); if binding.kind.is_annotation() && scope.defines(name) {
if !(binding.kind.is_annotation() && scope.defines(name)) { self.ctx.bindings.push(binding);
scope.add(name, binding_id); return;
} }
// Add the binding to the scope.
scope.add(name, binding_id);
// Add the binding to the arena.
self.ctx.bindings.push(binding); self.ctx.bindings.push(binding);
} }
@ -4579,9 +4596,10 @@ impl<'a> Checker<'a> {
return; return;
} }
let current = self.ctx.scope(); let scope = self.ctx.scope();
if id == "__all__" if id == "__all__"
&& matches!(current.kind, ScopeKind::Module) && scope.kind.is_module()
&& matches!( && matches!(
parent.node, parent.node,
StmtKind::Assign { .. } | StmtKind::AugAssign { .. } | StmtKind::AnnAssign { .. } StmtKind::Assign { .. } | StmtKind::AugAssign { .. } | StmtKind::AnnAssign { .. }
@ -4619,7 +4637,7 @@ impl<'a> Checker<'a> {
// Grab the existing bound __all__ values. // Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &parent.node { if let StmtKind::AugAssign { .. } = &parent.node {
if let Some(index) = current.get("__all__") { if let Some(index) = scope.get("__all__") {
if let BindingKind::Export(Export { names: existing }) = if let BindingKind::Export(Export { names: existing }) =
&self.ctx.bindings[*index].kind &self.ctx.bindings[*index].kind
{ {
@ -4662,6 +4680,27 @@ impl<'a> Checker<'a> {
} }
} }
if self
.ctx
.expr_ancestors()
.any(|expr| matches!(expr.node, ExprKind::NamedExpr { .. }))
{
self.add_binding(
id,
Binding {
kind: BindingKind::NamedExprAssignment,
runtime_usage: None,
synthetic_usage: None,
typing_usage: None,
range: expr.range(),
source: Some(*self.ctx.current_stmt()),
context: self.ctx.execution_context(),
exceptions: self.ctx.exceptions(),
},
);
return;
}
self.add_binding( self.add_binding(
id, id,
Binding { Binding {

View file

@ -322,7 +322,7 @@ pub fn unused_variable(checker: &mut Checker, scope: ScopeId) {
.map(|(name, index)| (name, &checker.ctx.bindings[*index])) .map(|(name, index)| (name, &checker.ctx.bindings[*index]))
{ {
if !binding.used() if !binding.used()
&& binding.kind.is_assignment() && (binding.kind.is_assignment() || binding.kind.is_named_expr_assignment())
&& !checker.settings.dummy_variable_rgx.is_match(name) && !checker.settings.dummy_variable_rgx.is_match(name)
&& name != &"__tracebackhide__" && name != &"__tracebackhide__"
&& name != &"__traceback_info__" && name != &"__traceback_info__"

View file

@ -210,4 +210,13 @@ F841_0.py:122:14: F841 [*] Local variable `y` is assigned to but never used
| |
= help: Remove assignment to unused variable `y` = help: Remove assignment to unused variable `y`
F841_0.py:127:21: F841 [*] Local variable `value` is assigned to but never used
|
127 | def f():
128 | if any((key := (value := x)) for x in ["ok"]):
| ^^^^^ F841
129 | print(key)
|
= help: Remove assignment to unused variable `value`

View file

@ -248,4 +248,13 @@ F841_0.py:122:14: F841 [*] Local variable `y` is assigned to but never used
| |
= help: Remove assignment to unused variable `y` = help: Remove assignment to unused variable `y`
F841_0.py:127:21: F841 [*] Local variable `value` is assigned to but never used
|
127 | def f():
128 | if any((key := (value := x)) for x in ["ok"]):
| ^^^^^ F841
129 | print(key)
|
= help: Remove assignment to unused variable `value`

View file

@ -253,6 +253,7 @@ pub enum BindingKind<'a> {
Annotation, Annotation,
Argument, Argument,
Assignment, Assignment,
NamedExprAssignment,
Binding, Binding,
LoopVar, LoopVar,
Global, Global,