diff options
Diffstat (limited to 'pylint/checkers/classes/__init__.py')
-rw-r--r-- | pylint/checkers/classes/__init__.py | 370 |
1 files changed, 1 insertions, 369 deletions
diff --git a/pylint/checkers/classes/__init__.py b/pylint/checkers/classes/__init__.py index 52601bc42..15c9cdb6c 100644 --- a/pylint/checkers/classes/__init__.py +++ b/pylint/checkers/classes/__init__.py @@ -58,9 +58,9 @@ import astroid from astroid import nodes from pylint.checkers import BaseChecker, utils +from pylint.checkers.classes.special_methods_checker import SpecialMethodsChecker from pylint.checkers.utils import ( PYMETHODS, - SPECIAL_METHODS_PARAMS, check_messages, class_is_abstract, decorated_with, @@ -71,7 +71,6 @@ from pylint.checkers.utils import ( is_attr_protected, is_builtin_object, is_comprehension, - is_function_body_ellipsis, is_iterable, is_overload_stub, is_property_setter, @@ -86,7 +85,6 @@ from pylint.checkers.utils import ( from pylint.interfaces import IAstroidChecker from pylint.utils import get_global_option -NEXT_METHOD = "__next__" INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"} BUILTIN_DECORATORS = {"builtins.property", "builtins.classmethod"} ASTROID_TYPE_COMPARATORS = { @@ -428,29 +426,6 @@ def _has_bare_super_call(fundef_node): return False -def _safe_infer_call_result(node, caller, context=None): - """ - Safely infer the return value of a function. - - Returns None if inference failed or if there is some ambiguity (more than - one node has been inferred). Otherwise returns inferred value. - """ - try: - inferit = node.infer_call_result(caller, context=context) - value = next(inferit) - except astroid.InferenceError: - return None # inference failed - except StopIteration: - return None # no values inferred - try: - next(inferit) - return None # there is ambiguity on the inferred node - except astroid.InferenceError: - return None # there is some kind of ambiguity - except StopIteration: - return value - - def _has_same_layout_slots(slots, assigned_value): inferred = next(assigned_value.infer()) if isinstance(inferred, nodes.ClassDef): @@ -2065,349 +2040,6 @@ a metaclass class method.", ) -class SpecialMethodsChecker(BaseChecker): - """Checker which verifies that special methods - are implemented correctly. - """ - - __implements__ = (IAstroidChecker,) - name = "classes" - msgs = { - "E0301": ( - "__iter__ returns non-iterator", - "non-iterator-returned", - "Used when an __iter__ method returns something which is not an " - f"iterable (i.e. has no `{NEXT_METHOD}` method)", - { - "old_names": [ - ("W0234", "old-non-iterator-returned-1"), - ("E0234", "old-non-iterator-returned-2"), - ] - }, - ), - "E0302": ( - "The special method %r expects %s param(s), %d %s given", - "unexpected-special-method-signature", - "Emitted when a special method was defined with an " - "invalid number of parameters. If it has too few or " - "too many, it might not work at all.", - {"old_names": [("E0235", "bad-context-manager")]}, - ), - "E0303": ( - "__len__ does not return non-negative integer", - "invalid-length-returned", - "Used when a __len__ method returns something which is not a " - "non-negative integer", - ), - "E0304": ( - "__bool__ does not return bool", - "invalid-bool-returned", - "Used when a __bool__ method returns something which is not a bool", - ), - "E0305": ( - "__index__ does not return int", - "invalid-index-returned", - "Used when an __index__ method returns something which is not " - "an integer", - ), - "E0306": ( - "__repr__ does not return str", - "invalid-repr-returned", - "Used when a __repr__ method returns something which is not a string", - ), - "E0307": ( - "__str__ does not return str", - "invalid-str-returned", - "Used when a __str__ method returns something which is not a string", - ), - "E0308": ( - "__bytes__ does not return bytes", - "invalid-bytes-returned", - "Used when a __bytes__ method returns something which is not bytes", - ), - "E0309": ( - "__hash__ does not return int", - "invalid-hash-returned", - "Used when a __hash__ method returns something which is not an integer", - ), - "E0310": ( - "__length_hint__ does not return non-negative integer", - "invalid-length-hint-returned", - "Used when a __length_hint__ method returns something which is not a " - "non-negative integer", - ), - "E0311": ( - "__format__ does not return str", - "invalid-format-returned", - "Used when a __format__ method returns something which is not a string", - ), - "E0312": ( - "__getnewargs__ does not return a tuple", - "invalid-getnewargs-returned", - "Used when a __getnewargs__ method returns something which is not " - "a tuple", - ), - "E0313": ( - "__getnewargs_ex__ does not return a tuple containing (tuple, dict)", - "invalid-getnewargs-ex-returned", - "Used when a __getnewargs_ex__ method returns something which is not " - "of the form tuple(tuple, dict)", - ), - } - priority = -2 - - def __init__(self, linter=None): - super().__init__(linter) - self._protocol_map = { - "__iter__": self._check_iter, - "__len__": self._check_len, - "__bool__": self._check_bool, - "__index__": self._check_index, - "__repr__": self._check_repr, - "__str__": self._check_str, - "__bytes__": self._check_bytes, - "__hash__": self._check_hash, - "__length_hint__": self._check_length_hint, - "__format__": self._check_format, - "__getnewargs__": self._check_getnewargs, - "__getnewargs_ex__": self._check_getnewargs_ex, - } - - @check_messages( - "unexpected-special-method-signature", - "non-iterator-returned", - "invalid-length-returned", - "invalid-bool-returned", - "invalid-index-returned", - "invalid-repr-returned", - "invalid-str-returned", - "invalid-bytes-returned", - "invalid-hash-returned", - "invalid-length-hint-returned", - "invalid-format-returned", - "invalid-getnewargs-returned", - "invalid-getnewargs-ex-returned", - ) - def visit_functiondef(self, node: nodes.FunctionDef) -> None: - if not node.is_method(): - return - - inferred = _safe_infer_call_result(node, node) - # Only want to check types that we are able to infer - if ( - inferred - and node.name in self._protocol_map - and not is_function_body_ellipsis(node) - ): - self._protocol_map[node.name](node, inferred) - - if node.name in PYMETHODS: - self._check_unexpected_method_signature(node) - - visit_asyncfunctiondef = visit_functiondef - - def _check_unexpected_method_signature(self, node): - expected_params = SPECIAL_METHODS_PARAMS[node.name] - - if expected_params is None: - # This can support a variable number of parameters. - return - if not node.args.args and not node.args.vararg: - # Method has no parameter, will be caught - # by no-method-argument. - return - - if decorated_with(node, ["builtins.staticmethod"]): - # We expect to not take in consideration self. - all_args = node.args.args - else: - all_args = node.args.args[1:] - mandatory = len(all_args) - len(node.args.defaults) - optional = len(node.args.defaults) - current_params = mandatory + optional - - if isinstance(expected_params, tuple): - # The expected number of parameters can be any value from this - # tuple, although the user should implement the method - # to take all of them in consideration. - emit = mandatory not in expected_params - # pylint: disable-next=consider-using-f-string - expected_params = "between %d or %d" % expected_params - else: - # If the number of mandatory parameters doesn't - # suffice, the expected parameters for this - # function will be deduced from the optional - # parameters. - rest = expected_params - mandatory - if rest == 0: - emit = False - elif rest < 0: - emit = True - elif rest > 0: - emit = not ((optional - rest) >= 0 or node.args.vararg) - - if emit: - verb = "was" if current_params <= 1 else "were" - self.add_message( - "unexpected-special-method-signature", - args=(node.name, expected_params, current_params, verb), - node=node, - ) - - @staticmethod - def _is_wrapped_type(node, type_): - return ( - isinstance(node, astroid.Instance) - and node.name == type_ - and not isinstance(node, nodes.Const) - ) - - @staticmethod - def _is_int(node): - if SpecialMethodsChecker._is_wrapped_type(node, "int"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, int) - - @staticmethod - def _is_str(node): - if SpecialMethodsChecker._is_wrapped_type(node, "str"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, str) - - @staticmethod - def _is_bool(node): - if SpecialMethodsChecker._is_wrapped_type(node, "bool"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, bool) - - @staticmethod - def _is_bytes(node): - if SpecialMethodsChecker._is_wrapped_type(node, "bytes"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, bytes) - - @staticmethod - def _is_tuple(node): - if SpecialMethodsChecker._is_wrapped_type(node, "tuple"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, tuple) - - @staticmethod - def _is_dict(node): - if SpecialMethodsChecker._is_wrapped_type(node, "dict"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, dict) - - @staticmethod - def _is_iterator(node): - if node is astroid.Uninferable: - # Just ignore Uninferable objects. - return True - if isinstance(node, astroid.bases.Generator): - # Generators can be iterated. - return True - - if isinstance(node, astroid.Instance): - try: - node.local_attr(NEXT_METHOD) - return True - except astroid.NotFoundError: - pass - elif isinstance(node, nodes.ClassDef): - metaclass = node.metaclass() - if metaclass and isinstance(metaclass, nodes.ClassDef): - try: - metaclass.local_attr(NEXT_METHOD) - return True - except astroid.NotFoundError: - pass - return False - - def _check_iter(self, node, inferred): - if not self._is_iterator(inferred): - self.add_message("non-iterator-returned", node=node) - - def _check_len(self, node, inferred): - if not self._is_int(inferred): - self.add_message("invalid-length-returned", node=node) - elif isinstance(inferred, nodes.Const) and inferred.value < 0: - self.add_message("invalid-length-returned", node=node) - - def _check_bool(self, node, inferred): - if not self._is_bool(inferred): - self.add_message("invalid-bool-returned", node=node) - - def _check_index(self, node, inferred): - if not self._is_int(inferred): - self.add_message("invalid-index-returned", node=node) - - def _check_repr(self, node, inferred): - if not self._is_str(inferred): - self.add_message("invalid-repr-returned", node=node) - - def _check_str(self, node, inferred): - if not self._is_str(inferred): - self.add_message("invalid-str-returned", node=node) - - def _check_bytes(self, node, inferred): - if not self._is_bytes(inferred): - self.add_message("invalid-bytes-returned", node=node) - - def _check_hash(self, node, inferred): - if not self._is_int(inferred): - self.add_message("invalid-hash-returned", node=node) - - def _check_length_hint(self, node, inferred): - if not self._is_int(inferred): - self.add_message("invalid-length-hint-returned", node=node) - elif isinstance(inferred, nodes.Const) and inferred.value < 0: - self.add_message("invalid-length-hint-returned", node=node) - - def _check_format(self, node, inferred): - if not self._is_str(inferred): - self.add_message("invalid-format-returned", node=node) - - def _check_getnewargs(self, node, inferred): - if not self._is_tuple(inferred): - self.add_message("invalid-getnewargs-returned", node=node) - - def _check_getnewargs_ex(self, node, inferred): - if not self._is_tuple(inferred): - self.add_message("invalid-getnewargs-ex-returned", node=node) - return - - if not isinstance(inferred, nodes.Tuple): - # If it's not an astroid.Tuple we can't analyze it further - return - - found_error = False - - if len(inferred.elts) != 2: - found_error = True - else: - for arg, check in ( - (inferred.elts[0], self._is_tuple), - (inferred.elts[1], self._is_dict), - ): - - if isinstance(arg, nodes.Call): - arg = safe_infer(arg) - - if arg and arg is not astroid.Uninferable: - if not check(arg): - found_error = True - break - - if found_error: - self.add_message("invalid-getnewargs-ex-returned", node=node) - - def _ancestors_to_call(klass_node, method="__init__"): """return a dictionary where keys are the list of base classes providing the queried method, and so that should/may be called from the method node |