diff --git a/ChangeLog b/ChangeLog index ea04a5ab93..8d73d6ebc5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -27,6 +27,9 @@ Release date: TBA .. Put bug fixes that will be cherry-picked to latest major version here +* Fix false positive for ``method-hidden`` when using private attribute and method + + Closes #3936 * ``use-symbolic-message-instead`` now also works on legacy messages like ``C0111`` (``missing-docstring``). * Remove unwanted print to stdout from ``_emit_no_member`` diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index d475cfcfbb..c96cce007b 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -1000,6 +1000,8 @@ def visit_functiondef(self, node): # If a subclass defined the method then it's not our fault. for ancestor in klass.ancestors(): + if node.name in ancestor.instance_attrs and is_attr_private(node.name): + return for obj in ancestor.lookup(node.name)[1]: if isinstance(obj, astroid.FunctionDef): return diff --git a/tests/checkers/unittest_classes.py b/tests/checkers/unittest_classes.py index ecbc72c4aa..4cf0a6cb1a 100644 --- a/tests/checkers/unittest_classes.py +++ b/tests/checkers/unittest_classes.py @@ -187,3 +187,33 @@ def _fake_special_(self, other): Message("protected-access", node=attribute_in_fake_2, args="__private"), ): self.walk(node.root()) + + def test_private_attribute_hides_method(self): + node = astroid.extract_node( + """ + class Parent: + def __init__(self): + self.__private = None + + class Child(Parent): + def __private(self): #@ + pass + """ + ) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + def test_protected_attribute_hides_method(self): + node = astroid.extract_node( + """ + class Parent: + def __init__(self): + self._protected = None + + class Child(Parent): + def _protected(self): #@ + pass + """ + ) + with self.assertAddsMessages(Message("method-hidden", node=node, args=("", 4))): + self.checker.visit_functiondef(node)