From 46dedd0f147eecd0b5803168b9126ae8e0f1e837 Mon Sep 17 00:00:00 2001 From: Pascal Corpet Date: Sun, 20 Jan 2019 21:46:56 +0100 Subject: [PATCH] Take into account `__class_getitem__` from PEP 560 which fixes some false positives for `no-self-argument` and `unsubscriptable-object`. https://www.python.org/dev/peps/pep-0560/ Close #2416 --- CONTRIBUTORS.txt | 4 +++- ChangeLog | 4 ++++ pylint/checkers/classes.py | 2 +- pylint/checkers/typecheck.py | 2 +- pylint/checkers/utils.py | 3 +++ pylint/test/functional/no_self_argument_py37.py | 15 +++++++++++++++ pylint/test/functional/no_self_argument_py37.rc | 2 ++ pylint/test/functional/no_self_argument_py37.txt | 1 + .../test/functional/unsubscriptable_value_py37.py | 14 ++++++++++++++ .../test/functional/unsubscriptable_value_py37.rc | 2 ++ .../functional/unsubscriptable_value_py37.txt | 1 + 11 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 pylint/test/functional/no_self_argument_py37.py create mode 100644 pylint/test/functional/no_self_argument_py37.rc create mode 100644 pylint/test/functional/no_self_argument_py37.txt create mode 100644 pylint/test/functional/unsubscriptable_value_py37.py create mode 100644 pylint/test/functional/unsubscriptable_value_py37.rc create mode 100644 pylint/test/functional/unsubscriptable_value_py37.txt diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 200e9651e50..a479e94618b 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -262,4 +262,6 @@ contributors: * Justin Li (justinnhli) -* Nicolas Dickreuter \ No newline at end of file +* Nicolas Dickreuter + +* Pascal Corpet diff --git a/ChangeLog b/ChangeLog index 5a5d268e9d4..e0951a2bb23 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,10 @@ What's New in Pylint 2.3.0? Release date: TBA +* Fixed false positives for ``no-self-argument`` and ``unsubscriptable-object`` when using ``__class_getitem__`` (new in Python 3.7) + + Close #2416 + * ``fixme`` gets triggered only on comments. Close #2321 diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index b0a85906bb0..2fb152bd121 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -1407,7 +1407,7 @@ def _check_first_arg_for_type(self, node, metaclass=0): # regular class else: # class method - if node.type == "classmethod": + if node.type == "classmethod" or node.name == "__class_getitem__": self._check_first_arg_config( first, self.config.valid_classmethod_first_arg, diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 976e7fa896d..321636e2f16 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -329,7 +329,7 @@ def _missing_member_hint(owner, attrname, distance_threshold, max_choices): "Value '%s' is unsubscriptable", "unsubscriptable-object", "Emitted when a subscripted value doesn't support subscription " - "(i.e. doesn't define __getitem__ method).", + "(i.e. doesn't define __getitem__ method or __class_getitem__ for a class).", ), "E1137": ( "%r does not support item assignment", diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 36aa8791fc1..73591ff3f14 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -67,6 +67,7 @@ AITER_METHOD = "__aiter__" NEXT_METHOD = "__next__" GETITEM_METHOD = "__getitem__" +CLASS_GETITEM_METHOD = "__class_getitem__" SETITEM_METHOD = "__setitem__" DELITEM_METHOD = "__delitem__" CONTAINS_METHOD = "__contains__" @@ -1035,6 +1036,8 @@ def supports_membership_test(value: astroid.node_classes.NodeNG) -> bool: def supports_getitem(value: astroid.node_classes.NodeNG) -> bool: + if isinstance(value, astroid.ClassDef): + return _supports_protocol_method(value, CLASS_GETITEM_METHOD) return _supports_protocol(value, _supports_getitem_protocol) diff --git a/pylint/test/functional/no_self_argument_py37.py b/pylint/test/functional/no_self_argument_py37.py new file mode 100644 index 00000000000..8e6d6fc34e1 --- /dev/null +++ b/pylint/test/functional/no_self_argument_py37.py @@ -0,0 +1,15 @@ +"""Test detection of self as argument of first method in Python 3.7 and above.""" + +# pylint: disable=missing-docstring,too-few-public-methods,useless-object-inheritance + + +class Toto(object): + + def __class_getitem__(cls, params): + # This is actually a special method which is always a class method. + # See https://www.python.org/dev/peps/pep-0560/#class-getitem + pass + + def __class_other__(cls, params): # [no-self-argument] + # This is not a special case and as such is an instance method. + pass diff --git a/pylint/test/functional/no_self_argument_py37.rc b/pylint/test/functional/no_self_argument_py37.rc new file mode 100644 index 00000000000..a17bb22dafb --- /dev/null +++ b/pylint/test/functional/no_self_argument_py37.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.7 diff --git a/pylint/test/functional/no_self_argument_py37.txt b/pylint/test/functional/no_self_argument_py37.txt new file mode 100644 index 00000000000..812b8aa0c69 --- /dev/null +++ b/pylint/test/functional/no_self_argument_py37.txt @@ -0,0 +1 @@ +no-self-argument:13:Toto.__class_other__:Method should have "self" as first argument diff --git a/pylint/test/functional/unsubscriptable_value_py37.py b/pylint/test/functional/unsubscriptable_value_py37.py new file mode 100644 index 00000000000..54af7f9e177 --- /dev/null +++ b/pylint/test/functional/unsubscriptable_value_py37.py @@ -0,0 +1,14 @@ +""" +Checks that class used in a subscript supports subscription +(i.e. defines __class_getitem__ method). +""" +# pylint: disable=missing-docstring,pointless-statement,expression-not-assigned,wrong-import-position +# pylint: disable=too-few-public-methods,import-error,invalid-name,wrong-import-order, useless-object-inheritance +class Subscriptable(object): + + def __class_getitem__(cls, params): + pass + + +Subscriptable[0] +Subscriptable()[0] # [unsubscriptable-object] diff --git a/pylint/test/functional/unsubscriptable_value_py37.rc b/pylint/test/functional/unsubscriptable_value_py37.rc new file mode 100644 index 00000000000..a17bb22dafb --- /dev/null +++ b/pylint/test/functional/unsubscriptable_value_py37.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.7 diff --git a/pylint/test/functional/unsubscriptable_value_py37.txt b/pylint/test/functional/unsubscriptable_value_py37.txt new file mode 100644 index 00000000000..d0319649493 --- /dev/null +++ b/pylint/test/functional/unsubscriptable_value_py37.txt @@ -0,0 +1 @@ +unsubscriptable-object:14::Value 'Subscriptable()' is unsubscriptable