diff options
author | Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> | 2021-10-23 20:54:39 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-23 20:54:39 +0200 |
commit | 8eb7ff153e951f69b4d1ff3cbddc367c797c0aa4 (patch) | |
tree | 1370821adf92646fe35454935d09bc79fb872964 | |
parent | 91f525f276ce73c436949a6128709f2dea99e64e (diff) | |
download | pylint-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.py | 36 | ||||
-rw-r--r-- | tests/functional/u/undefined/undefined_variable.py | 59 | ||||
-rw-r--r-- | tests/functional/u/undefined/undefined_variable.txt | 70 | ||||
-rw-r--r-- | tests/functional/u/undefined/undefined_variable_py38.py | 17 | ||||
-rw-r--r-- | tests/functional/u/undefined/undefined_variable_py38.rc | 2 | ||||
-rw-r--r-- | tests/functional/u/undefined/undefined_variable_py38.txt | 1 |
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 |