From 803a2522639a2cd93f889885b1cbfb47642477b3 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 5 Mar 2021 23:22:12 +0100 Subject: Fix false-positive no-member for typed annotations without default value --- ChangeLog | 4 ++++ pylint/checkers/typecheck.py | 7 +----- pylint/checkers/utils.py | 24 +++++++++++++++++++++ .../m/member_checks_typed_annotations.py | 25 ++++++++++++++++++++++ .../m/member_checks_typed_annotations.txt | 1 + 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 tests/functional/m/member_checks_typed_annotations.py create mode 100644 tests/functional/m/member_checks_typed_annotations.txt diff --git a/ChangeLog b/ChangeLog index 090003a4f..6bb635efb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -33,6 +33,10 @@ Release date: TBA Closes #4180 +* Fix false-positive ``no-member`` for typed annotations without default value. + + Closes #3167 + What's New in Pylint 2.7.2? =========================== diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index d0e2cfa5b..acbf7d4bc 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -474,12 +474,7 @@ def _emit_no_member(node, owner, owner_name, ignored_mixins=True, ignored_none=T # Exclude typed annotations, since these might actually exist # at some point during the runtime of the program. - attribute = owner.locals.get(node.attrname, [None])[0] - if ( - attribute - and isinstance(attribute, astroid.AssignName) - and isinstance(attribute.parent, astroid.AnnAssign) - ): + if utils.is_attribute_typed_annotation(owner, node.attrname): return False if isinstance(owner, objects.Super): # Verify if we are dealing with an invalid Super object. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index f1348f6e0..29f774a75 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1435,3 +1435,27 @@ def is_classdef_type(node: astroid.ClassDef) -> bool: if isinstance(base, astroid.Name) and base.name == "type": return True return False + + +def is_attribute_typed_annotation( + node: Union[astroid.ClassDef, astroid.Instance], attr_name: str +) -> bool: + """Test if attribute is typed annotation in current node + or any base nodes. + """ + attribute = node.locals.get(attr_name, [None])[0] + if ( + attribute + and isinstance(attribute, astroid.AssignName) + and isinstance(attribute.parent, astroid.AnnAssign) + ): + return True + for base in node.bases: + inferred = safe_infer(base) + if ( + inferred + and isinstance(inferred, astroid.ClassDef) + and is_attribute_typed_annotation(inferred, attr_name) + ): + return True + return False diff --git a/tests/functional/m/member_checks_typed_annotations.py b/tests/functional/m/member_checks_typed_annotations.py new file mode 100644 index 000000000..90f775a43 --- /dev/null +++ b/tests/functional/m/member_checks_typed_annotations.py @@ -0,0 +1,25 @@ +# pylint: disable=missing-docstring,invalid-name,too-few-public-methods +class A: + myfield: int + +class B(A): + pass + +class C: + pass + +class D(C, B): + pass + + +a = A() +print(a.myfield) + +b = B() +print(b.myfield) + +d = D() +print(d.myfield) + +c = C() +print(c.myfield) # [no-member] diff --git a/tests/functional/m/member_checks_typed_annotations.txt b/tests/functional/m/member_checks_typed_annotations.txt new file mode 100644 index 000000000..6261a31aa --- /dev/null +++ b/tests/functional/m/member_checks_typed_annotations.txt @@ -0,0 +1 @@ +no-member:25:6::Instance of 'C' has no 'myfield' member:INFERENCE -- cgit v1.2.1