summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Mueller <30130371+cdce8p@users.noreply.github.com>2021-03-05 23:22:12 +0100
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2021-03-06 14:22:52 +0100
commit803a2522639a2cd93f889885b1cbfb47642477b3 (patch)
tree376369a967fb855fba541635786c89a4527f9091
parenta7d37d22041bdf7a28631f42ea86a4a06ba35e24 (diff)
downloadpylint-git-803a2522639a2cd93f889885b1cbfb47642477b3.tar.gz
Fix false-positive no-member for typed annotations without default value
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/typecheck.py7
-rw-r--r--pylint/checkers/utils.py24
-rw-r--r--tests/functional/m/member_checks_typed_annotations.py25
-rw-r--r--tests/functional/m/member_checks_typed_annotations.txt1
5 files changed, 55 insertions, 6 deletions
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