From b1c4735d2897c55e16c5c14b3926ba4b676df8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 15 Oct 2021 13:49:58 +0200 Subject: Make ``undefined-variable`` flag type annotation without value assignment (#5158) Closes #5140 Co-authored-by: Pierre Sassoulas --- ChangeLog | 5 ++ doc/whatsnew/2.12.rst | 5 ++ pylint/checkers/variables.py | 18 ++++++ tests/functional/u/undefined/undefined_variable.py | 34 +++++++++++ .../functional/u/undefined/undefined_variable.txt | 66 +++++++++++----------- 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::Undefined variable 'doesnotexist' -undefined-variable:34:23::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.:Undefined variable 'arg' -undefined-variable:165:4::Undefined variable 'unicode_2' -undefined-variable:170:4::Undefined variable 'unicode_3' -undefined-variable:225:25:LambdaClass4.:Undefined variable 'LambdaClass4' -undefined-variable:233:25:LambdaClass5.: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::Undefined variable 'doesnotexist':HIGH +undefined-variable:34:23::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.: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.:Undefined variable 'LambdaClass4':HIGH +undefined-variable:233:25:LambdaClass5.: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 -- cgit v1.2.1