Skip to content

Commit

Permalink
Make used-before-assignment consider classes in method annotation…
Browse files Browse the repository at this point in the history
… and defaults

This closes pylint-dev#3771
  • Loading branch information
DanielNoord committed Oct 18, 2021
1 parent 1daee40 commit 0f87c0e
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 3 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ Release date: TBA

Closes #4031

* ``used-before-assignment`` now correctly considers references to classes as type annotation
or default values in first-level methods

Closes #3771

* Fix bug with importing namespace packages with relative imports

Closes #2967 and #5131
Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,8 @@ Other Changes
used in metaclass declarations

Closes #4031

* ``used-before-assignment`` now correctly considers references to classes as type annotation
or default values in first-level methods

Closes #3771
30 changes: 30 additions & 0 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,11 @@ def visit_name(self, node: nodes.Name) -> None:
)
elif self._is_only_type_assignment(node, defstmt):
self.add_message("undefined-variable", node=node, args=node.name)
elif self._is_first_level_self_referring_annotation(node, defstmt):
self.add_message(
"used-before-assignment", node=node, args=node.name
)
break

current_consumer.mark_as_consumed(node.name, found_nodes)
# check it's not a loop variable used outside the loop
Expand Down Expand Up @@ -1569,6 +1574,31 @@ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool
return False
return True

@staticmethod
def _is_first_level_self_referring_annotation(
node: nodes.Name, defstmt: nodes.Statement
) -> bool:
"""Check if a method annotation refers to its own class on the first level.
This schould be done with quotes:
def MyClass():
def method(parameter: "MyClass"):
pass
def other_method(self) -> bool:
def inner_method(self, other: MyClass) -> bool:
pass
pass
"""
if isinstance(defstmt, nodes.ClassDef) and node.frame().parent == defstmt:
# Check if used as type annotation
if isinstance(node.parent, nodes.Arguments):
return True
# Check if used as default value by calling the class
if isinstance(node.parent, nodes.Call) and isinstance(
node.parent.parent, nodes.Arguments
):
return True
return False

def _ignore_class_scope(self, node):
"""
Return True if the node is in a local class scope, as an assignment.
Expand Down
20 changes: 19 additions & 1 deletion tests/functional/u/use/used_before_assignement.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
"""pylint doesn't see the NameError in this module"""
#pylint: disable=consider-using-f-string
#pylint: disable=consider-using-f-string, missing-function-docstring
__revision__ = None

MSG = "hello %s" % MSG # [used-before-assignment]

MSG2 = ("hello %s" %
MSG2) # [used-before-assignment]


class MyClass:
"""Type annotation or default values for first level methods can't refer to their own class"""
def incorrect_method(self, other: MyClass) -> bool: # [used-before-assignment]
return self == other

def second_incorrect_method(self, other = MyClass()) -> bool: # [used-before-assignment]
return self == other

def correct_method(self, other: "MyClass") -> bool:
return self == other

def second_correct_method(self) -> bool:
def inner_method(self, other: MyClass) -> bool:
return self == other

return inner_method(self, MyClass())
6 changes: 4 additions & 2 deletions tests/functional/u/use/used_before_assignement.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
used-before-assignment:5:19::Using variable 'MSG' before assignment
used-before-assignment:8:8::Using variable 'MSG2' before assignment
used-before-assignment:5:19::Using variable 'MSG' before assignment:HIGH
used-before-assignment:8:8::Using variable 'MSG2' before assignment:HIGH
used-before-assignment:13:38:MyClass.incorrect_method:Using variable 'MyClass' before assignment:HIGH
used-before-assignment:16:46:MyClass.second_incorrect_method:Using variable 'MyClass' before assignment:HIGH

0 comments on commit 0f87c0e

Please sign in to comment.