diff options
author | Julien Palard <julien@palard.fr> | 2020-10-09 23:41:22 +0200 |
---|---|---|
committer | Julien Palard <julien@palard.fr> | 2020-11-02 18:36:49 +0100 |
commit | c9be321222e0c765fe0aaff2aee2e0ba1552b22d (patch) | |
tree | 407f8ab9244f81a5874621c688c0de39e81c415a | |
parent | 70cdb9d74913f3ccf0b60ed040d0530a42732980 (diff) | |
download | pylint-git-c9be321222e0c765fe0aaff2aee2e0ba1552b22d.tar.gz |
Handle class decorators during typing checks.
-rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | doc/development_guide/contribute.rst | 5 | ||||
-rw-r--r-- | pylint/checkers/typecheck.py | 11 | ||||
-rw-r--r-- | tests/checkers/unittest_typecheck.py | 57 |
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 @@ -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 |