Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,8 @@ def f():
def f():
global x
print(f"{x=}")


# surprisingly still an error, global in module scope
x = None
global x
6 changes: 5 additions & 1 deletion crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2057,8 +2057,12 @@ impl<'a> Visitor<'a> for Checker<'a> {
}

impl<'a> Checker<'a> {
/// Visit a [`Module`]. Returns `true` if the module contains a module-level docstring.
/// Visit a [`Module`].
fn visit_module(&mut self, python_ast: &'a Suite) {
// Extract any global bindings from the module body.
if let Some(globals) = Globals::from_body(python_ast) {
self.semantic.set_globals(globals);
}
analyze::module(python_ast, self);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,11 @@ load_before_global_declaration.py:113:14: PLE0118 Name `x` is used prior to glob
| ^ PLE0118
114 | global x
|

load_before_global_declaration.py:162:1: PLE0118 Name `x` is used prior to global declaration on line 163
|
161 | # surprisingly still an error, global in module scope
162 | x = None
| ^ PLE0118
163 | global x
|
60 changes: 42 additions & 18 deletions crates/ruff_python_semantic/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1503,24 +1503,48 @@ impl<'a> SemanticModel<'a> {

/// Set the [`Globals`] for the current [`Scope`].
pub fn set_globals(&mut self, globals: Globals<'a>) {
// If any global bindings don't already exist in the global scope, add them.
for (name, range) in globals.iter() {
if self
.global_scope()
.get(name)
.is_none_or(|binding_id| self.bindings[binding_id].is_unbound())
{
let id = self.bindings.push(Binding {
kind: BindingKind::Assignment,
range: *range,
references: Vec::new(),
scope: ScopeId::global(),
source: self.node_id,
context: self.execution_context(),
exceptions: self.exceptions(),
flags: BindingFlags::empty(),
});
self.global_scope_mut().add(name, id);
// If any global bindings don't already exist in the global scope, add them, unless we are
// also in the global scope, where we don't want these to count as definitions for rules
// like `undefined-name` (F821). For example, adding bindings in the top-level scope causes
// a false negative in cases like this:
//
// ```python
// global x
//
// def f():
// print(x) # F821 false negative
// ```
//
// On the other hand, failing to add bindings in non-top-level scopes causes false
// positives:
//
// ```python
// def f():
// global foo
// import foo
//
// def g():
// foo.is_used() # F821 false positive
// ```
if !self.at_top_level() {
for (name, range) in globals.iter() {
if self
.global_scope()
.get(name)
.is_none_or(|binding_id| self.bindings[binding_id].is_unbound())
{
let id = self.bindings.push(Binding {
kind: BindingKind::Assignment,
range: *range,
references: Vec::new(),
scope: ScopeId::global(),
source: self.node_id,
context: self.execution_context(),
exceptions: self.exceptions(),
flags: BindingFlags::empty(),
});
self.global_scope_mut().add(name, id);
}
}
}

Expand Down
Loading