summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Sassoulas <pierre.sassoulas@gmail.com>2021-06-30 22:32:53 +0200
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2021-06-30 23:13:20 +0200
commitaa6d91f45407544e49287ca5b43d6c86d317f5a7 (patch)
tree50fb2ac887d9e35890a29f48e5be804a4e4a863c
parentefc35c3c40622f419ea07f698c029f13fb9fed52 (diff)
downloadpylint-git-aa6d91f45407544e49287ca5b43d6c86d317f5a7.tar.gz
[unused-private-member] Handle the case with Call node
As seen in issue #4638
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/classes.py23
-rw-r--r--tests/functional/u/unused/unused_private_member.py11
3 files changed, 34 insertions, 4 deletions
diff --git a/ChangeLog b/ChangeLog
index 5a4f700ee..c84b2620b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -17,6 +17,10 @@ Release date: TBA
..
Put bug fixes that should not wait for a new minor version here
+* Fix a crash that happened when analysing code using ``type(self)`` to access
+ a class attribute in the ``unused-private-member`` checker.
+
+ Closes #4638
What's New in Pylint 2.9.1?
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 6fffd2b16..a85e216c5 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -909,14 +909,29 @@ a metaclass class method.",
continue
for attribute in node.nodes_of_class(astroid.Attribute):
attribute = cast(astroid.Attribute, attribute)
- if attribute.expr is None: # attribute.expr is probably not really an Optional ?
+ if attribute.expr is None:
+ # attribute.expr is an Optional[NodeNG] it can be None
continue
if (
- attribute.attrname == function_def.name
- and attribute.scope() != function_def # We ignore recursive calls
- and attribute.expr.name in ("self", node.name)
+ attribute.attrname != function_def.name
+ or attribute.scope() == function_def
):
+ # We ignore recursive calls
+ continue
+ if isinstance(attribute.expr, astroid.Name) and attribute.expr.name in (
+ "self",
+ node.name,
+ ):
+ # self.__attrname / node_name.__attrname
break
+ if isinstance(attribute.expr, astroid.Call):
+ # type(self).__attrname
+ inferred = safe_infer(attribute.expr)
+ if (
+ isinstance(inferred, astroid.ClassDef)
+ and inferred.name == node.name
+ ):
+ break
else:
function_repr = f"{function_def.name}({function_def.args.as_string()})"
self.add_message(
diff --git a/tests/functional/u/unused/unused_private_member.py b/tests/functional/u/unused/unused_private_member.py
index d65478503..35c5801e4 100644
--- a/tests/functional/u/unused/unused_private_member.py
+++ b/tests/functional/u/unused/unused_private_member.py
@@ -66,3 +66,14 @@ class MyCls:
@classmethod
def get_class_var(cls):
return cls.__class_var
+
+
+class Bla:
+ """Regression test for issue 4638"""
+
+ def __init__(self):
+ type(self).__a()
+
+ @classmethod
+ def __a(cls):
+ pass