diff --git a/ChangeLog b/ChangeLog index ec4a9860a7..d102e2ddb0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,11 @@ Release date: TBA * Fix ``col_offset`` attribute for nodes involving ``with`` on ``PyPy``. +* Made ``FunctionDef.implicit_parameters`` return 1 for methods by making + ``FunctionDef.is_bound`` return ``True``, as it does for class methods. + + Closes PyCQA/pylint#6464 + * Fixed a crash when ``_filter_stmts`` encounters an ``EmptyNode``. Closes PyCQA/pylint#6438 diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index 0c03f205f1..8d63d277f0 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -1581,6 +1581,9 @@ def blockstart_tolineno(self): """ return self.args.tolineno + def implicit_parameters(self) -> Literal[0, 1]: + return 1 if self.is_bound() else 0 + def block_range(self, lineno): """Get a range from the given line number to where this node ends. @@ -1642,7 +1645,7 @@ def is_bound(self): False otherwise. :rtype: bool """ - return self.type == "classmethod" + return self.type in {"method", "classmethod"} def is_abstract(self, pass_is_abstract=True, any_raise_is_abstract=False): """Check if the method is abstract. diff --git a/tests/unittest_brain_qt.py b/tests/unittest_brain_qt.py index 18e0d6d3e8..f9805bce77 100644 --- a/tests/unittest_brain_qt.py +++ b/tests/unittest_brain_qt.py @@ -16,6 +16,8 @@ @pytest.mark.skipif(HAS_PYQT6 is None, reason="This test requires the PyQt6 library.") class TestBrainQt: + AstroidManager.brain["extension_package_whitelist"] = {"PyQt6"} + @staticmethod def test_value_of_lambda_instance_attrs_is_list(): """Regression test for https://github.com/PyCQA/pylint/issues/6221 @@ -28,7 +30,6 @@ def test_value_of_lambda_instance_attrs_is_list(): from PyQt6 import QtPrintSupport as printsupport printsupport.QPrintPreviewDialog.paintRequested #@ """ - AstroidManager.brain["extension_package_whitelist"] = {"PyQt6.QtPrintSupport"} node = extract_node(src) attribute_node = node.inferred()[0] if attribute_node is Uninferable: @@ -36,3 +37,18 @@ def test_value_of_lambda_instance_attrs_is_list(): assert isinstance(attribute_node, UnboundMethod) # scoped_nodes.Lambda.instance_attrs is typed as Dict[str, List[NodeNG]] assert isinstance(attribute_node.instance_attrs["connect"][0], FunctionDef) + + @staticmethod + def test_implicit_parameters() -> None: + """Regression test for https://github.com/PyCQA/pylint/issues/6464""" + src = """ + from PyQt6.QtCore import QTimer + timer = QTimer() + timer.timeout.connect #@ + """ + node = extract_node(src) + attribute_node = node.inferred()[0] + if attribute_node is Uninferable: + pytest.skip("PyQt6 C bindings may not be installed?") + assert isinstance(attribute_node, FunctionDef) + assert attribute_node.implicit_parameters() == 1 diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py index 6d1652eb10..cf01a5f7e4 100644 --- a/tests/unittest_scoped_nodes.py +++ b/tests/unittest_scoped_nodes.py @@ -617,6 +617,24 @@ def test(): self.assertIsInstance(one, nodes.Const) self.assertEqual(one.value, 1) + def test_func_is_bound(self) -> None: + data = """ + class MyClass: + def bound(): #@ + pass + """ + func = builder.extract_node(data) + self.assertIs(func.is_bound(), True) + self.assertEqual(func.implicit_parameters(), 1) + + data2 = """ + def not_bound(): #@ + pass + """ + func2 = builder.extract_node(data2) + self.assertIs(func2.is_bound(), False) + self.assertEqual(func2.implicit_parameters(), 0) + def test_type_builtin_descriptor_subclasses(self) -> None: astroid = builder.parse( """