summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Walls <jacobtylerwalls@gmail.com>2022-04-06 13:17:46 -0400
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2022-04-06 21:15:17 +0200
commita03b6e77bef920a7c72be9f3e2c2babddecd2fd2 (patch)
tree9f06689c45ca2d6291931047af7acfce19045e0c
parent074131312977fbd423fe4faff004d4fa8dbba4e5 (diff)
downloadpylint-git-a03b6e77bef920a7c72be9f3e2c2babddecd2fd2.tar.gz
Prevent `used-before-assignment` for assignment via nonlocal after type annotation (#6185)
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.13.rst5
-rw-r--r--pylint/checkers/variables.py14
-rw-r--r--tests/functional/u/used/used_before_assignment_nonlocal.py31
-rw-r--r--tests/functional/u/used/used_before_assignment_nonlocal.txt1
5 files changed, 56 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index e477660c4..602ac4113 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -40,6 +40,11 @@ Release date: TBA
* functions & classes which contain both a docstring and an ellipsis.
* A body which contains an ellipsis ``nodes.Expr`` node & at least one other statement.
+* Fix false positive for ``used-before-assignment`` for assignments taking place via
+ nonlocal declarations after an earlier type annotation.
+
+ Closes #5394
+
* Fix crash for ``redefined-slots-in-subclass`` when the type of the slot is not a const or a string.
Closes #6100
diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst
index d8d105f3c..73e898d24 100644
--- a/doc/whatsnew/2.13.rst
+++ b/doc/whatsnew/2.13.rst
@@ -576,6 +576,11 @@ Other Changes
Closes #5803
+* Fix false positive for ``used-before-assignment`` for assignments taking place via
+ nonlocal declarations after an earlier type annotation.
+
+ Closes #5394
+
* Fix false positive for 'nonexistent-operator' when repeated '-' are
separated (e.g. by parens).
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index dcd59b39b..be30c0579 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -2074,6 +2074,20 @@ class VariablesChecker(BaseChecker):
parent = node
while parent is not defstmt_frame.parent:
parent_scope = parent.scope()
+
+ # Find out if any nonlocals receive values in nested functions
+ for inner_func in parent_scope.nodes_of_class(nodes.FunctionDef):
+ if inner_func is parent_scope:
+ continue
+ if any(
+ node.name in nl.names
+ for nl in inner_func.nodes_of_class(nodes.Nonlocal)
+ ) and any(
+ node.name == an.name
+ for an in inner_func.nodes_of_class(nodes.AssignName)
+ ):
+ return False
+
local_refs = parent_scope.locals.get(node.name, [])
for ref_node in local_refs:
# If local ref is in the same frame as our node, but on a later lineno
diff --git a/tests/functional/u/used/used_before_assignment_nonlocal.py b/tests/functional/u/used/used_before_assignment_nonlocal.py
index 5cbdd510c..aa0b654b7 100644
--- a/tests/functional/u/used/used_before_assignment_nonlocal.py
+++ b/tests/functional/u/used/used_before_assignment_nonlocal.py
@@ -58,3 +58,34 @@ def nonlocal_in_ifexp():
on_click(True)
nonlocal_in_ifexp()
+
+
+def type_annotation_only_gets_value_via_nonlocal():
+ """https://github.com/PyCQA/pylint/issues/5394"""
+ some_num: int
+ def inner():
+ nonlocal some_num
+ some_num = 5
+ inner()
+ print(some_num)
+
+
+def type_annotation_only_gets_value_via_nonlocal_nested():
+ """Similar, with nesting"""
+ some_num: int
+ def inner():
+ def inner2():
+ nonlocal some_num
+ some_num = 5
+ inner2()
+ inner()
+ print(some_num)
+
+
+def type_annotation_never_gets_value_despite_nonlocal():
+ """Type annotation lacks a value despite nonlocal declaration"""
+ some_num: int
+ def inner():
+ nonlocal some_num
+ inner()
+ print(some_num) # [used-before-assignment]
diff --git a/tests/functional/u/used/used_before_assignment_nonlocal.txt b/tests/functional/u/used/used_before_assignment_nonlocal.txt
index 90525457e..f3e873315 100644
--- a/tests/functional/u/used/used_before_assignment_nonlocal.txt
+++ b/tests/functional/u/used/used_before_assignment_nonlocal.txt
@@ -4,3 +4,4 @@ used-before-assignment:30:20:30:30:test_fail3:Using variable 'test_fail4' before
used-before-assignment:34:22:34:32:test_fail4:Using variable 'test_fail5' before assignment:HIGH
used-before-assignment:34:44:34:53:test_fail4:Using variable 'undefined' before assignment:HIGH
used-before-assignment:40:18:40:28:test_fail5:Using variable 'undefined1' before assignment:HIGH
+used-before-assignment:91:10:91:18:type_annotation_never_gets_value_despite_nonlocal:Using variable 'some_num' before assignment:HIGH