From 4113a8f61ee3e9a8d5ff021f1bc02f96f2f532c7 Mon Sep 17 00:00:00 2001 From: Andrew Haigh Date: Wed, 15 Sep 2021 18:25:15 +1000 Subject: [PATCH 1/4] WIP: fix overzealous filtering of FunctionDef nodes in ClassDef.igetattr Ref #1015. When there are multiple statements defining some attribute ClassDef.igetattr filters out later definitions if they are not in the same scope as the first (to support cases like "self.a = 1; self.a = 2" or "self.items = []; self.items += 1"). However, it checks the scope of the first attribute against the *parent scope* of the later attributes. For mundane statements this makes no difference, but for scope-introducing statements such as FunctionDef these are not the same. --- astroid/nodes/scoped_nodes/scoped_nodes.py | 2 +- tests/test_inference.py | 34 +++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index 9cda4f1be0..e357e0f840 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -2508,7 +2508,7 @@ def igetattr( # to the attribute happening *after* the attribute's definition (e.g. AugAssigns on lists) if len(attributes) > 1: first_attr, attributes = attributes[0], attributes[1:] - first_scope = first_attr.scope() + first_scope = first_attr.parent.scope() attributes = [first_attr] + [ attr for attr in attributes diff --git a/tests/test_inference.py b/tests/test_inference.py index ffd78fe035..0033d834a3 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -30,7 +30,7 @@ ) from astroid import decorators as decoratorsmod from astroid.arguments import CallSite -from astroid.bases import BoundMethod, Instance, UnboundMethod, UnionType +from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod, UnionType from astroid.builder import AstroidBuilder, _extract_single_node, extract_node, parse from astroid.const import IS_PYPY, PY39_PLUS, PY310_PLUS, PY312_PLUS from astroid.context import CallContext, InferenceContext @@ -4321,6 +4321,38 @@ class Test(Outer.Inner): assert isinstance(inferred, nodes.Const) assert inferred.value == 123 + def test_infer_method_empty_body(self) -> None: + # https://github.com/PyCQA/astroid/issues/1015 + node = extract_node( + """ + class A: + def foo(self): ... + + A().foo() #@ + """ + ) + inferred = next(node.infer()) + assert isinstance(inferred, nodes.Const) + assert inferred.value is None + + def test_infer_method_overload(self) -> None: + # https://github.com/PyCQA/astroid/issues/1015 + node = extract_node( + """ + class A: + def foo(self): ... + + def foo(self): + yield + + A().foo() #@ + """ + ) + inferred = list(node.infer()) + assert len(inferred) == 2 + assert isinstance(inferred[0], nodes.Const) + assert isinstance(inferred[1], Generator) + def test_delayed_attributes_without_slots(self) -> None: ast_node = extract_node( """ From 5a86f413705fcb9ccb20d4a7d8716e003420f24b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 24 Feb 2024 19:17:30 -0500 Subject: [PATCH 2/4] Filter to just last declared function --- astroid/nodes/scoped_nodes/scoped_nodes.py | 9 +++++++++ tests/test_inference.py | 5 ++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index e357e0f840..79b7643e55 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -2514,6 +2514,15 @@ def igetattr( for attr in attributes if attr.parent and attr.parent.scope() == first_scope ] + functions = [attr for attr in attributes if isinstance(attr, FunctionDef)] + if functions: + # Prefer only the last function, unless a property is involved. + last_function = functions[-1] + attributes = [ + a + for a in attributes + if a not in functions or a is last_function or bases._is_property(a) + ] for inferred in bases._infer_stmts(attributes, context, frame=self): # yield Uninferable object instead of descriptors when necessary diff --git a/tests/test_inference.py b/tests/test_inference.py index 0033d834a3..b456f89920 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -4349,9 +4349,8 @@ def foo(self): """ ) inferred = list(node.infer()) - assert len(inferred) == 2 - assert isinstance(inferred[0], nodes.Const) - assert isinstance(inferred[1], Generator) + assert len(inferred) == 1 + assert isinstance(inferred[0], Generator) def test_delayed_attributes_without_slots(self) -> None: ast_node = extract_node( From 38bb9c0b18b3a5f3038ef633ce250c411e7d6552 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 25 Feb 2024 08:00:59 -0500 Subject: [PATCH 3/4] Add test for conditionally defined functions --- tests/test_inference.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_inference.py b/tests/test_inference.py index b456f89920..10fceb7b56 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -4352,6 +4352,22 @@ def foo(self): assert len(inferred) == 1 assert isinstance(inferred[0], Generator) + def test_infer_function_under_if(self) -> None: + node = extract_node( + """ + if 1 in [1]: + def func(): + return 42 + else: + def func(): + return False + + func() #@ + """ + ) + inferred = list(node.inferred()) + assert [const.value for const in inferred] == [42, False] + def test_delayed_attributes_without_slots(self) -> None: ast_node = extract_node( """ From bc908f29e17f462c03f9011d366af8002e003528 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 1 Mar 2024 07:32:20 -0500 Subject: [PATCH 4/4] Add news --- ChangeLog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ChangeLog b/ChangeLog index aedde4bdd4..91e34b051e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,17 @@ astroid's ChangeLog =================== +What's New in astroid 3.2.0? +============================ +Release date: TBA + +* ``igetattr()`` returns the last same-named function in a class (instead of + the first). This avoids false positives in pylint with ``@overload``. + + Closes #1015 + Refs pylint-dev/pylint#4696 + + What's New in astroid 3.1.0? ============================ Release date: TBA