From 1425ce72eac55b33a2fb5998946fe5929ccf979a Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 10:08:55 -0600 Subject: [PATCH 01/19] Failing test case. --- .../super_init_with_non_self_argument.py | 15 +++++++++++++++ tests/test_self.py | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/regrtest_data/super_init_with_non_self_argument.py 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..5a273bb45e 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -341,6 +341,22 @@ 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 ] + out = StringIO() + self._run_pylint(args, out=out) + actual_output = self._clean_paths(out.getvalue().strip()) + + expected_output = textwrap.dedent( + f""" + ************* Module super_init_with_non_self_argument + {module1}:11:0: blah + """ + ) + assert "Your code has been rated at 10.00/10" not in actual_output, "bad code should fail." + assert self._clean_paths(expected_output.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") From 791142b74eb2b11f5f3db4eefd7c56c17db88f05 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 10:46:51 -0600 Subject: [PATCH 02/19] Add debug print junk to help understand code, to be removed. --- pylint/checkers/typecheck.py | 39 ++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index edef5188b4..f911fa4720 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1453,45 +1453,69 @@ def visit_call(self, node: nodes.Call) -> None: """Check that called functions/methods are inferred to callable objects, and that passed arguments match the parameters in the inferred function. """ - called = safe_infer(node.func, compare_constructors=True) + def _dp(s, val = None): + if val is None: + print(f" {s}", flush=True) + else: + print(f" {s}: {val}", flush=True) + _dp("-" * 25) + _dp("visit call, node", node) + called = safe_infer(node.func, compare_constructors=True) + _dp("a") self._check_not_callable(node, called) - + _dp("b") try: + _dp("c") called, implicit_args, callable_name = _determine_callable(called) + _dp("d") except ValueError: # Any error occurred during determining the function type, most of # those errors are handled by different warnings. + _dp("e") return + _dp("f") if called.args.args is None: + _dp("g") if called.name == "isinstance": # Verify whether second argument of isinstance is a valid type + _dp("h") self._check_isinstance_args(node, callable_name) # Built-in functions have no argument information. + _dp("i") return + _dp("j") if len(called.argnames()) != len(set(called.argnames())): # Duplicate parameter name (see duplicate-argument). We can't really # make sense of the function call in this case, so just return. + _dp("k") return # Build the set of keyword arguments, checking for duplicate keywords, # and count the positional arguments. + _dp("L") call_site = astroid.arguments.CallSite.from_call(node) # Warn about duplicated keyword arguments, such as `f=24, **{'f': 24}` + _dp("m") for keyword in call_site.duplicated_keywords: + _dp("N") self.add_message("repeated-keyword", node=node, args=(keyword,)) + _dp("O") if call_site.has_invalid_arguments() or call_site.has_invalid_keywords(): # Can't make sense of this. + _dp("p") return # Has the function signature changed in ways we cannot reliably detect? + _dp("q") if hasattr(called, "decorators") and decorated_with( called, self.linter.config.signature_mutators ): + _dp("R") return num_positional_args = len(call_site.positional_arguments) @@ -1523,6 +1547,15 @@ def visit_call(self, node: nodes.Call) -> None: # inside the class where the function is defined. # This avoids emitting `too-many-function-args` since `num_positional_args` # includes an implicit `self` argument which is not present in `called.args`. + _dp("NOTE: about to dec") + _dp("node frame", node.frame()) + _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) + _dp("funcdef", isinstance(called, nodes.FunctionDef)) + _dp("called", called) + _dp("frame body", node.frame().body) + _dp("called in frame body", called in node.frame().body) + _dp("npa", num_positional_args) + _dp("dec names", called.decoratornames()) if ( isinstance(node.frame(), nodes.ClassDef) and isinstance(called, nodes.FunctionDef) @@ -1530,7 +1563,9 @@ def visit_call(self, node: nodes.Call) -> None: and num_positional_args > 0 and "builtins.staticmethod" not in called.decoratornames() ): + _dp("NOTE: decrementing") num_positional_args -= 1 + _dp("NOTE: dec done") # Analyze the list of formal parameters. args = list(itertools.chain(called.args.posonlyargs or (), called.args.args)) From 256bf41771c0fa1b9dbb6b379a1034a041b3137a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:50:28 +0000 Subject: [PATCH 03/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pylint/checkers/typecheck.py | 4 +++- tests/test_self.py | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f911fa4720..ac2d3f1e15 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1453,11 +1453,13 @@ def visit_call(self, node: nodes.Call) -> None: """Check that called functions/methods are inferred to callable objects, and that passed arguments match the parameters in the inferred function. """ - def _dp(s, val = None): + + def _dp(s, val=None): if val is None: print(f" {s}", flush=True) else: print(f" {s}: {val}", flush=True) + _dp("-" * 25) _dp("visit call, node", node) diff --git a/tests/test_self.py b/tests/test_self.py index 5a273bb45e..c64d926f4e 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -343,7 +343,7 @@ def test_wrong_import_position_when_others_disabled(self) -> None: def test_super_init_with_non_self_argument(self) -> None: module1 = join(HERE, "regrtest_data", "super_init_with_non_self_argument.py") - args = [ module1 ] + args = [module1] out = StringIO() self._run_pylint(args, out=out) actual_output = self._clean_paths(out.getvalue().strip()) @@ -354,9 +354,11 @@ def test_super_init_with_non_self_argument(self) -> None: {module1}:11:0: blah """ ) - assert "Your code has been rated at 10.00/10" not in actual_output, "bad code should fail." + assert ( + "Your code has been rated at 10.00/10" not in actual_output + ), "bad code should fail." assert self._clean_paths(expected_output.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") From e648db762ac493d370717daa21a0401f53496d36 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 11:00:49 -0600 Subject: [PATCH 04/19] Less debug cruft. --- pylint/checkers/typecheck.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index ac2d3f1e15..1963cdbd1b 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1455,6 +1455,9 @@ def visit_call(self, node: nodes.Call) -> None: """ def _dp(s, val=None): + if "Attribute.__init__ l.13" not in str(node): + # print("Not in init") + return if val is None: print(f" {s}", flush=True) else: @@ -1480,6 +1483,7 @@ def _dp(s, val=None): _dp("f") if called.args.args is None: _dp("g") + _dp("called.name", called.name) if called.name == "isinstance": # Verify whether second argument of isinstance is a valid type _dp("h") From 9f4828d680f05ea117e59f689daabc29e47181f7 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 11:09:58 -0600 Subject: [PATCH 05/19] Removed excess logging. --- pylint/checkers/typecheck.py | 43 ++++++++++++------------------------ 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 1963cdbd1b..29572785b2 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1467,61 +1467,57 @@ def _dp(s, val=None): _dp("visit call, node", node) called = safe_infer(node.func, compare_constructors=True) - _dp("a") self._check_not_callable(node, called) - _dp("b") try: - _dp("c") called, implicit_args, callable_name = _determine_callable(called) - _dp("d") except ValueError: # Any error occurred during determining the function type, most of # those errors are handled by different warnings. - _dp("e") return - _dp("f") + _dp("Data dump for __init__ call") + call_site = astroid.arguments.CallSite.from_call(node) + num_positional_args = len(call_site.positional_arguments) + _dp("node frame", node.frame()) + _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) + _dp("funcdef", isinstance(called, nodes.FunctionDef)) + _dp("called", called) + _dp("frame body", node.frame().body) + _dp("called in frame body", called in node.frame().body) + _dp("npa", num_positional_args) + _dp("dec names", called.decoratornames()) + if called.args.args is None: - _dp("g") + _dp("called.args.args is None") _dp("called.name", called.name) if called.name == "isinstance": # Verify whether second argument of isinstance is a valid type - _dp("h") self._check_isinstance_args(node, callable_name) # Built-in functions have no argument information. - _dp("i") + _dp("Returning now") return - _dp("j") if len(called.argnames()) != len(set(called.argnames())): # Duplicate parameter name (see duplicate-argument). We can't really # make sense of the function call in this case, so just return. - _dp("k") return # Build the set of keyword arguments, checking for duplicate keywords, # and count the positional arguments. - _dp("L") call_site = astroid.arguments.CallSite.from_call(node) # Warn about duplicated keyword arguments, such as `f=24, **{'f': 24}` - _dp("m") for keyword in call_site.duplicated_keywords: - _dp("N") self.add_message("repeated-keyword", node=node, args=(keyword,)) - _dp("O") if call_site.has_invalid_arguments() or call_site.has_invalid_keywords(): # Can't make sense of this. - _dp("p") return # Has the function signature changed in ways we cannot reliably detect? - _dp("q") if hasattr(called, "decorators") and decorated_with( called, self.linter.config.signature_mutators ): - _dp("R") return num_positional_args = len(call_site.positional_arguments) @@ -1553,15 +1549,6 @@ def _dp(s, val=None): # inside the class where the function is defined. # This avoids emitting `too-many-function-args` since `num_positional_args` # includes an implicit `self` argument which is not present in `called.args`. - _dp("NOTE: about to dec") - _dp("node frame", node.frame()) - _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) - _dp("funcdef", isinstance(called, nodes.FunctionDef)) - _dp("called", called) - _dp("frame body", node.frame().body) - _dp("called in frame body", called in node.frame().body) - _dp("npa", num_positional_args) - _dp("dec names", called.decoratornames()) if ( isinstance(node.frame(), nodes.ClassDef) and isinstance(called, nodes.FunctionDef) @@ -1569,9 +1556,7 @@ def _dp(s, val=None): and num_positional_args > 0 and "builtins.staticmethod" not in called.decoratornames() ): - _dp("NOTE: decrementing") num_positional_args -= 1 - _dp("NOTE: dec done") # Analyze the list of formal parameters. args = list(itertools.chain(called.args.posonlyargs or (), called.args.args)) From f7a6b15ce007920fb43ad33999cb924f7f5f1908 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 12:28:04 -0600 Subject: [PATCH 06/19] Handle built-in __init__ call with bad args. --- pylint/checkers/typecheck.py | 52 +++++++++++++++++++++++++----------- tests/test_self.py | 12 +++------ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 29572785b2..a9c39d5c88 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1455,13 +1455,13 @@ def visit_call(self, node: nodes.Call) -> None: """ def _dp(s, val=None): - if "Attribute.__init__ l.13" not in str(node): - # print("Not in init") - return - if val is None: - print(f" {s}", flush=True) - else: - print(f" {s}: {val}", flush=True) + return + ## if "Attribute.__init__" not in str(node): + ## return + ## if val is None: + ## print(f" {s}", flush=True) + ## else: + ## print(f" {s}: {val}", flush=True) _dp("-" * 25) _dp("visit call, node", node) @@ -1477,24 +1477,44 @@ def _dp(s, val=None): _dp("Data dump for __init__ call") call_site = astroid.arguments.CallSite.from_call(node) - num_positional_args = len(call_site.positional_arguments) + _dp("call_site", call_site) + # _dp("call_site args", call_site.arguments) + # _dp("call site positional args:", call_site.positional_arguments) + # _dp("call site keyword args:", call_site.keyword_arguments) + # _dp("call site invalid args", call_site.has_invalid_arguments()) + # _dp("call site inv keywords", call_site.has_invalid_keywords()) + _dp("node args", node.args) _dp("node frame", node.frame()) - _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) - _dp("funcdef", isinstance(called, nodes.FunctionDef)) + # _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) + # _dp("funcdef", isinstance(called, nodes.FunctionDef)) _dp("called", called) + _dp("bound method init in called", "BoundMethod __init__ of builtins.object" in str(called)) + _dp("called.args", called.args) _dp("frame body", node.frame().body) - _dp("called in frame body", called in node.frame().body) - _dp("npa", num_positional_args) - _dp("dec names", called.decoratornames()) + # _dp("called in frame body", called in node.frame().body) + # _dp("dec names", called.decoratornames()) + + def _call_site_has_args(cs): + "True if any args passed." + has_args = ( + False + or len(cs.positional_arguments) > 0 + or len(cs.keyword_arguments.items()) > 0 + or cs.starargs is not None + or cs.kwargs is not None + ) + return has_args if called.args.args is None: - _dp("called.args.args is None") - _dp("called.name", called.name) 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. - _dp("Returning now") + # Check built-in __init__ ... a user-defined __init__ function + # is handled elsewhere. + if "BoundMethod __init__ of builtins.object" in str(called): + if _call_site_has_args(call_site): + self.add_message("too-many-function-args", node=node, args=("__init__",)) return if len(called.argnames()) != len(set(called.argnames())): diff --git a/tests/test_self.py b/tests/test_self.py index c64d926f4e..3f7e895715 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -343,21 +343,17 @@ def test_wrong_import_position_when_others_disabled(self) -> None: def test_super_init_with_non_self_argument(self) -> None: module1 = join(HERE, "regrtest_data", "super_init_with_non_self_argument.py") - args = [module1] + args = [module1, "-rn", "-sn"] out = StringIO() self._run_pylint(args, out=out) actual_output = self._clean_paths(out.getvalue().strip()) - - expected_output = textwrap.dedent( + expected = textwrap.dedent( f""" ************* Module super_init_with_non_self_argument - {module1}:11:0: blah + {module1}:13:8: E1121: Too many positional arguments for __init__ call (too-many-function-args) """ ) - assert ( - "Your code has been rated at 10.00/10" not in actual_output - ), "bad code should fail." - assert self._clean_paths(expected_output.strip()) == actual_output.strip() + assert self._clean_paths(expected.strip()) == actual_output.strip() def test_progress_reporting(self) -> None: module1 = join(HERE, "regrtest_data", "import_something.py") From 97c933957688b6909457215904a48319c7aab1a4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:29:08 +0000 Subject: [PATCH 07/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pylint/checkers/typecheck.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index a9c39d5c88..f6e5ac3d13 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1488,14 +1488,17 @@ def _dp(s, val=None): # _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) # _dp("funcdef", isinstance(called, nodes.FunctionDef)) _dp("called", called) - _dp("bound method init in called", "BoundMethod __init__ of builtins.object" in str(called)) + _dp( + "bound method init in called", + "BoundMethod __init__ of builtins.object" in str(called), + ) _dp("called.args", called.args) _dp("frame body", node.frame().body) # _dp("called in frame body", called in node.frame().body) # _dp("dec names", called.decoratornames()) def _call_site_has_args(cs): - "True if any args passed." + """True if any args passed.""" has_args = ( False or len(cs.positional_arguments) > 0 @@ -1514,7 +1517,9 @@ def _call_site_has_args(cs): # is handled elsewhere. if "BoundMethod __init__ of builtins.object" in str(called): if _call_site_has_args(call_site): - self.add_message("too-many-function-args", node=node, args=("__init__",)) + self.add_message( + "too-many-function-args", node=node, args=("__init__",) + ) return if len(called.argnames()) != len(set(called.argnames())): From 161cb5bc34142b836d4757bdd3ae94f1b007072c Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 12:32:59 -0600 Subject: [PATCH 08/19] Separate dev cruft from code. --- pylint/checkers/typecheck.py | 67 +++++++++++++++++------------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f6e5ac3d13..a4589e1cbc 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1454,18 +1454,6 @@ def visit_call(self, node: nodes.Call) -> None: and that passed arguments match the parameters in the inferred function. """ - def _dp(s, val=None): - return - ## if "Attribute.__init__" not in str(node): - ## return - ## if val is None: - ## print(f" {s}", flush=True) - ## else: - ## print(f" {s}: {val}", flush=True) - - _dp("-" * 25) - _dp("visit call, node", node) - called = safe_infer(node.func, compare_constructors=True) self._check_not_callable(node, called) try: @@ -1475,27 +1463,38 @@ def _dp(s, val=None): # those errors are handled by different warnings. return - _dp("Data dump for __init__ call") + # Build the set of keyword arguments, checking for duplicate keywords, + # and count the positional arguments. call_site = astroid.arguments.CallSite.from_call(node) - _dp("call_site", call_site) - # _dp("call_site args", call_site.arguments) - # _dp("call site positional args:", call_site.positional_arguments) - # _dp("call site keyword args:", call_site.keyword_arguments) - # _dp("call site invalid args", call_site.has_invalid_arguments()) - # _dp("call site inv keywords", call_site.has_invalid_keywords()) - _dp("node args", node.args) - _dp("node frame", node.frame()) - # _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) - # _dp("funcdef", isinstance(called, nodes.FunctionDef)) - _dp("called", called) - _dp( - "bound method init in called", - "BoundMethod __init__ of builtins.object" in str(called), - ) - _dp("called.args", called.args) - _dp("frame body", node.frame().body) - # _dp("called in frame body", called in node.frame().body) - # _dp("dec names", called.decoratornames()) + + ### Debug cruft used during dev, will remove when done. + ### def _dp(s, val=None): + ### return + ### ## if "Attribute.__init__" not in str(node): + ### ## return + ### ## if val is None: + ### ## print(f" {s}", flush=True) + ### ## else: + ### ## print(f" {s}: {val}", flush=True) + ### _dp("-" * 25) + ### _dp("visit call, node", node) + ### _dp("Data dump for __init__ call") + ### _dp("call_site", call_site) + ### _dp("call_site args", call_site.arguments) + ### _dp("call site positional args:", call_site.positional_arguments) + ### _dp("call site keyword args:", call_site.keyword_arguments) + ### _dp("call site invalid args", call_site.has_invalid_arguments()) + ### _dp("call site inv keywords", call_site.has_invalid_keywords()) + ### _dp("node args", node.args) + ### _dp("node frame", node.frame()) + ### _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) + ### _dp("funcdef", isinstance(called, nodes.FunctionDef)) + ### _dp("called", called) + ### _dp("bound method init in called", "BoundMethod __init__ of builtins.object" in str(called)) + ### _dp("called.args", called.args) + ### _dp("frame body", node.frame().body) + ### _dp("called in frame body", called in node.frame().body) + ### _dp("dec names", called.decoratornames()) def _call_site_has_args(cs): """True if any args passed.""" @@ -1527,10 +1526,6 @@ def _call_site_has_args(cs): # 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,)) From ccc61d5f852bc03edddbc31898182a1dd1941d77 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 12:36:15 -0600 Subject: [PATCH 09/19] Restore original spacing. --- pylint/checkers/typecheck.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index a4589e1cbc..0c57cbdaf9 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1453,9 +1453,10 @@ def visit_call(self, node: nodes.Call) -> None: """Check that called functions/methods are inferred to callable objects, and that passed arguments match the parameters in the inferred function. """ - called = safe_infer(node.func, compare_constructors=True) + self._check_not_callable(node, called) + try: called, implicit_args, callable_name = _determine_callable(called) except ValueError: From d1d3de191be8cb39124ce9ca135d373d70a0cdb4 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 12:46:38 -0600 Subject: [PATCH 10/19] Bad api usage. --- pylint/checkers/typecheck.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 0c57cbdaf9..8754546207 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1503,8 +1503,8 @@ def _call_site_has_args(cs): False or len(cs.positional_arguments) > 0 or len(cs.keyword_arguments.items()) > 0 - or cs.starargs is not None - or cs.kwargs is not None + # or cs.starargs is not None + # or cs.kwargs is not None ) return has_args From 03f53c0c50b3c5208cd5c5b56bb72e9af9f6d97d Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Fri, 17 Jan 2025 12:49:37 -0600 Subject: [PATCH 11/19] Fix mypy. --- pylint/checkers/typecheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 8754546207..f9e6c0cec8 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1497,7 +1497,7 @@ def visit_call(self, node: nodes.Call) -> None: ### _dp("called in frame body", called in node.frame().body) ### _dp("dec names", called.decoratornames()) - def _call_site_has_args(cs): + def _call_site_has_args(cs: arguments.CallSite) -> bool: """True if any args passed.""" has_args = ( False From 6b5bd9f95bb44b2a30b408794cfa95dc442c1b1b Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Tue, 11 Mar 2025 12:57:08 -0600 Subject: [PATCH 12/19] Remove debug/dev cruft. --- pylint/checkers/typecheck.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f9e6c0cec8..3aecbc69a0 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1468,35 +1468,6 @@ def visit_call(self, node: nodes.Call) -> None: # and count the positional arguments. call_site = astroid.arguments.CallSite.from_call(node) - ### Debug cruft used during dev, will remove when done. - ### def _dp(s, val=None): - ### return - ### ## if "Attribute.__init__" not in str(node): - ### ## return - ### ## if val is None: - ### ## print(f" {s}", flush=True) - ### ## else: - ### ## print(f" {s}: {val}", flush=True) - ### _dp("-" * 25) - ### _dp("visit call, node", node) - ### _dp("Data dump for __init__ call") - ### _dp("call_site", call_site) - ### _dp("call_site args", call_site.arguments) - ### _dp("call site positional args:", call_site.positional_arguments) - ### _dp("call site keyword args:", call_site.keyword_arguments) - ### _dp("call site invalid args", call_site.has_invalid_arguments()) - ### _dp("call site inv keywords", call_site.has_invalid_keywords()) - ### _dp("node args", node.args) - ### _dp("node frame", node.frame()) - ### _dp("isinst", isinstance(node.frame(), nodes.ClassDef)) - ### _dp("funcdef", isinstance(called, nodes.FunctionDef)) - ### _dp("called", called) - ### _dp("bound method init in called", "BoundMethod __init__ of builtins.object" in str(called)) - ### _dp("called.args", called.args) - ### _dp("frame body", node.frame().body) - ### _dp("called in frame body", called in node.frame().body) - ### _dp("dec names", called.decoratornames()) - def _call_site_has_args(cs: arguments.CallSite) -> bool: """True if any args passed.""" has_args = ( From e13c11a0db8ce077f5c4d696cdd6144c764ef0fd Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Tue, 11 Mar 2025 13:06:19 -0600 Subject: [PATCH 13/19] Replace code with PR comments as-is. --- pylint/checkers/typecheck.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 3aecbc69a0..f9988cd440 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1453,6 +1453,19 @@ def visit_call(self, node: nodes.Call) -> None: """Check that called functions/methods are inferred to callable objects, and that passed arguments match the parameters in the inferred function. """ + + is_super = False + 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): + is_super = True + called = safe_infer(node.func, compare_constructors=True) self._check_not_callable(node, called) @@ -1483,13 +1496,19 @@ def _call_site_has_args(cs: arguments.CallSite) -> bool: 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. - # Check built-in __init__ ... a user-defined __init__ function - # is handled elsewhere. - if "BoundMethod __init__ of builtins.object" in str(called): - if _call_site_has_args(call_site): + # Built-in functions have no argument information, but we know + # super() only takes self. + if is_super and utils.is_builtin_object(called): + if ( + (call_site := astroid.arguments.CallSite.from_call(node)) + 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=("__init__",) + "too-many-function-args", + node=node, + args=(callable_name,), ) return From 5e8aeddc0d774b93fb3086d1eede3bd0252e3f81 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Tue, 11 Mar 2025 13:07:21 -0600 Subject: [PATCH 14/19] Update test so it passes with feedback code. --- tests/test_self.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_self.py b/tests/test_self.py index 3f7e895715..285290dcce 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -350,7 +350,7 @@ def test_super_init_with_non_self_argument(self) -> None: expected = textwrap.dedent( f""" ************* Module super_init_with_non_self_argument - {module1}:13:8: E1121: Too many positional arguments for __init__ call (too-many-function-args) + {module1}:13:8: E1121: Too many positional arguments for method call (too-many-function-args) """ ) assert self._clean_paths(expected.strip()) == actual_output.strip() From b79e04dffd4b9dcd0aa0e13d0345317bc98c12dc Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Tue, 11 Mar 2025 13:09:12 -0600 Subject: [PATCH 15/19] Remove unused helper method. --- pylint/checkers/typecheck.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f9988cd440..2e49d52895 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1481,17 +1481,6 @@ def visit_call(self, node: nodes.Call) -> None: # and count the positional arguments. call_site = astroid.arguments.CallSite.from_call(node) - def _call_site_has_args(cs: arguments.CallSite) -> bool: - """True if any args passed.""" - has_args = ( - False - or len(cs.positional_arguments) > 0 - or len(cs.keyword_arguments.items()) > 0 - # or cs.starargs is not None - # or cs.kwargs is not None - ) - return has_args - if called.args.args is None: if called.name == "isinstance": # Verify whether second argument of isinstance is a valid type From cccaaf13203ba3f2c8d17b6792f0c9f3772a2d53 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Tue, 11 Mar 2025 13:19:10 -0600 Subject: [PATCH 16/19] Add _is_super helper check --- pylint/checkers/typecheck.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 2e49d52895..9f9b48de76 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, From 15850fd69366dd0ec8d21c234677e7120487b353 Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Tue, 11 Mar 2025 13:19:53 -0600 Subject: [PATCH 17/19] Use _is_super helper. --- pylint/checkers/typecheck.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 9f9b48de76..d727289e35 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1467,19 +1467,6 @@ def visit_call(self, node: nodes.Call) -> None: """Check that called functions/methods are inferred to callable objects, and that passed arguments match the parameters in the inferred function. """ - - is_super = False - 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): - is_super = True - called = safe_infer(node.func, compare_constructors=True) self._check_not_callable(node, called) @@ -1501,7 +1488,7 @@ def visit_call(self, node: nodes.Call) -> None: self._check_isinstance_args(node, callable_name) # Built-in functions have no argument information, but we know # super() only takes self. - if is_super and utils.is_builtin_object(called): + if self._is_super(node) and utils.is_builtin_object(called): if ( (call_site := astroid.arguments.CallSite.from_call(node)) and call_site.positional_arguments From 41ae494aaeafe8249d0f673fab14d950c54b9808 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:21:25 +0000 Subject: [PATCH 18/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pylint/checkers/typecheck.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index d727289e35..14589d6794 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1449,7 +1449,7 @@ def _check_isinstance_args(self, node: nodes.Call, callable_name: str) -> None: ) def _is_super(self, node: nodes.Call) -> bool: - "True if this node is a call to super()." + """True if this node is a call to super().""" if ( isinstance(node.func, nodes.Attribute) and node.func.attrname == "__init__" @@ -1493,7 +1493,9 @@ def visit_call(self, node: nodes.Call) -> None: (call_site := astroid.arguments.CallSite.from_call(node)) 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") + and not ( + isinstance(first_arg, nodes.Name) and first_arg.name == "self" + ) ): self.add_message( "too-many-function-args", From 68bd688fd612623598022894b9814e80e4a6eb5d Mon Sep 17 00:00:00 2001 From: Jeff Zohrab Date: Tue, 11 Mar 2025 13:23:45 -0600 Subject: [PATCH 19/19] call_site already assigned identically only line 1483 --- pylint/checkers/typecheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 14589d6794..aa6ece436f 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1490,7 +1490,7 @@ def visit_call(self, node: nodes.Call) -> None: # super() only takes self. if self._is_super(node) and utils.is_builtin_object(called): if ( - (call_site := astroid.arguments.CallSite.from_call(node)) + call_site and call_site.positional_arguments and (first_arg := call_site.positional_arguments[0]) and not (