From f17282d61582962c002df3a19e39ed1fd45fa3ae Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 7 Jun 2023 18:25:36 -0400 Subject: [PATCH] Skip class scopes when resolving nonlocal references (#4943) --- .../test/fixtures/pyflakes/F841_0.py | 19 +++++++++++++++++++ crates/ruff/src/checkers/ast/mod.rs | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F841_0.py b/crates/ruff/resources/test/fixtures/pyflakes/F841_0.py index c0e33502d6930..94bc0b6ca507d 100644 --- a/crates/ruff/resources/test/fixtures/pyflakes/F841_0.py +++ b/crates/ruff/resources/test/fixtures/pyflakes/F841_0.py @@ -126,3 +126,22 @@ def f(x: int): def f(): if any((key := (value := x)) for x in ["ok"]): print(key) + + +def f() -> None: + is_connected = False + + class Foo: + @property + def is_connected(self): + nonlocal is_connected + return is_connected + + def do_thing(self): + # This should resolve to the `is_connected` in the function scope. + nonlocal is_connected + print(is_connected) + + obj = Foo() + obj.do_thing() + diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index e2892a3d49ada..32c6c61fdc919 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -291,14 +291,14 @@ where } // Mark the binding in the defining scopes as used too. (Skip the global scope - // and the current scope.) + // and the current scope, and, per standard resolution rules, any class scopes.) for (name, range) in names.iter().zip(ranges.iter()) { let binding_id = self .semantic_model .scopes .ancestors(self.semantic_model.scope_id) .skip(1) - .take_while(|scope| !scope.kind.is_module()) + .filter(|scope| !(scope.kind.is_module() || scope.kind.is_class())) .find_map(|scope| scope.get(name.as_str())); if let Some(binding_id) = binding_id {