From 0f87c0e5eb03b97ef4095ba3c93b705a3b04d967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 18 Oct 2021 17:45:51 +0200 Subject: [PATCH] Make ``used-before-assignment`` consider classes in method annotation and defaults This closes #3771 --- ChangeLog | 5 ++++ doc/whatsnew/2.12.rst | 5 ++++ pylint/checkers/variables.py | 30 +++++++++++++++++++ .../u/use/used_before_assignement.py | 20 ++++++++++++- .../u/use/used_before_assignement.txt | 6 ++-- 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index f26007d1e4..c19d95c54b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst index be1e5245c4..94772a77b8 100644 --- a/doc/whatsnew/2.12.rst +++ b/doc/whatsnew/2.12.rst @@ -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 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 1d78184c81..8ab167485d 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -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 @@ -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. diff --git a/tests/functional/u/use/used_before_assignement.py b/tests/functional/u/use/used_before_assignement.py index 285f3d180d..cffddb9b94 100644 --- a/tests/functional/u/use/used_before_assignement.py +++ b/tests/functional/u/use/used_before_assignement.py @@ -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()) diff --git a/tests/functional/u/use/used_before_assignement.txt b/tests/functional/u/use/used_before_assignement.txt index d19fe322ac..708880ac03 100644 --- a/tests/functional/u/use/used_before_assignement.txt +++ b/tests/functional/u/use/used_before_assignement.txt @@ -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