Skip to content

Commit 69d9212

Browse files
Propagate reads on global variables (#11584)
## Summary This PR ensures that if a variable is bound via `global`, and then the `global` is read, the originating variable is also marked as read. It's not perfect, in that it won't detect _rebindings_, like: ```python from app import redis_connection def func(): global redis_connection redis_connection = 1 redis_connection() ``` So, above, `redis_connection` is still marked as unused. But it does avoid flagging `redis_connection` as unused in: ```python from app import redis_connection def func(): global redis_connection redis_connection() ``` Closes #11518.
1 parent 4a30558 commit 69d9212

File tree

6 files changed

+46
-15
lines changed

6 files changed

+46
-15
lines changed

crates/ruff_linter/src/checkers/ast/mod.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -588,16 +588,18 @@ impl<'a> Visitor<'a> for Checker<'a> {
588588
Stmt::Global(ast::StmtGlobal { names, range: _ }) => {
589589
if !self.semantic.scope_id.is_global() {
590590
for name in names {
591-
if let Some(binding_id) = self.semantic.global_scope().get(name) {
592-
// Mark the binding in the global scope as "rebound" in the current scope.
591+
let binding_id = self.semantic.global_scope().get(name);
592+
593+
// Mark the binding in the global scope as "rebound" in the current scope.
594+
if let Some(binding_id) = binding_id {
593595
self.semantic
594596
.add_rebinding_scope(binding_id, self.semantic.scope_id);
595597
}
596598

597599
// Add a binding to the current scope.
598600
let binding_id = self.semantic.push_binding(
599601
name.range(),
600-
BindingKind::Global,
602+
BindingKind::Global(binding_id),
601603
BindingFlags::GLOBAL,
602604
);
603605
let scope = self.semantic.current_scope_mut();
@@ -609,7 +611,8 @@ impl<'a> Visitor<'a> for Checker<'a> {
609611
if !self.semantic.scope_id.is_global() {
610612
for name in names {
611613
if let Some((scope_id, binding_id)) = self.semantic.nonlocal(name) {
612-
// Mark the binding as "used".
614+
// Mark the binding as "used", since the `nonlocal` requires an existing
615+
// binding.
613616
self.semantic.add_local_reference(
614617
binding_id,
615618
ExprContext::Load,
@@ -624,7 +627,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
624627
// Add a binding to the current scope.
625628
let binding_id = self.semantic.push_binding(
626629
name.range(),
627-
BindingKind::Nonlocal(scope_id),
630+
BindingKind::Nonlocal(binding_id, scope_id),
628631
BindingFlags::NONLOCAL,
629632
);
630633
let scope = self.semantic.current_scope_mut();

crates/ruff_linter/src/renamer.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ impl Renamer {
125125
let scope_id = scope.get_all(name).find_map(|binding_id| {
126126
let binding = semantic.binding(binding_id);
127127
match binding.kind {
128-
BindingKind::Global => Some(ScopeId::global()),
129-
BindingKind::Nonlocal(symbol_id) => Some(symbol_id),
128+
BindingKind::Global(_) => Some(ScopeId::global()),
129+
BindingKind::Nonlocal(_, scope_id) => Some(scope_id),
130130
_ => None,
131131
}
132132
});
@@ -266,8 +266,8 @@ impl Renamer {
266266
| BindingKind::LoopVar
267267
| BindingKind::ComprehensionVar
268268
| BindingKind::WithItemVar
269-
| BindingKind::Global
270-
| BindingKind::Nonlocal(_)
269+
| BindingKind::Global(_)
270+
| BindingKind::Nonlocal(_, _)
271271
| BindingKind::ClassDefinition(_)
272272
| BindingKind::FunctionDefinition(_)
273273
| BindingKind::Deletion

crates/ruff_linter/src/rules/pandas_vet/helpers.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti
4848
| BindingKind::NamedExprAssignment
4949
| BindingKind::LoopVar
5050
| BindingKind::ComprehensionVar
51-
| BindingKind::Global
52-
| BindingKind::Nonlocal(_) => Resolution::RelevantLocal,
51+
| BindingKind::Global(_)
52+
| BindingKind::Nonlocal(_, _) => Resolution::RelevantLocal,
5353
BindingKind::Import(import)
5454
if matches!(import.qualified_name().segments(), ["pandas"]) =>
5555
{

crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option<Dia
5454
BindingKind::LoopVar => Kind::LoopVar,
5555
BindingKind::ComprehensionVar => Kind::ComprenhensionVar,
5656
BindingKind::WithItemVar => Kind::WithItemVar,
57-
BindingKind::Global => Kind::Global,
58-
BindingKind::Nonlocal(_) => Kind::Nonlocal,
57+
BindingKind::Global(_) => Kind::Global,
58+
BindingKind::Nonlocal(_, _) => Kind::Nonlocal,
5959
BindingKind::ClassDefinition(_) => Kind::ClassDefinition,
6060
BindingKind::FunctionDefinition(_) => Kind::FunctionDefinition,
6161
BindingKind::BoundException => Kind::BoundException,

crates/ruff_python_semantic/src/binding.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -470,14 +470,14 @@ pub enum BindingKind<'a> {
470470
/// def foo():
471471
/// global x
472472
/// ```
473-
Global,
473+
Global(Option<BindingId>),
474474

475475
/// A binding for a nonlocal variable, like `x` in:
476476
/// ```python
477477
/// def foo():
478478
/// nonlocal x
479479
/// ```
480-
Nonlocal(ScopeId),
480+
Nonlocal(BindingId, ScopeId),
481481

482482
/// A binding for a builtin, like `print` or `bool`.
483483
Builtin,
@@ -670,3 +670,14 @@ impl<'a, 'ast> Imported<'ast> for AnyImport<'a, 'ast> {
670670
}
671671
}
672672
}
673+
674+
#[cfg(test)]
675+
mod tests {
676+
use crate::BindingKind;
677+
678+
#[test]
679+
#[cfg(target_pointer_width = "64")]
680+
fn size() {
681+
assert!(std::mem::size_of::<BindingKind>() <= 24);
682+
}
683+
}

crates/ruff_python_semantic/src/model.rs

+17
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,23 @@ impl<'a> SemanticModel<'a> {
540540
return ReadResult::Resolved(binding_id);
541541
}
542542

543+
BindingKind::Global(Some(binding_id))
544+
| BindingKind::Nonlocal(binding_id, _) => {
545+
// Mark the shadowed binding as used.
546+
let reference_id = self.resolved_references.push(
547+
self.scope_id,
548+
self.node_id,
549+
ExprContext::Load,
550+
self.flags,
551+
name.range,
552+
);
553+
self.bindings[binding_id].references.push(reference_id);
554+
555+
// Treat it as resolved.
556+
self.resolved_names.insert(name.into(), binding_id);
557+
return ReadResult::Resolved(binding_id);
558+
}
559+
543560
_ => {
544561
// Otherwise, treat it as resolved.
545562
self.resolved_names.insert(name.into(), binding_id);

0 commit comments

Comments
 (0)