summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Palard <julien@palard.fr>2020-10-09 23:41:22 +0200
committerJulien Palard <julien@palard.fr>2020-11-02 18:36:49 +0100
commitc9be321222e0c765fe0aaff2aee2e0ba1552b22d (patch)
tree407f8ab9244f81a5874621c688c0de39e81c415a
parent70cdb9d74913f3ccf0b60ed040d0530a42732980 (diff)
downloadpylint-git-c9be321222e0c765fe0aaff2aee2e0ba1552b22d.tar.gz
Handle class decorators during typing checks.
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog4
-rw-r--r--doc/development_guide/contribute.rst5
-rw-r--r--pylint/checkers/typecheck.py11
-rw-r--r--tests/checkers/unittest_typecheck.py57
5 files changed, 77 insertions, 2 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 702af289a..e4cd248f4 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -427,3 +427,5 @@ contributors:
* Takashi Hirashima: contributor
* Joffrey Mander: contributor
+
+* Julien Palard: contributor
diff --git a/ChangeLog b/ChangeLog
index fff8ec92f..d2625bc2e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,10 @@
Pylint's ChangeLog
------------------
+* Handle class decorators applied to function.
+
+ Closes #3882
+
* Add check for empty comments
* Fix minor documentation issue in contribute.rst
diff --git a/doc/development_guide/contribute.rst b/doc/development_guide/contribute.rst
index 0476c197c..f82bc442c 100644
--- a/doc/development_guide/contribute.rst
+++ b/doc/development_guide/contribute.rst
@@ -110,7 +110,10 @@ your patch gets accepted.
(`What's New` section). For the release document we usually write some more details,
and it is also a good place to offer examples on how the new change is supposed to work.
-- Add yourself to the `CONTRIBUTORS` file, if you are not already there.
+- Add a short entry in :file:`doc/whatsnew/VERSION.rst`.
+
+- Add yourself to the `CONTRIBUTORS` file, flag youself appropriately
+ (if in doubt, you're a ``contributor``).
- Write a comprehensive commit message
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index 0e669ec33..17dcd50c2 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -65,7 +65,7 @@ import astroid
import astroid.arguments
import astroid.context
import astroid.nodes
-from astroid import bases, decorators, exceptions, modutils, objects
+from astroid import bases, decorators, exceptions, helpers, modutils, objects
from astroid.interpreter import dunder_lookup
from pylint.checkers import BaseChecker, utils
@@ -1720,9 +1720,18 @@ accessed. Python regular expressions are accepted.",
return
inferred = safe_infer(node.value)
+
if inferred is None or inferred is astroid.Uninferable:
return
+ if inferred.decorators:
+ first_decorator = helpers.safe_infer(inferred.decorators.nodes[0])
+ if isinstance(first_decorator, astroid.ClassDef):
+ inferred = first_decorator.instantiate_class()
+ else:
+ return # It would be better to handle function
+ # decorators, but let's start slow.
+
if not supported_protocol(inferred):
self.add_message(msg, args=node.value.as_string(), node=node.value)
diff --git a/tests/checkers/unittest_typecheck.py b/tests/checkers/unittest_typecheck.py
index 346f5f38d..500ae60d9 100644
--- a/tests/checkers/unittest_typecheck.py
+++ b/tests/checkers/unittest_typecheck.py
@@ -24,6 +24,7 @@ import astroid
import pytest
from pylint.checkers import typecheck
+from pylint.interfaces import UNDEFINED
from pylint.testutils import CheckerTestCase, Message, set_config
try:
@@ -286,6 +287,62 @@ class TestTypeChecker(CheckerTestCase):
with self.assertNoMessages():
self.checker.visit_subscript(subscript)
+ def test_typing_option_object_is_subscriptable_issue3882(self):
+ module = astroid.parse(
+ """
+ import typing
+ test = typing.Optional[int]
+ """
+ )
+ subscript = module.body[-1].value
+ with self.assertNoMessages():
+ self.checker.visit_subscript(subscript)
+
+ def test_decorated_by_a_subscriptable_class_issue3882(self):
+ module = astroid.parse(
+ """
+ class Deco:
+ def __init__(self, f):
+ self.f = f
+
+ def __getitem__(self, item):
+ return item
+ @Deco
+ def decorated():
+ ...
+
+ test = decorated[None]
+ """
+ )
+ subscript = module.body[-1].value
+ with self.assertNoMessages():
+ self.checker.visit_subscript(subscript)
+
+ def test_decorated_by_an_unsubscriptable_class_issue3882(self):
+ module = astroid.parse(
+ """
+ class Deco:
+ def __init__(self, f):
+ self.f = f
+
+ @Deco
+ def decorated():
+ ...
+
+ test = decorated[None]
+ """
+ )
+ subscript = module.body[-1].value
+ with self.assertAddsMessages(
+ Message(
+ "unsubscriptable-object",
+ node=subscript.value,
+ args="decorated",
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_subscript(subscript)
+
def test_staticmethod_multiprocessing_call(self):
"""Make sure not-callable isn't raised for descriptors