From 91c7108c496dd9fa73eb3002c4a983f354d30630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 25 Oct 2021 00:15:05 +0200 Subject: [PATCH 1/4] Refactor and add typing to ``NodeNG.frame()`` --- astroid/nodes/node_ng.py | 11 ++++--- astroid/nodes/scoped_nodes.py | 60 ++++++++++++++++++++++------------ tests/unittest_scoped_nodes.py | 50 ++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 26 deletions(-) diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index c9aa0e0e8b..e6d0d50b1b 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -25,7 +25,7 @@ from astroid.nodes.const import OP_PRECEDENCE if TYPE_CHECKING: - from astroid.nodes import LocalsDictNodeNG + from astroid import nodes # Types for 'NodeNG.nodes_of_class()' T_Nodes = TypeVar("T_Nodes", bound="NodeNG") @@ -258,18 +258,19 @@ def statement(self): return self return self.parent.statement() - def frame(self): + def frame( + self, + ) -> Union["nodes.FunctionDef", "nodes.Module", "nodes.ClassDef", "nodes.Lambda"]: """The first parent frame node. A frame node is a :class:`Module`, :class:`FunctionDef`, - or :class:`ClassDef`. + :class:`ClassDef` or :class:`Lambda`. :returns: The first parent frame node. - :rtype: Module or FunctionDef or ClassDef """ return self.parent.frame() - def scope(self) -> "LocalsDictNodeNG": + def scope(self) -> "nodes.LocalsDictNodeNG": """The first parent node defining a new scope. These can be Module, FunctionDef, ClassDef, Lambda, or GeneratorExp nodes. diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index e9ccd4a2e1..b8da0ac0fb 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -230,17 +230,6 @@ def qname(self): return self.name return f"{self.parent.frame().qname()}.{self.name}" - def frame(self): - """The first parent frame node. - - A frame node is a :class:`Module`, :class:`FunctionDef`, - or :class:`ClassDef`. - - :returns: The first parent frame node. - :rtype: Module or FunctionDef or ClassDef - """ - return self - def scope(self: T) -> T: """The first parent node defining a new scope. @@ -826,20 +815,19 @@ def bool_value(self, context=None): def get_children(self): yield from self.body - -class ComprehensionScope(LocalsDictNodeNG): - """Scoping for different types of comprehensions.""" - - def frame(self): - """The first parent frame node. + def frame(self: T) -> T: + """The node's frame node. A frame node is a :class:`Module`, :class:`FunctionDef`, - or :class:`ClassDef`. + :class:`ClassDef` or :class:`Lambda`. - :returns: The first parent frame node. - :rtype: Module or FunctionDef or ClassDef + :returns: The node itself. """ - return self.parent.frame() + return self + + +class ComprehensionScope(LocalsDictNodeNG): + """Scoping for different types of comprehensions.""" scope_lookup = LocalsDictNodeNG._scope_lookup @@ -1344,6 +1332,16 @@ def get_children(self): yield self.args yield self.body + def frame(self: T) -> T: + """The node's frame node. + + A frame node is a :class:`Module`, :class:`FunctionDef`, + :class:`ClassDef` or :class:`Lambda`. + + :returns: The node itself. + """ + return self + class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): """Class representing an :class:`ast.FunctionDef`. @@ -1839,6 +1837,16 @@ def scope_lookup(self, node, name, offset=0): return self, [frame] return super().scope_lookup(node, name, offset) + def frame(self: T) -> T: + """The node's frame node. + + A frame node is a :class:`Module`, :class:`FunctionDef`, + :class:`ClassDef` or :class:`Lambda`. + + :returns: The node itself. + """ + return self + class AsyncFunctionDef(FunctionDef): """Class representing an :class:`ast.FunctionDef` node. @@ -3054,3 +3062,13 @@ def _get_assign_nodes(self): child_node._get_assign_nodes() for child_node in self.body ) return list(itertools.chain.from_iterable(children_assign_nodes)) + + def frame(self: T) -> T: + """The node's frame node. + + A frame node is a :class:`Module`, :class:`FunctionDef`, + :class:`ClassDef` or :class:`Lambda`. + + :returns: The node itself. + """ + return self diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py index 327ed44593..788fed21b8 100644 --- a/tests/unittest_scoped_nodes.py +++ b/tests/unittest_scoped_nodes.py @@ -2289,5 +2289,55 @@ class First(object, object): #@ astroid["First"].slots() +class TestFrameNodes: + + @staticmethod + def test_frame_node(): + """Test if the frame of FunctionDef, ClassDef and Module is correctly set""" + module = builder.parse( + """ + def func(): + var_1 = x + return var_1 + + class MyClass: + + attribute = 1 + + def method(): + pass + + VAR = lambda y = (named_expr_3 := "walrus"): print(y) + """ + ) + function = module.body[0] + assert function.frame() == function + assert function.body[0].frame() == function + + class_node = module.body[1] + assert class_node.frame() == class_node + assert class_node.body[0].frame() == class_node + assert class_node.body[1].frame() == class_node.body[1] + + lambda_assignment = module.body[2].value + assert lambda_assignment.args.args[0].frame() == lambda_assignment + + assert module.frame() == module + + @staticmethod + def test_non_frame_node(): + """Test if the frame of non frame nodes is set correctly""" + module = builder.parse( + """ + VAR_ONE = 1 + + VAR_TWO = [x for x in range(1)] + """ + ) + assert module.body[0].frame() == module + + assert module.body[1].value.locals["x"][0].frame() == module + + if __name__ == "__main__": unittest.main() From 64323a475c14f0a425a990ccb9b91dffca1f442c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Oct 2021 22:17:42 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/unittest_scoped_nodes.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py index 788fed21b8..4a5756e1ba 100644 --- a/tests/unittest_scoped_nodes.py +++ b/tests/unittest_scoped_nodes.py @@ -2290,7 +2290,6 @@ class First(object, object): #@ class TestFrameNodes: - @staticmethod def test_frame_node(): """Test if the frame of FunctionDef, ClassDef and Module is correctly set""" @@ -2303,10 +2302,10 @@ def func(): class MyClass: attribute = 1 - + def method(): pass - + VAR = lambda y = (named_expr_3 := "walrus"): print(y) """ ) @@ -2330,7 +2329,7 @@ def test_non_frame_node(): module = builder.parse( """ VAR_ONE = 1 - + VAR_TWO = [x for x in range(1)] """ ) From 6ab7d42ce3c457236ae421a1554b8f21d40745cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 25 Oct 2021 00:24:31 +0200 Subject: [PATCH 3/4] Skip test --- tests/unittest_scoped_nodes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py index 788fed21b8..97d489e055 100644 --- a/tests/unittest_scoped_nodes.py +++ b/tests/unittest_scoped_nodes.py @@ -44,6 +44,7 @@ from astroid import MANAGER, builder, nodes, objects, test_utils, util from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod +from astroid.const import PY38_PLUS from astroid.exceptions import ( AttributeInferenceError, DuplicateBasesError, @@ -2291,6 +2292,7 @@ class First(object, object): #@ class TestFrameNodes: + @pytest.mark.skipif(not PY38_PLUS, reason="needs assignment expressions") @staticmethod def test_frame_node(): """Test if the frame of FunctionDef, ClassDef and Module is correctly set""" @@ -2307,7 +2309,7 @@ class MyClass: def method(): pass - VAR = lambda y = (named_expr_3 := "walrus"): print(y) + VAR = lambda y = (named_expr := "walrus"): print(y) """ ) function = module.body[0] From 98c6f3e1f340ce1dbc4a23633df31b3cea4a6487 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Oct 2021 22:26:10 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/unittest_scoped_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py index 7a8cf458e5..7a44b105e7 100644 --- a/tests/unittest_scoped_nodes.py +++ b/tests/unittest_scoped_nodes.py @@ -2307,7 +2307,7 @@ class MyClass: def method(): pass - + VAR = lambda y = (named_expr := "walrus"): print(y) """ )