summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/whatsnew/2/2.15/index.rst4
-rw-r--r--pylint/checkers/base/basic_error_checker.py9
-rw-r--r--tests/functional/n/nonlocal_without_binding.py19
-rw-r--r--tests/functional/n/nonlocal_without_binding.txt7
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