Skip to content

Fix regression for _is_only_type_assignment #5163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Oct 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1555,18 +1555,42 @@ def _is_variable_violation(

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

def _ignore_class_scope(self, node):
Expand Down
59 changes: 56 additions & 3 deletions tests/functional/u/undefined/undefined_variable.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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)
70 changes: 36 additions & 34 deletions tests/functional/u/undefined/undefined_variable.txt
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions tests/functional/u/undefined/undefined_variable_py38.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions tests/functional/u/undefined/undefined_variable_py38.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.8
1 change: 1 addition & 0 deletions tests/functional/u/undefined/undefined_variable_py38.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
undefined-variable:16:15:typing_and_self_referncing_assignment_expression:Undefined variable 'var':HIGH