Skip to content

Commit

Permalink
Fix - undefined variable PR 5184
Browse files Browse the repository at this point in the history
Squashed commit of the following:

commit 3d7d5d3
Merge: f7333e2 27cabbb
Author: Pierre Sassoulas <pierre.sassoulas@gmail.com>
Date:   Tue Oct 19 13:26:45 2021 +0200

    Merge branch 'main' into undefined-variable

commit f7333e2
Merge: 63e34c0 8e6cd18
Author: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
Date:   Mon Oct 18 22:37:39 2021 +0200

    Merge branch 'undefined-variable' of https://github.com/DanielNoord/pylint into undefined-variable

commit 63e34c0
Author: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
Date:   Mon Oct 18 22:36:32 2021 +0200

    Handle `annotations` import

commit 8e6cd18
Merge: c45d82e cd5838c
Author: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
Date:   Mon Oct 18 22:02:14 2021 +0200

    Merge branch 'main' into undefined-variable

commit c45d82e
Author: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
Date:   Mon Oct 18 20:32:06 2021 +0200

    Update name and docstring

commit 0f87c0e
Author: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
Date:   Mon Oct 18 17:45:51 2021 +0200

    Make ``used-before-assignment`` consider classes in method annotation and defaults
    This closes pylint-dev#3771
  • Loading branch information
cdce8p committed Oct 21, 2021
1 parent 729953f commit 786991a
Show file tree
Hide file tree
Showing 8 changed files with 98 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 @@ -77,6 +77,11 @@ Other Changes

Closes #4031

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

Closes #3771

* Fix double emitting of ``not-callable`` on inferrable ``properties``

Closes #4426
Expand Down
38 changes: 38 additions & 0 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,17 @@ 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_type_annotation_reference(node, defstmt):
if not utils.is_postponed_evaluation_enabled(node):
self.add_message(
"used-before-assignment", node=node, args=node.name
)
break
elif self._is_first_level_default_reference(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 +1580,33 @@ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool
return False
return True

@staticmethod
def _is_first_level_type_annotation_reference(
node: nodes.Name, defstmt: nodes.Statement
) -> bool:
"""Check if a first level method's annotation refers to its own class.
See tests/functional/u/use/used_before_assignement.py for additional examples.
"""
if isinstance(defstmt, nodes.ClassDef) and node.frame().parent == defstmt:
# Check if used as type annotation
if isinstance(node.parent, nodes.Arguments):
return True
return False

@staticmethod
def _is_first_level_default_reference(
node: nodes.Name, defstmt: nodes.Statement
) -> bool:
"""Check if a first level method's default value refers to its own class.
See tests/functional/u/use/used_before_assignement.py for additional examples.
"""
if isinstance(defstmt, nodes.ClassDef) and node.frame().parent == defstmt:
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
24 changes: 24 additions & 0 deletions tests/functional/u/use/used_before_assignment_py37.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Tests for used-before-assignment with functions added in python 3.7"""
# pylint: disable=missing-function-docstring
from __future__ import annotations


class MyClass:
"""With the future import only default values can't refer to the base class"""

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

def second_incorrect_method(
self, other=MyClass() # [used-before-assignment]
) -> bool:
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())
2 changes: 2 additions & 0 deletions tests/functional/u/use/used_before_assignment_py37.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.7
1 change: 1 addition & 0 deletions tests/functional/u/use/used_before_assignment_py37.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
used-before-assignment:13:20:MyClass.second_incorrect_method:Using variable 'MyClass' before assignment:HIGH

0 comments on commit 786991a

Please sign in to comment.