diff options
-rw-r--r-- | doc/whatsnew/2/2.15/index.rst | 4 | ||||
-rw-r--r-- | pylint/checkers/base/basic_error_checker.py | 9 | ||||
-rw-r--r-- | tests/functional/n/nonlocal_without_binding.py | 19 | ||||
-rw-r--r-- | tests/functional/n/nonlocal_without_binding.txt | 7 |
4 files changed, 32 insertions, 7 deletions
diff --git a/doc/whatsnew/2/2.15/index.rst b/doc/whatsnew/2/2.15/index.rst index dc358063f..ff67eeae6 100644 --- a/doc/whatsnew/2/2.15/index.rst +++ b/doc/whatsnew/2/2.15/index.rst @@ -65,6 +65,10 @@ False negatives fixed Closes #6643 +* Emit ``nonlocal-without-binding`` when a nonlocal name has been assigned at a later point in the same scope. + + Closes #6883 + * Rename ``unhashable-dict-key`` to ``unhashable-member`` and emit when creating sets and dicts, not just when accessing dicts. diff --git a/pylint/checkers/base/basic_error_checker.py b/pylint/checkers/base/basic_error_checker.py index 8ad9e79d0..99f94645d 100644 --- a/pylint/checkers/base/basic_error_checker.py +++ b/pylint/checkers/base/basic_error_checker.py @@ -404,7 +404,10 @@ class BasicErrorChecker(_BasicChecker): self.add_message("nonlocal-without-binding", args=(name,), node=node) return - if name not in current_scope.locals: + # Search for `name` in the parent scope if: + # `current_scope` is the same scope in which the `nonlocal` name is declared + # or `name` is not in `current_scope.locals`. + if current_scope is node.scope() or name not in current_scope.locals: current_scope = current_scope.parent.scope() continue @@ -412,7 +415,9 @@ class BasicErrorChecker(_BasicChecker): return if not isinstance(current_scope, nodes.FunctionDef): - self.add_message("nonlocal-without-binding", args=(name,), node=node) + self.add_message( + "nonlocal-without-binding", args=(name,), node=node, confidence=HIGH + ) @utils.only_required_for_messages("nonlocal-without-binding") def visit_nonlocal(self, node: nodes.Nonlocal) -> None: diff --git a/tests/functional/n/nonlocal_without_binding.py b/tests/functional/n/nonlocal_without_binding.py index de4675eca..2b10db1a3 100644 --- a/tests/functional/n/nonlocal_without_binding.py +++ b/tests/functional/n/nonlocal_without_binding.py @@ -2,30 +2,45 @@ # pylint: disable=missing-docstring,invalid-name,unused-variable, useless-object-inheritance # pylint: disable=too-few-public-methods + def test(): def parent(): a = 42 + def stuff(): nonlocal a c = 24 + def parent2(): a = 42 + def stuff(): def other_stuff(): nonlocal a nonlocal c + b = 42 + + def func(): def other_func(): nonlocal b # [nonlocal-without-binding] + # Case where `nonlocal-without-binding` was not emitted when + # the nonlocal name was assigned later in the same scope. + # https://github.com/PyCQA/pylint/issues/6883 + def other_func2(): + nonlocal c # [nonlocal-without-binding] + c = 1 + + class SomeClass(object): - nonlocal x # [nonlocal-without-binding] + nonlocal x # [nonlocal-without-binding] def func(self): - nonlocal some_attr # [nonlocal-without-binding] + nonlocal some_attr # [nonlocal-without-binding] def func2(): diff --git a/tests/functional/n/nonlocal_without_binding.txt b/tests/functional/n/nonlocal_without_binding.txt index dc48e7539..039d07b52 100644 --- a/tests/functional/n/nonlocal_without_binding.txt +++ b/tests/functional/n/nonlocal_without_binding.txt @@ -1,3 +1,4 @@ -nonlocal-without-binding:22:8:22:18:func.other_func:nonlocal name b found without binding:UNDEFINED -nonlocal-without-binding:25:4:25:14:SomeClass:nonlocal name x found without binding:UNDEFINED -nonlocal-without-binding:28:8:28:26:SomeClass.func:nonlocal name some_attr found without binding:UNDEFINED +nonlocal-without-binding:29:8:29:18:func.other_func:nonlocal name b found without binding:HIGH +nonlocal-without-binding:35:8:35:18:func.other_func2:nonlocal name c found without binding:HIGH +nonlocal-without-binding:40:4:40:14:SomeClass:nonlocal name x found without binding:HIGH +nonlocal-without-binding:43:8:43:26:SomeClass.func:nonlocal name some_attr found without binding:HIGH |