From 247a3d825deb37a83bd326c7d84257357afbe961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 24 Oct 2021 14:31:09 +0200 Subject: [PATCH 1/7] Change ``frame`` and ``scope`` of ``NamedExpr`` for certain parents --- astroid/nodes/node_classes.py | 40 ++++++++++++++ tests/unittest_nodes.py | 98 +++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index c27b768926..80740a67f4 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -4225,6 +4225,46 @@ def postinit(self, target: NodeNG, value: NodeNG) -> None: self.target = target self.value = value + 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. + """ + if not self.parent: + raise ParentMissingError(target=self) + + # For certain parents NamedExpr evaluate to the scope of the parent + if isinstance(self.parent, (Arguments, Keyword)): + if not self.parent.parent: + raise ParentMissingError(target=self.parent) + if not self.parent.parent.parent: + raise ParentMissingError(target=self.parent.parent) + return self.parent.parent.parent.frame() + + return self.parent.frame() + + def scope(self) -> "LocalsDictNodeNG": + """The first parent node defining a new scope. + These can be Module, FunctionDef, ClassDef, Lambda, or GeneratorExp nodes. + + :returns: The first parent scope node. + """ + if not self.parent: + raise ParentMissingError(target=self) + + # For certain parents NamedExpr evaluate to the scope of the parent + if isinstance(self.parent, (Arguments, Keyword)): + if not self.parent.parent: + raise ParentMissingError(target=self.parent) + if not self.parent.parent.parent: + raise ParentMissingError(target=self.parent.parent) + return self.parent.parent.parent.scope() + + return self.parent.scope() + class Unknown(mixins.AssignTypeMixin, NodeNG): """This node represents a node in a constructed AST where diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 81c8379f45..0661630ef4 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -682,6 +682,104 @@ def hello(False): builder.parse(code) +class NamedExprNodeTest(unittest.TestCase): + """Tests for the NamedExpr node""" + + def test_frame(self) -> None: + """Test if the frame of NamedExpr is correctly set for certain types + of parent nodes. + """ + module = builder.parse( + """ + def func(var_1): + pass + + def func_two(var_2, var_2 = (named_expr_1 := "walrus")): + pass + + class MyBaseClass: + pass + + class MyInheritedClass(MyBaseClass, var_3=(named_expr_2 := "walrus")): + pass + + VAR = lambda y = (named_expr_3 := "walrus"): print(y) + + def func_with_lambda( + var_5 = ( + named_expr_4 := lambda y = (named_expr_5 := "walrus"): y + ) + ): + pass + """ + ) + function = module.body[0] + assert function.args.frame() == function + + function_two = module.body[1] + assert function_two.args.args[0].frame() == function_two + assert function_two.args.args[1].frame() == function_two + assert function_two.args.defaults[0].frame() == module + + inherited_class = module.body[3] + assert inherited_class.keywords[0].frame() == inherited_class + assert inherited_class.keywords[0].value.frame() == module + + lambda_assignment = module.body[4].value + assert lambda_assignment.args.args[0].frame() == lambda_assignment + assert lambda_assignment.args.defaults[0].frame() == module + + lambda_named_expr = module.body[5].args.defaults[0] + assert lambda_named_expr.value.args.defaults[0].frame() == module + + def test_scope(self) -> None: + """Test if the scope of NamedExpr is correctly set for certain types + of parent nodes. + """ + module = builder.parse( + """ + def func(var_1): + pass + + def func_two(var_2, var_2 = (named_expr_1 := "walrus")): + pass + + class MyBaseClass: + pass + + class MyInheritedClass(MyBaseClass, var_3=(named_expr_2 := "walrus")): + pass + + VAR = lambda y = (named_expr_3 := "walrus"): print(y) + + def func_with_lambda( + var_5 = ( + named_expr_4 := lambda y = (named_expr_5 := "walrus"): y + ) + ): + pass + """ + ) + function = module.body[0] + assert function.args.scope() == function + + function_two = module.body[1] + assert function_two.args.args[0].scope() == function_two + assert function_two.args.args[1].scope() == function_two + assert function_two.args.defaults[0].scope() == module + + inherited_class = module.body[3] + assert inherited_class.keywords[0].scope() == inherited_class + assert inherited_class.keywords[0].value.scope() == module + + lambda_assignment = module.body[4].value + assert lambda_assignment.args.args[0].scope() == lambda_assignment + assert lambda_assignment.args.defaults[0].scope() + + lambda_named_expr = module.body[5].args.defaults[0] + assert lambda_named_expr.value.args.defaults[0].scope() == module + + class AnnAssignNodeTest(unittest.TestCase): def test_primitive(self) -> None: code = textwrap.dedent( From acd25f210092a9247006b902e6dbe35bddf012d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 24 Oct 2021 14:37:54 +0200 Subject: [PATCH 2/7] Add skip to test for <3.8 --- tests/unittest_nodes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 0661630ef4..a1415cc2e4 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -682,6 +682,7 @@ def hello(False): builder.parse(code) +@pytest.mark.skipif(not PY38_PLUS, reason="needs assignment expressions") class NamedExprNodeTest(unittest.TestCase): """Tests for the NamedExpr node""" From d9b602842f78a2cac81087cb7c5c82a063e880f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 24 Oct 2021 17:01:55 +0200 Subject: [PATCH 3/7] Fix the `scope()` and `frame()` methods of `NamedExpr` nodes --- ChangeLog | 5 ++++- astroid/nodes/node_classes.py | 4 ++-- tests/unittest_nodes.py | 18 +++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 98b014a5a0..ee93e13c09 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,7 +12,10 @@ What's New in astroid 2.8.4? ============================ Release date: TBA - +* Fix the `scope()` and `frame()` methods of `NamedExpr` nodes. + When these nodes occur in `Arguments`, `Keyword` or `Comprehension` nodes these + methods now correctly point to the outer-scope of the `FunctionDef`, + `ClassDef` or comprehension. What's New in astroid 2.8.3? ============================ diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 80740a67f4..cb1d09c9f0 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -4237,7 +4237,7 @@ def frame(self): raise ParentMissingError(target=self) # For certain parents NamedExpr evaluate to the scope of the parent - if isinstance(self.parent, (Arguments, Keyword)): + if isinstance(self.parent, (Arguments, Keyword, Comprehension)): if not self.parent.parent: raise ParentMissingError(target=self.parent) if not self.parent.parent.parent: @@ -4256,7 +4256,7 @@ def scope(self) -> "LocalsDictNodeNG": raise ParentMissingError(target=self) # For certain parents NamedExpr evaluate to the scope of the parent - if isinstance(self.parent, (Arguments, Keyword)): + if isinstance(self.parent, (Arguments, Keyword, Comprehension)): if not self.parent.parent: raise ParentMissingError(target=self.parent) if not self.parent.parent.parent: diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index a1415cc2e4..6f1ccb1fb5 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -683,10 +683,11 @@ def hello(False): @pytest.mark.skipif(not PY38_PLUS, reason="needs assignment expressions") -class NamedExprNodeTest(unittest.TestCase): +class TestNamedExprNode: """Tests for the NamedExpr node""" - def test_frame(self) -> None: + @staticmethod + def test_frame() -> None: """Test if the frame of NamedExpr is correctly set for certain types of parent nodes. """ @@ -712,6 +713,8 @@ def func_with_lambda( ) ): pass + + COMPREHENSION = [y for i in (1, 2) if (y := i ** 2)] """ ) function = module.body[0] @@ -733,7 +736,11 @@ def func_with_lambda( lambda_named_expr = module.body[5].args.defaults[0] assert lambda_named_expr.value.args.defaults[0].frame() == module - def test_scope(self) -> None: + comprehension = module.body[6].value + assert comprehension.generators[0].ifs[0].frame() == module + + @staticmethod + def test_scope() -> None: """Test if the scope of NamedExpr is correctly set for certain types of parent nodes. """ @@ -759,6 +766,8 @@ def func_with_lambda( ) ): pass + + COMPREHENSION = [y for i in (1, 2) if (y := i ** 2)] """ ) function = module.body[0] @@ -780,6 +789,9 @@ def func_with_lambda( lambda_named_expr = module.body[5].args.defaults[0] assert lambda_named_expr.value.args.defaults[0].scope() == module + comprehension = module.body[6].value + assert comprehension.generators[0].ifs[0].scope() == module + class AnnAssignNodeTest(unittest.TestCase): def test_primitive(self) -> None: From e445e3c0368b3ffce48b476a9cec0220e2937b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 24 Oct 2021 20:03:45 +0200 Subject: [PATCH 4/7] Update ChangeLog Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index ee93e13c09..3e8b70ee43 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,11 +12,13 @@ What's New in astroid 2.8.4? ============================ Release date: TBA + * Fix the `scope()` and `frame()` methods of `NamedExpr` nodes. When these nodes occur in `Arguments`, `Keyword` or `Comprehension` nodes these methods now correctly point to the outer-scope of the `FunctionDef`, `ClassDef` or comprehension. + What's New in astroid 2.8.3? ============================ Release date: 2021-10-17 From c19b2a3e2793fe2964e331f6390f043709aaeccd Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 24 Oct 2021 20:07:35 +0200 Subject: [PATCH 5/7] Remove empty line --- ChangeLog | 1 - 1 file changed, 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 3e8b70ee43..09a74c9f16 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,7 +12,6 @@ What's New in astroid 2.8.4? ============================ Release date: TBA - * Fix the `scope()` and `frame()` methods of `NamedExpr` nodes. When these nodes occur in `Arguments`, `Keyword` or `Comprehension` nodes these methods now correctly point to the outer-scope of the `FunctionDef`, From d9a8dbd7e97512f0126eabeedb63e956001db0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 24 Oct 2021 20:32:53 +0200 Subject: [PATCH 6/7] Update ChangeLog Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- ChangeLog | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 09a74c9f16..dde561c8d0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,10 +12,10 @@ What's New in astroid 2.8.4? ============================ Release date: TBA -* Fix the `scope()` and `frame()` methods of `NamedExpr` nodes. - When these nodes occur in `Arguments`, `Keyword` or `Comprehension` nodes these - methods now correctly point to the outer-scope of the `FunctionDef`, - `ClassDef` or comprehension. +* Fix the ``scope()`` and ``frame()`` methods of ``NamedExpr`` nodes. + When these nodes occur in ``Arguments``, ``Keyword`` or ``Comprehension`` nodes these + methods now correctly point to the outer-scope of the `` FunctionDef``, + ``ClassDef`` or comprehension. What's New in astroid 2.8.3? From f9e2e19517a743afc7c5eb713d23508650da5400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 24 Oct 2021 20:34:40 +0200 Subject: [PATCH 7/7] Update ChangeLog Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index dde561c8d0..f52f684678 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,7 +15,7 @@ Release date: TBA * Fix the ``scope()`` and ``frame()`` methods of ``NamedExpr`` nodes. When these nodes occur in ``Arguments``, ``Keyword`` or ``Comprehension`` nodes these methods now correctly point to the outer-scope of the `` FunctionDef``, - ``ClassDef`` or comprehension. + ``ClassDef`` or ``Comprehension``. What's New in astroid 2.8.3?