summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2021-10-23 20:54:39 +0200
committerGitHub <noreply@github.com>2021-10-23 20:54:39 +0200
commit8eb7ff153e951f69b4d1ff3cbddc367c797c0aa4 (patch)
tree1370821adf92646fe35454935d09bc79fb872964
parent91f525f276ce73c436949a6128709f2dea99e64e (diff)
downloadpylint-git-8eb7ff153e951f69b4d1ff3cbddc367c797c0aa4.tar.gz
Fix regression for ``_is_only_type_assignment`` (#5163)
* Fix regression for ``_is_only_type_assignment`` This closes #5162 * Remove references to `name` * Better tests for assignment * Move walrus tests to different file * Fix tests with incorrect typing * Update typing of `defstmt` Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-rw-r--r--pylint/checkers/variables.py36
-rw-r--r--tests/functional/u/undefined/undefined_variable.py59
-rw-r--r--tests/functional/u/undefined/undefined_variable.txt70
-rw-r--r--tests/functional/u/undefined/undefined_variable_py38.py17
-rw-r--r--tests/functional/u/undefined/undefined_variable_py38.rc2
-rw-r--r--tests/functional/u/undefined/undefined_variable_py38.txt1
6 files changed, 142 insertions, 43 deletions
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index c8d1f4f5d..bbcf855a3 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -1571,18 +1571,42 @@ class VariablesChecker(BaseChecker):
return maybee0601, annotation_return, use_outer_definition
+ # pylint: disable-next=fixme
+ # TODO: The typing of `NodeNG.statement()` in astroid is non-specific
+ # After this has been updated the typing of `defstmt` should reflect this
+ # See: https://github.com/PyCQA/astroid/pull/1217
@staticmethod
- def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool:
+ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.NodeNG) -> bool:
"""Check if variable only gets assigned a type and never a value"""
if not isinstance(defstmt, nodes.AnnAssign) or defstmt.value:
return False
- for ref_node in node.scope().locals[node.name][1:]:
- if ref_node.lineno < node.lineno:
- if not (
- isinstance(ref_node.parent, nodes.AnnAssign)
- and ref_node.parent.value
+
+ defstmt_frame = defstmt.frame()
+ node_frame = node.frame()
+
+ parent = node
+ while parent is not defstmt_frame.parent:
+ parent_scope = parent.scope()
+ 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
+ # we don't actually care about this local ref.
+ # Local refs are ordered, so we break.
+ # print(var)
+ # var = 1 # <- irrelevant
+ if defstmt_frame == node_frame and not ref_node.lineno < node.lineno:
+ break
+
+ # If the parent of the local refence is anything but a AnnAssign
+ # Or if the AnnAssign adds a value the variable will now have a value
+ # var = 1 # OR
+ # var: int = 1
+ if (
+ not isinstance(ref_node.parent, nodes.AnnAssign)
+ or ref_node.parent.value
):
return False
+ parent = parent_scope.parent
return True
@staticmethod
diff --git a/tests/functional/u/undefined/undefined_variable.py b/tests/functional/u/undefined/undefined_variable.py
index 1f9988376..f4f22e523 100644
--- a/tests/functional/u/undefined/undefined_variable.py
+++ b/tests/functional/u/undefined/undefined_variable.py
@@ -1,9 +1,10 @@
-# pylint: disable=missing-docstring, multiple-statements, useless-object-inheritance,import-outside-toplevel
-# pylint: disable=too-few-public-methods, no-init, no-self-use,bare-except,broad-except, import-error
+# pylint: disable=missing-docstring, multiple-statements, useless-object-inheritance, import-outside-toplevel
+# pylint: disable=too-few-public-methods, no-init, no-self-use, bare-except, broad-except
+# pylint: disable=using-constant-test, import-error, global-variable-not-assigned, unnecessary-comprehension
from __future__ import print_function
# pylint: disable=wrong-import-position
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, List
DEFINED = 1
@@ -372,3 +373,55 @@ def value_assignment_from_iterator():
variable: int
for variable in (1, 2):
print(variable)
+
+
+GLOBAL_VAR: int
+GLOBAL_VAR_TWO: int
+
+def global_var_mixed_assignment():
+ """One global variable never gets assigned a value"""
+ global GLOBAL_VAR
+ print(GLOBAL_VAR) # [undefined-variable]
+ global GLOBAL_VAR_TWO
+ print(GLOBAL_VAR_TWO)
+
+GLOBAL_VAR_TWO = 2
+
+
+GLOBAL_VAR: int
+GLOBAL_VAR_TWO: int
+
+
+def assignment_in_comprehension():
+ """A previously typed variables gets used in a comprehension. Don't crash!"""
+ some_list: List[int]
+ some_list = [1, 2, 3]
+ some_list = [i * 2 for i in some_list]
+
+
+def decorator_returning_function():
+ """A decorator that returns a wrapper function with decoupled typing"""
+ def wrapper_with_decoupled_typing():
+ print(var)
+
+ var: int
+ var = 2
+ return wrapper_with_decoupled_typing
+
+
+def decorator_returning_incorrect_function():
+ """A decorator that returns a wrapper function with decoupled typing"""
+ def wrapper_with_type_and_no_value():
+ print(var) # [undefined-variable]
+
+ var: int
+ return wrapper_with_type_and_no_value
+
+
+def typing_and_value_assignment_with_tuple_assignment():
+ """The typed variables get assigned with a tuple assignment"""
+ var_one: int
+ var_two: int
+ var_one, var_two = 1, 1
+ print(var_one)
+ print(var_two)
diff --git a/tests/functional/u/undefined/undefined_variable.txt b/tests/functional/u/undefined/undefined_variable.txt
index ad5f73b09..23bb4ff44 100644
--- a/tests/functional/u/undefined/undefined_variable.txt
+++ b/tests/functional/u/undefined/undefined_variable.txt
@@ -1,34 +1,36 @@
-undefined-variable:11:19::Undefined variable 'unknown':HIGH
-undefined-variable:17:10:in_method:Undefined variable 'nomoreknown':HIGH
-undefined-variable:20:19::Undefined variable '__revision__':HIGH
-undefined-variable:22:8::Undefined variable '__revision__':HIGH
-undefined-variable:26:29:bad_default:Undefined variable 'unknown2':HIGH
-undefined-variable:29:10:bad_default:Undefined variable 'xxxx':HIGH
-undefined-variable:30:4:bad_default:Undefined variable 'augvar':HIGH
-undefined-variable:31:8:bad_default:Undefined variable 'vardel':HIGH
-undefined-variable:33:19:<lambda>:Undefined variable 'doesnotexist':HIGH
-undefined-variable:34:23:<lambda>:Undefined variable 'z':HIGH
-used-before-assignment:42:4::Using variable 'POUETT' before assignment:HIGH
-used-before-assignment:55:4::Using variable 'PLOUF' before assignment:HIGH
-used-before-assignment:64:11:if_branch_test:Using variable 'xxx' before assignment:HIGH
-used-before-assignment:90:23:test_arguments:Using variable 'TestClass' before assignment:HIGH
-used-before-assignment:94:16:TestClass:Using variable 'Ancestor' before assignment:HIGH
-used-before-assignment:97:26:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment:HIGH
-used-before-assignment:104:36:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment:HIGH
-undefined-variable:118:10:Self:Undefined variable 'Self':HIGH
-undefined-variable:134:7::Undefined variable 'BAT':HIGH
-used-before-assignment:145:31:KeywordArgument.test1:Using variable 'enabled' before assignment:HIGH
-undefined-variable:148:32:KeywordArgument.test2:Undefined variable 'disabled':HIGH
-undefined-variable:153:22:KeywordArgument.<lambda>:Undefined variable 'arg':HIGH
-undefined-variable:165:4::Undefined variable 'unicode_2':HIGH
-undefined-variable:170:4::Undefined variable 'unicode_3':HIGH
-undefined-variable:225:25:LambdaClass4.<lambda>:Undefined variable 'LambdaClass4':HIGH
-undefined-variable:233:25:LambdaClass5.<lambda>:Undefined variable 'LambdaClass5':HIGH
-used-before-assignment:254:26:func_should_fail:Using variable 'datetime' before assignment:HIGH
-undefined-variable:281:18:not_using_loop_variable_accordingly:Undefined variable 'iteree':HIGH
-undefined-variable:292:27:undefined_annotation:Undefined variable 'x':HIGH
-used-before-assignment:293:7:undefined_annotation:Using variable 'x' before assignment:HIGH
-undefined-variable:323:11:decorated3:Undefined variable 'x':HIGH
-undefined-variable:328:19:decorated4:Undefined variable 'y':HIGH
-undefined-variable:353:10:only_type_assignment:Undefined variable 'variable':HIGH
-undefined-variable:366:10:value_assignment_after_access:Undefined variable 'variable':HIGH
+undefined-variable:12:19::Undefined variable 'unknown':HIGH
+undefined-variable:18:10:in_method:Undefined variable 'nomoreknown':HIGH
+undefined-variable:21:19::Undefined variable '__revision__':HIGH
+undefined-variable:23:8::Undefined variable '__revision__':HIGH
+undefined-variable:27:29:bad_default:Undefined variable 'unknown2':HIGH
+undefined-variable:30:10:bad_default:Undefined variable 'xxxx':HIGH
+undefined-variable:31:4:bad_default:Undefined variable 'augvar':HIGH
+undefined-variable:32:8:bad_default:Undefined variable 'vardel':HIGH
+undefined-variable:34:19:<lambda>:Undefined variable 'doesnotexist':HIGH
+undefined-variable:35:23:<lambda>:Undefined variable 'z':HIGH
+used-before-assignment:43:4::Using variable 'POUETT' before assignment:HIGH
+used-before-assignment:56:4::Using variable 'PLOUF' before assignment:HIGH
+used-before-assignment:65:11:if_branch_test:Using variable 'xxx' before assignment:HIGH
+used-before-assignment:91:23:test_arguments:Using variable 'TestClass' before assignment:HIGH
+used-before-assignment:95:16:TestClass:Using variable 'Ancestor' before assignment:HIGH
+used-before-assignment:98:26:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment:HIGH
+used-before-assignment:105:36:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment:HIGH
+undefined-variable:119:10:Self:Undefined variable 'Self':HIGH
+undefined-variable:135:7::Undefined variable 'BAT':HIGH
+used-before-assignment:146:31:KeywordArgument.test1:Using variable 'enabled' before assignment:HIGH
+undefined-variable:149:32:KeywordArgument.test2:Undefined variable 'disabled':HIGH
+undefined-variable:154:22:KeywordArgument.<lambda>:Undefined variable 'arg':HIGH
+undefined-variable:166:4::Undefined variable 'unicode_2':HIGH
+undefined-variable:171:4::Undefined variable 'unicode_3':HIGH
+undefined-variable:226:25:LambdaClass4.<lambda>:Undefined variable 'LambdaClass4':HIGH
+undefined-variable:234:25:LambdaClass5.<lambda>:Undefined variable 'LambdaClass5':HIGH
+used-before-assignment:255:26:func_should_fail:Using variable 'datetime' before assignment:HIGH
+undefined-variable:282:18:not_using_loop_variable_accordingly:Undefined variable 'iteree':HIGH
+undefined-variable:293:27:undefined_annotation:Undefined variable 'x':HIGH
+used-before-assignment:294:7:undefined_annotation:Using variable 'x' before assignment:HIGH
+undefined-variable:324:11:decorated3:Undefined variable 'x':HIGH
+undefined-variable:329:19:decorated4:Undefined variable 'y':HIGH
+undefined-variable:354:10:only_type_assignment:Undefined variable 'variable':HIGH
+undefined-variable:367:10:value_assignment_after_access:Undefined variable 'variable':HIGH
+undefined-variable:384:10:global_var_mixed_assignment:Undefined variable 'GLOBAL_VAR':HIGH
+undefined-variable:415:14:decorator_returning_incorrect_function.wrapper_with_type_and_no_value:Undefined variable 'var':HIGH
diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py
new file mode 100644
index 000000000..818423c8e
--- /dev/null
+++ b/tests/functional/u/undefined/undefined_variable_py38.py
@@ -0,0 +1,17 @@
+"""Tests for undefined variable with assignment expressions"""
+# pylint: disable=using-constant-test
+
+# Tests for annotation of variables and potentially undefinition
+
+def typing_and_assignment_expression():
+ """The variable gets assigned in an assignment expression"""
+ var: int
+ if (var := 1 ** 2):
+ print(var)
+
+
+def typing_and_self_referncing_assignment_expression():
+ """The variable gets assigned in an assignment expression that references itself"""
+ var: int
+ if (var := var ** 2): # [undefined-variable]
+ print(var)
diff --git a/tests/functional/u/undefined/undefined_variable_py38.rc b/tests/functional/u/undefined/undefined_variable_py38.rc
new file mode 100644
index 000000000..85fc502b3
--- /dev/null
+++ b/tests/functional/u/undefined/undefined_variable_py38.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.8
diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt
new file mode 100644
index 000000000..96731db52
--- /dev/null
+++ b/tests/functional/u/undefined/undefined_variable_py38.txt
@@ -0,0 +1 @@
+undefined-variable:16:15:typing_and_self_referncing_assignment_expression:Undefined variable 'var':HIGH