summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2021-10-15 13:49:58 +0200
committerGitHub <noreply@github.com>2021-10-15 13:49:58 +0200
commitb1c4735d2897c55e16c5c14b3926ba4b676df8c7 (patch)
tree43e1f97b4f579016070731d9323efc4abf34e367
parent9ae82580ec70058a6a3f70ccdeb97a2b1ed4df3d (diff)
downloadpylint-git-b1c4735d2897c55e16c5c14b3926ba4b676df8c7.tar.gz
Make ``undefined-variable`` flag type annotation without value assignment (#5158)
Closes #5140 Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.12.rst5
-rw-r--r--pylint/checkers/variables.py18
-rw-r--r--tests/functional/u/undefined/undefined_variable.py34
-rw-r--r--tests/functional/u/undefined/undefined_variable.txt66
5 files changed, 96 insertions, 32 deletions
diff --git a/ChangeLog b/ChangeLog
index e4a957ac5..dee3d32fb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -21,6 +21,11 @@ Release date: TBA
* Added support for ``ModuleNotFoundError`` (``import-error`` and ``no-name-in-module``).
``ModuleNotFoundError`` inherits from ``ImportError`` and was added in Python ``3.6``
+* ``undefined-variable`` now correctly flags variables which only receive a type annotations
+ and never get assigned a value
+
+ Closes #5140
+
* Improve and flatten ``unused-wildcard-import`` message
Closes #3859
diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst
index 68a3dcac4..ec4817eed 100644
--- a/doc/whatsnew/2.12.rst
+++ b/doc/whatsnew/2.12.rst
@@ -52,3 +52,8 @@ Other Changes
* In length checker, ``len-as-condition`` has been renamed as
``use-implicit-booleaness-not-len`` in order to be consistent with
``use-implicit-booleaness-not-comparison``.
+
+* ``undefined-variable`` now correctly flags variables which only receive a type annotations
+ and never get assigned a value
+
+ Closes #5140
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index b4027d602..44044f76b 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -1186,6 +1186,8 @@ class VariablesChecker(BaseChecker):
)
elif current_consumer.scope_type == "lambda":
self.add_message("undefined-variable", node=node, args=name)
+ elif self._is_only_type_assignment(node, name, defstmt):
+ self.add_message("undefined-variable", node=node, args=name)
current_consumer.mark_as_consumed(name, found_nodes)
# check it's not a loop variable used outside the loop
@@ -1548,6 +1550,22 @@ class VariablesChecker(BaseChecker):
return maybee0601, annotation_return, use_outer_definition
+ @staticmethod
+ def _is_only_type_assignment(
+ node: nodes.Name, name: str, defstmt: nodes.Statement
+ ) -> 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[name][1:]:
+ if ref_node.lineno < node.lineno:
+ if not (
+ isinstance(ref_node.parent, nodes.AnnAssign)
+ and ref_node.parent.value
+ ):
+ return False
+ return True
+
def _ignore_class_scope(self, node):
"""
Return True if the node is in a local class scope, as an assignment.
diff --git a/tests/functional/u/undefined/undefined_variable.py b/tests/functional/u/undefined/undefined_variable.py
index 23cf1a31b..1f9988376 100644
--- a/tests/functional/u/undefined/undefined_variable.py
+++ b/tests/functional/u/undefined/undefined_variable.py
@@ -338,3 +338,37 @@ if TYPE_CHECKING:
else:
from types import GenericAlias
object().__class_getitem__ = classmethod(GenericAlias)
+
+# Tests for annotation of variables and potentially undefinition
+
+def value_and_type_assignment():
+ """The variable assigned a value and type"""
+ variable: int = 2
+ print(variable)
+
+
+def only_type_assignment():
+ """The variable never gets assigned a value"""
+ variable: int
+ print(variable) # [undefined-variable]
+
+
+def both_type_and_value_assignment():
+ """The variable first gets a type and subsequently a value"""
+ variable: int
+ variable = 1
+ print(variable)
+
+
+def value_assignment_after_access():
+ """The variable gets a value after it has been accessed"""
+ variable: int
+ print(variable) # [undefined-variable]
+ variable = 1
+
+
+def value_assignment_from_iterator():
+ """The variables gets a value from an iterator"""
+ variable: int
+ for variable in (1, 2):
+ print(variable)
diff --git a/tests/functional/u/undefined/undefined_variable.txt b/tests/functional/u/undefined/undefined_variable.txt
index a6db029bb..ad5f73b09 100644
--- a/tests/functional/u/undefined/undefined_variable.txt
+++ b/tests/functional/u/undefined/undefined_variable.txt
@@ -1,32 +1,34 @@
-undefined-variable:11:19::Undefined variable 'unknown'
-undefined-variable:17:10:in_method:Undefined variable 'nomoreknown'
-undefined-variable:20:19::Undefined variable '__revision__'
-undefined-variable:22:8::Undefined variable '__revision__'
-undefined-variable:26:29:bad_default:Undefined variable 'unknown2'
-undefined-variable:29:10:bad_default:Undefined variable 'xxxx'
-undefined-variable:30:4:bad_default:Undefined variable 'augvar'
-undefined-variable:31:8:bad_default:Undefined variable 'vardel'
-undefined-variable:33:19:<lambda>:Undefined variable 'doesnotexist'
-undefined-variable:34:23:<lambda>:Undefined variable 'z'
-used-before-assignment:42:4::Using variable 'POUETT' before assignment
-used-before-assignment:55:4::Using variable 'PLOUF' before assignment
-used-before-assignment:64:11:if_branch_test:Using variable 'xxx' before assignment
-used-before-assignment:90:23:test_arguments:Using variable 'TestClass' before assignment
-used-before-assignment:94:16:TestClass:Using variable 'Ancestor' before assignment
-used-before-assignment:97:26:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment
-used-before-assignment:104:36:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment
-undefined-variable:118:10:Self:Undefined variable 'Self'
-undefined-variable:134:7::Undefined variable 'BAT'
-used-before-assignment:145:31:KeywordArgument.test1:Using variable 'enabled' before assignment
-undefined-variable:148:32:KeywordArgument.test2:Undefined variable 'disabled'
-undefined-variable:153:22:KeywordArgument.<lambda>:Undefined variable 'arg'
-undefined-variable:165:4::Undefined variable 'unicode_2'
-undefined-variable:170:4::Undefined variable 'unicode_3'
-undefined-variable:225:25:LambdaClass4.<lambda>:Undefined variable 'LambdaClass4'
-undefined-variable:233:25:LambdaClass5.<lambda>:Undefined variable 'LambdaClass5'
-used-before-assignment:254:26:func_should_fail:Using variable 'datetime' before assignment
-undefined-variable:281:18:not_using_loop_variable_accordingly:Undefined variable 'iteree'
-undefined-variable:292:27:undefined_annotation:Undefined variable 'x'
-used-before-assignment:293:7:undefined_annotation:Using variable 'x' before assignment
-undefined-variable:323:11:decorated3:Undefined variable 'x'
-undefined-variable:328:19:decorated4:Undefined variable 'y'
+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