From 958cc3649d9b9e544606dc198a7d85a771e97e8e Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 17 Dec 2021 09:06:44 -0500 Subject: [PATCH] Fix #1555: Fix false negative for `no-member` when assigning instance attribute to itself --- ChangeLog | 3 ++ doc/whatsnew/2.14.rst | 5 +++ pylint/checkers/typecheck.py | 28 +++++++++----- .../n/no/no_member_assign_same_line.py | 37 +++++++++++++++++++ .../n/no/no_member_assign_same_line.txt | 1 + 5 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 tests/functional/n/no/no_member_assign_same_line.py create mode 100644 tests/functional/n/no/no_member_assign_same_line.txt diff --git a/ChangeLog b/ChangeLog index 4a4cb500b7..4553529d27 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,7 +9,10 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.14.rst' +* Fix false negative for ``no-member`` when attempting to assign an instance + attribute to itself without any prior assignment. + Closes #1555 .. Insert your changelog randomly, it will reduce merge conflicts diff --git a/doc/whatsnew/2.14.rst b/doc/whatsnew/2.14.rst index 64a5f7564d..deabf2c517 100644 --- a/doc/whatsnew/2.14.rst +++ b/doc/whatsnew/2.14.rst @@ -23,3 +23,8 @@ Extensions Other Changes ============= + +* Fix false negative for ``no-member`` when attempting to assign an instance + attribute to itself without any prior assignment. + + Closes #1555 diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 70b958238d..cd5b210a21 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1007,15 +1007,7 @@ def visit_attribute(self, node: nodes.Attribute) -> None: return try: - if not [ - n - for n in owner.getattr(node.attrname) - if not isinstance(n.statement(future=True), nodes.AugAssign) - ]: - missingattr.add((owner, name)) - continue - except astroid.exceptions.StatementMissing: - continue + attr_nodes = owner.getattr(node.attrname) except AttributeError: continue except astroid.DuplicateBasesError: @@ -1039,6 +1031,24 @@ def visit_attribute(self, node: nodes.Attribute) -> None: continue missingattr.add((owner, name)) continue + else: + for attr_node in attr_nodes: + attr_parent = attr_node.parent + # Skip augmented assignments + try: + if isinstance( + attr_node.statement(future=True), nodes.AugAssign + ): + continue + except astroid.exceptions.StatementMissing: + break + # Skip self-referencing assignments + if attr_parent is node.parent: + continue + break + else: + missingattr.add((owner, name)) + continue # stop on the first found break else: diff --git a/tests/functional/n/no/no_member_assign_same_line.py b/tests/functional/n/no/no_member_assign_same_line.py new file mode 100644 index 0000000000..45138d933e --- /dev/null +++ b/tests/functional/n/no/no_member_assign_same_line.py @@ -0,0 +1,37 @@ +"""Tests for no-member for self-referencing instance attributes +See https://github.com/PyCQA/pylint/issues/1555 +""" +# pylint: disable=too-few-public-methods + + +class ClassWithMember: + """Member defined in superclass.""" + def __init__(self): + self.member = True + + +class AssignMemberInSameLine: + """This class attempts to assign and access a member in the same line.""" + def __init__(self): + self.member = self.member # [no-member] + + +class AssignMemberInSameLineAfterTypeAnnotation: + """This might emit a message like `maybe-no-member` in the future.""" + def __init__(self): + self.member: bool + self.member = self.member + + +class AssignMemberFromSuper1(ClassWithMember): + """This assignment is valid due to inheritance.""" + def __init__(self): + self.member = self.member + super().__init__() + + +class AssignMemberFromSuper2(ClassWithMember): + """This assignment is valid due to inheritance.""" + def __init__(self): + super().__init__() + self.member = self.member diff --git a/tests/functional/n/no/no_member_assign_same_line.txt b/tests/functional/n/no/no_member_assign_same_line.txt new file mode 100644 index 0000000000..a6b15b9cda --- /dev/null +++ b/tests/functional/n/no/no_member_assign_same_line.txt @@ -0,0 +1 @@ +no-member:16:22:16:33:AssignMemberInSameLine.__init__:Instance of 'AssignMemberInSameLine' has no 'member' member:INFERENCE