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 https://github.com/astral-sh/ruff/issues/11518.
This commit is contained in:
Charlie Marsh 2024-05-28 14:47:05 -04:00 committed by GitHub
parent 4a305588e9
commit 69d9212817
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 46 additions and 15 deletions

View file

@ -470,14 +470,14 @@ pub enum BindingKind<'a> {
/// def foo():
/// global x
/// ```
Global,
Global(Option<BindingId>),
/// A binding for a nonlocal variable, like `x` in:
/// ```python
/// def foo():
/// nonlocal x
/// ```
Nonlocal(ScopeId),
Nonlocal(BindingId, ScopeId),
/// A binding for a builtin, like `print` or `bool`.
Builtin,
@ -670,3 +670,14 @@ impl<'a, 'ast> Imported<'ast> for AnyImport<'a, 'ast> {
}
}
}
#[cfg(test)]
mod tests {
use crate::BindingKind;
#[test]
#[cfg(target_pointer_width = "64")]
fn size() {
assert!(std::mem::size_of::<BindingKind>() <= 24);
}
}

View file

@ -540,6 +540,23 @@ impl<'a> SemanticModel<'a> {
return ReadResult::Resolved(binding_id);
}
BindingKind::Global(Some(binding_id))
| BindingKind::Nonlocal(binding_id, _) => {
// Mark the shadowed binding as used.
let reference_id = self.resolved_references.push(
self.scope_id,
self.node_id,
ExprContext::Load,
self.flags,
name.range,
);
self.bindings[binding_id].references.push(reference_id);
// Treat it as resolved.
self.resolved_names.insert(name.into(), binding_id);
return ReadResult::Resolved(binding_id);
}
_ => {
// Otherwise, treat it as resolved.
self.resolved_names.insert(name.into(), binding_id);