From 75ae5cc302b7c9b3b8b21d2f2e2b4fc0449eebbd Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Thu, 17 Oct 2019 09:29:53 +0200 Subject: [PATCH] ``typing.overload`` functions are exempted from ``too-many-function-args`` Close #3170 --- ChangeLog | 5 ++- pylint/checkers/typecheck.py | 17 +++++---- pylint/checkers/utils.py | 10 +++-- .../too/too_many_arguments_overload.py | 38 +++++++++++++++++++ 4 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 tests/functional/too/too_many_arguments_overload.py diff --git a/ChangeLog b/ChangeLog index 111118ee90..d02e14984c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -41,7 +41,6 @@ Release date: TBA Close #3148 - * ``import-outside-toplevel`` is emitted for ``ImportFrom`` nodes as well. Close #3175 @@ -63,6 +62,10 @@ Release date: TBA Closes #3185 +* ``typing.overload`` functions are exempted from ``too-many-function-args`` + + Close #3170 + What's New in Pylint 2.4.2? =========================== diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 53014bc541..472f7d417b 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -65,6 +65,7 @@ is_inside_abstract_class, is_iterable, is_mapping, + is_overload_stub, is_super, node_ignores_exception, safe_infer, @@ -1185,6 +1186,7 @@ def visit_call(self, node): num_positional_args = len(call_site.positional_arguments) keyword_args = list(call_site.keyword_arguments.keys()) + overload_function = is_overload_stub(called) # Determine if we don't have a context for our call and we use variadics. node_scope = node.scope() @@ -1251,11 +1253,12 @@ def visit_call(self, node): # parameter. break else: - # Too many positional arguments. - self.add_message( - "too-many-function-args", node=node, args=(callable_name,) - ) - break + if not overload_function: + # Too many positional arguments. + self.add_message( + "too-many-function-args", node=node, args=(callable_name,) + ) + break # 2. Match the keyword arguments. for keyword in keyword_args: @@ -1290,7 +1293,7 @@ def visit_call(self, node): elif called.args.kwarg is not None: # The keyword argument gets assigned to the **kwargs parameter. pass - else: + elif not overload_function: # Unexpected keyword argument. self.add_message( "unexpected-keyword-arg", node=node, args=(keyword, callable_name) @@ -1315,7 +1318,7 @@ def visit_call(self, node): display_name = "" else: display_name = repr(name) - if not has_no_context_positional_variadic: + if not has_no_context_positional_variadic and not overload_function: self.add_message( "no-value-for-parameter", node=node, diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 5819edbb10..f353ae5c7d 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -753,7 +753,10 @@ def _is_property_decorator(decorator: astroid.Name) -> bool: return False -def decorated_with(func: astroid.FunctionDef, qnames: Iterable[str]) -> bool: +def decorated_with( + func: Union[astroid.FunctionDef, astroid.BoundMethod, astroid.UnboundMethod], + qnames: Iterable[str], +) -> bool: """Determine if the `func` node has a decorator with the qualified name `qname`.""" decorators = func.decorators.nodes if func.decorators else [] for decorator_node in decorators: @@ -1237,9 +1240,8 @@ def is_overload_stub(node: astroid.node_classes.NodeNG) -> bool: :param node: Node to check. :returns: True if node is an overload function stub. False otherwise. """ - return isinstance(node, astroid.FunctionDef) and decorated_with( - node, ["typing.overload"] - ) + decorators = getattr(node, "decorators", None) + return bool(decorators and decorated_with(node, ["typing.overload", "overload"])) def is_protocol_class(cls: astroid.node_classes.NodeNG) -> bool: diff --git a/tests/functional/too/too_many_arguments_overload.py b/tests/functional/too/too_many_arguments_overload.py new file mode 100644 index 0000000000..1155deb0c9 --- /dev/null +++ b/tests/functional/too/too_many_arguments_overload.py @@ -0,0 +1,38 @@ +# pylint: disable=missing-function-docstring,missing-module-docstring,missing-class-docstring +# pylint: disable=too-few-public-methods +from typing import overload + + +class ClassA: + @classmethod + @overload + def method(cls, arg1): + pass + + @classmethod + @overload + def method(cls, arg1, arg2): + pass + + @classmethod + def method(cls, arg1, arg2=None): + pass + + +ClassA.method(1, 2) + + +class ClassB: + @overload + def method(self, arg1): + pass + + @overload + def method(self, arg1, arg2): + pass + + def method(self, arg1, arg2=None): + pass + + +ClassB().method(1, arg2=2)