summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2021-12-17 20:39:49 +0100
committerGitHub <noreply@github.com>2021-12-17 20:39:49 +0100
commit889a1d0cc5f9f2074159720e72cb879c45faa810 (patch)
tree8cad8814d1869be921499c1abdc805c1c135c83c
parent566a377154ce68e795aa3f952bb70bd987045228 (diff)
downloadpylint-git-889a1d0cc5f9f2074159720e72cb879c45faa810.tar.gz
Fix ``not-callable`` for attributes that alias ``NamedTuple`` (#5537)
-rw-r--r--ChangeLog4
-rw-r--r--doc/whatsnew/2.13.rst4
-rw-r--r--pylint/checkers/typecheck.py51
-rw-r--r--tests/functional/n/not_callable.py14
-rw-r--r--tests/functional/n/not_callable.txt2
5 files changed, 55 insertions, 20 deletions
diff --git a/ChangeLog b/ChangeLog
index 10137bdaa..68ec3a782 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -59,6 +59,10 @@ Release date: TBA
* The ``PyLinter`` class will now be initialized with a ``TextReporter``
as its reporter if none is provided.
+* Fix false positive ``not-callable`` with attributes that alias ``NamedTuple``
+
+ Partially closes #1730
+
* Fatal errors now emit a score of 0.0 regardless of whether the linted module
contained any statements
diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst
index 72f071510..ef72d5855 100644
--- a/doc/whatsnew/2.13.rst
+++ b/doc/whatsnew/2.13.rst
@@ -83,6 +83,10 @@ Other Changes
* The ``PyLinter`` class will now be initialized with a ``TextReporter``
as its reporter if none is provided.
+* Fix false positive ``not-callable`` with attributes that alias ``NamedTuple``
+
+ Partially closes #1730
+
* The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests
without these will trigger a ``DeprecationWarning``.
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index df01cc2fe..c3e76228d 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -1293,24 +1293,8 @@ accessed. Python regular expressions are accepted.",
the inferred function's definition
"""
called = safe_infer(node.func)
- # only function, generator and object defining __call__ are allowed
- # Ignore instances of descriptors since astroid cannot properly handle them
- # yet
- if called and not called.callable():
- if isinstance(called, astroid.Instance) and (
- not has_known_bases(called)
- or (
- called.parent is not None
- and isinstance(called.scope(), nodes.ClassDef)
- and "__get__" in called.locals
- )
- ):
- # Don't emit if we can't make sure this object is callable.
- pass
- else:
- self.add_message("not-callable", node=node, args=node.func.as_string())
- else:
- self._check_uninferable_call(node)
+
+ self._check_not_callable(node, called)
try:
called, implicit_args, callable_name = _determine_callable(called)
@@ -1570,6 +1554,37 @@ accessed. Python regular expressions are accepted.",
self.add_message("invalid-sequence-index", node=subscript)
return None
+ def _check_not_callable(
+ self, node: nodes.Call, inferred_call: Optional[nodes.NodeNG]
+ ) -> None:
+ """Checks to see if the not-callable message should be emitted
+
+ Only functions, generators and objects defining __call__ are "callable"
+ We ignore instances of descriptors since astroid cannot properly handle them yet
+ """
+ # Handle uninferable calls
+ if not inferred_call or inferred_call.callable():
+ self._check_uninferable_call(node)
+ return
+
+ if not isinstance(inferred_call, astroid.Instance):
+ self.add_message("not-callable", node=node, args=node.func.as_string())
+ return
+
+ # Don't emit if we can't make sure this object is callable.
+ if not has_known_bases(inferred_call):
+ return
+
+ if inferred_call.parent and isinstance(inferred_call.scope(), nodes.ClassDef):
+ # Ignore descriptor instances
+ if "__get__" in inferred_call.locals:
+ return
+ # NamedTuple instances are callable
+ if inferred_call.qname() == "typing.NamedTuple":
+ return
+
+ self.add_message("not-callable", node=node, args=node.func.as_string())
+
@check_messages("invalid-sequence-index")
def visit_extslice(self, node: nodes.ExtSlice) -> None:
if not node.parent or not hasattr(node.parent, "value"):
diff --git a/tests/functional/n/not_callable.py b/tests/functional/n/not_callable.py
index a7c59ae9c..31d364b88 100644
--- a/tests/functional/n/not_callable.py
+++ b/tests/functional/n/not_callable.py
@@ -136,13 +136,25 @@ class ClassWithProperty:
CLASS_WITH_PROP = ClassWithProperty().value() # [not-callable]
-# Test typing.Namedtuple not callable
+# Test typing.Namedtuple is callable
# See: https://github.com/PyCQA/pylint/issues/1295
import typing
Named = typing.NamedTuple("Named", [("foo", int), ("bar", int)])
named = Named(1, 2)
+
+# NamedTuple is callable, even if it aliased to a attribute
+# See https://github.com/PyCQA/pylint/issues/1730
+class TestNamedTuple:
+ def __init__(self, field: str) -> None:
+ self.my_tuple = typing.NamedTuple("Tuple", [(field, int)])
+ self.item: self.my_tuple
+
+ def set_item(self, item: int) -> None:
+ self.item = self.my_tuple(item)
+
+
# Test descriptor call
def func():
pass
diff --git a/tests/functional/n/not_callable.txt b/tests/functional/n/not_callable.txt
index f079bbfe8..e8a06b003 100644
--- a/tests/functional/n/not_callable.txt
+++ b/tests/functional/n/not_callable.txt
@@ -7,4 +7,4 @@ not-callable:32:12:32:17::INT is not callable:UNDEFINED
not-callable:67:0:67:13::PROP.test is not callable:UNDEFINED
not-callable:68:0:68:13::PROP.custom is not callable:UNDEFINED
not-callable:137:18:137:45::ClassWithProperty().value is not callable:UNDEFINED
-not-callable:190:0:190:16::get_number(10) is not callable:UNDEFINED
+not-callable:202:0:202:16::get_number(10) is not callable:UNDEFINED