diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index edef5188b4..aa6ece436f 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1448,6 +1448,20 @@ def _check_isinstance_args(self, node: nodes.Call, callable_name: str) -> None: confidence=INFERENCE, ) + def _is_super(self, node: nodes.Call) -> bool: + """True if this node is a call to super().""" + if ( + isinstance(node.func, nodes.Attribute) + and node.func.attrname == "__init__" + and isinstance(node.func.expr, nodes.Call) + and isinstance(node.func.expr.func, nodes.Name) + and node.func.expr.func.name == "super" + ): + inferred = safe_infer(node.func.expr) + if isinstance(inferred, astroid.objects.Super): + return True + return False + # pylint: disable = too-many-branches, too-many-locals, too-many-statements def visit_call(self, node: nodes.Call) -> None: """Check that called functions/methods are inferred to callable objects, @@ -1464,11 +1478,30 @@ def visit_call(self, node: nodes.Call) -> None: # those errors are handled by different warnings. return + # Build the set of keyword arguments, checking for duplicate keywords, + # and count the positional arguments. + call_site = astroid.arguments.CallSite.from_call(node) + if called.args.args is None: if called.name == "isinstance": # Verify whether second argument of isinstance is a valid type self._check_isinstance_args(node, callable_name) - # Built-in functions have no argument information. + # Built-in functions have no argument information, but we know + # super() only takes self. + if self._is_super(node) and utils.is_builtin_object(called): + if ( + call_site + and call_site.positional_arguments + and (first_arg := call_site.positional_arguments[0]) + and not ( + isinstance(first_arg, nodes.Name) and first_arg.name == "self" + ) + ): + self.add_message( + "too-many-function-args", + node=node, + args=(callable_name,), + ) return if len(called.argnames()) != len(set(called.argnames())): @@ -1476,10 +1509,6 @@ def visit_call(self, node: nodes.Call) -> None: # make sense of the function call in this case, so just return. return - # Build the set of keyword arguments, checking for duplicate keywords, - # and count the positional arguments. - call_site = astroid.arguments.CallSite.from_call(node) - # Warn about duplicated keyword arguments, such as `f=24, **{'f': 24}` for keyword in call_site.duplicated_keywords: self.add_message("repeated-keyword", node=node, args=(keyword,)) diff --git a/tests/regrtest_data/super_init_with_non_self_argument.py b/tests/regrtest_data/super_init_with_non_self_argument.py new file mode 100644 index 0000000000..f8578954e0 --- /dev/null +++ b/tests/regrtest_data/super_init_with_non_self_argument.py @@ -0,0 +1,15 @@ +""" +https://github.com/pylint-dev/pylint/issues/9519 +""" + +# pylint: disable=missing-class-docstring,missing-function-docstring,too-few-public-methods +class Window: + def print_text(self, txt): + print(f'{__class__} {txt}') + + +class Win(Window): + def __init__(self, txt): + super().__init__(txt) + +Win('hello') diff --git a/tests/test_self.py b/tests/test_self.py index 2d9bd70e88..285290dcce 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -341,6 +341,20 @@ def test_wrong_import_position_when_others_disabled(self) -> None: actual_output = actual_output[actual_output.find("\n") :] assert self._clean_paths(expected_output.strip()) == actual_output.strip() + def test_super_init_with_non_self_argument(self) -> None: + module1 = join(HERE, "regrtest_data", "super_init_with_non_self_argument.py") + args = [module1, "-rn", "-sn"] + out = StringIO() + self._run_pylint(args, out=out) + actual_output = self._clean_paths(out.getvalue().strip()) + expected = textwrap.dedent( + f""" + ************* Module super_init_with_non_self_argument + {module1}:13:8: E1121: Too many positional arguments for method call (too-many-function-args) + """ + ) + assert self._clean_paths(expected.strip()) == actual_output.strip() + def test_progress_reporting(self) -> None: module1 = join(HERE, "regrtest_data", "import_something.py") module2 = join(HERE, "regrtest_data", "wrong_import_position.py")