Skip to content

Commit

Permalink
Fix #5713: Emit used-before-assignment instead of `undefined-variab…
Browse files Browse the repository at this point in the history
…le` when accessing unused type annotations (#5718)

* Add confidence level HIGH to  `used-before-assignment`
  • Loading branch information
jacobtylerwalls authored Jan 26, 2022
1 parent f85fb8d commit 0fb6a12
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 90 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ Release date: TBA

* Pyreverse - add output in mermaidjs format

* Emit ``used-before-assignment`` instead of ``undefined-variable`` when attempting
to access unused type annotations.

Closes #5713

* ``used-before-assignment`` now considers that assignments in a try block
may not have occurred when the except or finally blocks are executed.

Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ Other Changes
Closes #4798
Closes #5081

* Emit ``used-before-assignment`` instead of ``undefined-variable`` when attempting
to access unused type annotations.

Closes #5713

* Fixed extremely long processing of long lines with comma's.

Closes #5483
Expand Down
9 changes: 8 additions & 1 deletion pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1551,7 +1551,14 @@ def _check_consumer(
)

elif self._is_only_type_assignment(node, defstmt):
self.add_message("undefined-variable", args=node.name, node=node)
if node.scope().locals.get(node.name):
self.add_message(
"used-before-assignment", args=node.name, node=node, confidence=HIGH
)
else:
self.add_message(
"undefined-variable", args=node.name, node=node, confidence=HIGH
)
return (VariableVisitConsumerAction.CONSUME, found_nodes)

elif isinstance(defstmt, nodes.ClassDef):
Expand Down
86 changes: 1 addition & 85 deletions tests/functional/u/undefined/undefined_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from __future__ import print_function

# pylint: disable=wrong-import-position
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING

DEFINED = 1

Expand Down Expand Up @@ -340,40 +340,6 @@ def decorated4(x):
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)


GLOBAL_VAR: int
GLOBAL_VAR_TWO: int
Expand All @@ -390,53 +356,3 @@ def global_var_mixed_assignment():

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)


def nested_class_as_return_annotation():
"""A namedtuple as a class attribute is used as a return annotation
Taken from https://github.com/PyCQA/pylint/issues/5568"""
from collections import namedtuple

class MyObject:
Coords = namedtuple('Point', ['x', 'y'])

def my_method(self) -> Coords:
pass

print(MyObject)
5 changes: 1 addition & 4 deletions tests/functional/u/undefined/undefined_variable.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,4 @@ undefined-variable:293:27:293:28:undefined_annotation:Undefined variable 'x':UND
used-before-assignment:294:7:294:8:undefined_annotation:Using variable 'x' before assignment:UNDEFINED
undefined-variable:324:11:324:12:decorated3:Undefined variable 'x':UNDEFINED
undefined-variable:329:19:329:20:decorated4:Undefined variable 'y':UNDEFINED
undefined-variable:354:10:354:18:only_type_assignment:Undefined variable 'variable':UNDEFINED
undefined-variable:367:10:367:18:value_assignment_after_access:Undefined variable 'variable':UNDEFINED
undefined-variable:384:10:384:20:global_var_mixed_assignment:Undefined variable 'GLOBAL_VAR':UNDEFINED
undefined-variable:415:14:415:17:decorator_returning_incorrect_function.wrapper_with_type_and_no_value:Undefined variable 'var':UNDEFINED
undefined-variable:350:10:350:20:global_var_mixed_assignment:Undefined variable 'GLOBAL_VAR':HIGH
90 changes: 90 additions & 0 deletions tests/functional/u/use/used_before_assignment_type_annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Tests for annotation of variables and potential use before assignment"""
# pylint: disable=too-few-public-methods, global-variable-not-assigned
from collections import namedtuple
from typing import List

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) # [used-before-assignment]


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) # [used-before-assignment]
variable = 1


def value_assignment_from_iterator():
"""The variables gets a value from an iterator"""
variable: int
for variable in (1, 2):
print(variable)


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():
# This emits NameError rather than UnboundLocalError, so
# undefined-variable is okay, even though the traceback refers
# to "free variable 'var' referenced before assignment"
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)


def nested_class_as_return_annotation():
"""A namedtuple as a class attribute is used as a return annotation
Taken from https://github.com/PyCQA/pylint/issues/5568"""
class MyObject:
"""namedtuple as class attribute"""
Coords = namedtuple('Point', ['x', 'y'])

def my_method(self) -> Coords:
"""Return annotation is valid"""
# pylint: disable=unnecessary-pass
pass

print(MyObject)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
used-before-assignment:15:10:15:18:only_type_assignment:Using variable 'variable' before assignment:HIGH
used-before-assignment:28:10:28:18:value_assignment_after_access:Using variable 'variable' before assignment:HIGH
undefined-variable:62:14:62:17:decorator_returning_incorrect_function.wrapper_with_type_and_no_value:Undefined variable 'var':HIGH

0 comments on commit 0fb6a12

Please sign in to comment.