From b9b4d4791982e1e88d1f63e912534e95caf97e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 7 Nov 2021 23:52:05 +0200 Subject: [PATCH 1/8] Add ``future`` argument to all ``NodeNG.statement()`` calls --- astroid/brain/brain_dataclasses.py | 2 +- astroid/brain/brain_namedtuple_enum.py | 2 +- astroid/mixins.py | 4 ++-- astroid/nodes/node_classes.py | 19 ++++++++++++++----- astroid/nodes/scoped_nodes.py | 2 +- astroid/protocols.py | 2 +- tests/unittest_builder.py | 4 +++- 7 files changed, 23 insertions(+), 12 deletions(-) diff --git a/astroid/brain/brain_dataclasses.py b/astroid/brain/brain_dataclasses.py index 7bb2a60f01..6ea145117a 100644 --- a/astroid/brain/brain_dataclasses.py +++ b/astroid/brain/brain_dataclasses.py @@ -304,7 +304,7 @@ def _looks_like_dataclass_field_call(node: Call, check_scope: bool = True) -> bo If check_scope is False, skips checking the statement and body. """ if check_scope: - stmt = node.statement() + stmt = node.statement(future=True) scope = stmt.scope() if not ( isinstance(stmt, AnnAssign) diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py index 2ce69a90a3..1ca661fad8 100644 --- a/astroid/brain/brain_namedtuple_enum.py +++ b/astroid/brain/brain_namedtuple_enum.py @@ -365,7 +365,7 @@ def infer_enum_class(node): if any(not isinstance(value, nodes.AssignName) for value in values): continue - stmt = values[0].statement() + stmt = values[0].statement(future=True) if isinstance(stmt, nodes.Assign): if isinstance(stmt.targets[0], nodes.Tuple): targets = stmt.targets[0].itered() diff --git a/astroid/mixins.py b/astroid/mixins.py index d7c027a14a..b1d5b12556 100644 --- a/astroid/mixins.py +++ b/astroid/mixins.py @@ -46,7 +46,7 @@ class FilterStmtsMixin: def _get_filtered_stmts(self, _, node, _stmts, mystmt): """method used in _filter_stmts to get statements and trigger break""" - if self.statement() is mystmt: + if self.statement(future=True) is mystmt: # original node's statement is the assignment, only keep # current node (gen exp, list comp) return [node], True @@ -64,7 +64,7 @@ def _get_filtered_stmts(self, lookup_node, node, _stmts, mystmt): """method used in filter_stmts""" if self is mystmt: return _stmts, True - if self.statement() is mystmt: + if self.statement(future=True) is mystmt: # original node's statement is the assignment, only keep # current node (gen exp, list comp) return [node], True diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index b8b7c57545..2b1654df02 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -54,6 +54,7 @@ InferenceError, NoDefault, ParentMissingError, + StatementMissing, ) from astroid.manager import AstroidManager from astroid.nodes.const import OP_PRECEDENCE @@ -387,7 +388,7 @@ def ilookup(self, name): return _infer_stmts(stmts, context, frame) def _get_filtered_node_statements(self, nodes): - statements = [(node, node.statement()) for node in nodes] + statements = [(node, node.statement(future=True)) for node in nodes] # Next we check if we have ExceptHandlers that are parent # of the underlying variable, in which case the last one survives if len(statements) > 1 and all( @@ -437,10 +438,18 @@ def _filter_stmts(self, stmts, frame, offset): # # def test(b=1): # ... - - if self.statement() is myframe and myframe.parent: + if ( + self.parent + and self.statement(future=True) is myframe + and myframe.parent + ): myframe = myframe.parent.frame() - mystmt = self.statement() + + # nodes.Module don't have a parent attribute + try: + mystmt = self.statement(future=True) + except StatementMissing: + mystmt = self # line filtering if we are in the same frame # # take care node may be missing lineno information (this is the case for @@ -1816,7 +1825,7 @@ def _get_filtered_stmts(self, lookup_node, node, stmts, mystmt): if isinstance(lookup_node, (Const, Name)): return [lookup_node], True - elif self.statement() is mystmt: + elif self.statement(future=True) is mystmt: # original node's statement is the assignment, only keeps # current node (gen exp, list comp) diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index df153b8875..e672bdcfd8 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -2587,7 +2587,7 @@ def getattr(self, name, context=None, class_context=True): # Look for AnnAssigns, which are not attributes in the purest sense. for value in values: if isinstance(value, node_classes.AssignName): - stmt = value.statement() + stmt = value.statement(future=True) if isinstance(stmt, node_classes.AnnAssign) and stmt.value is None: raise AttributeInferenceError( target=self, attribute=name, context=context diff --git a/astroid/protocols.py b/astroid/protocols.py index 4ec92a2a22..674766e163 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -637,7 +637,7 @@ def _determine_starred_iteration_lookups(starred, target, lookups): lookups.append((index, len(element.itered()))) _determine_starred_iteration_lookups(starred, element, lookups) - stmt = self.statement() + stmt = self.statement(future=True) if not isinstance(stmt, (nodes.Assign, nodes.For)): raise InferenceError( "Statement {stmt!r} enclosing {node!r} " "must be an Assign or For node.", diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py index 11019e1ba0..7b2e4ed075 100644 --- a/tests/unittest_builder.py +++ b/tests/unittest_builder.py @@ -614,7 +614,9 @@ def test_module_base_props(self) -> None: self.assertEqual(module.pure_python, 1) self.assertEqual(module.package, 0) self.assertFalse(module.is_statement) - self.assertEqual(module.statement(), module) + with pytest.warns(DeprecationWarning) as records: + self.assertEqual(module.statement(), module) + assert len(records) == 1 with pytest.warns(DeprecationWarning) as records: module.statement() assert len(records) == 1 From 44c87f058c2c497c311faf1a2f1893b122c887e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 7 Nov 2021 23:57:07 +0200 Subject: [PATCH 2/8] Update tests/unittest_builder.py --- tests/unittest_builder.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py index 7b2e4ed075..6b1fdfcab7 100644 --- a/tests/unittest_builder.py +++ b/tests/unittest_builder.py @@ -617,9 +617,6 @@ def test_module_base_props(self) -> None: with pytest.warns(DeprecationWarning) as records: self.assertEqual(module.statement(), module) assert len(records) == 1 - with pytest.warns(DeprecationWarning) as records: - module.statement() - assert len(records) == 1 with self.assertRaises(StatementMissing): module.statement(future=True) From 1c8344f77fe101e85398cfa7dce8b71b6a0aeff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 15 Nov 2021 14:26:20 +0100 Subject: [PATCH 3/8] Fix `_filter_stmts` --- astroid/nodes/node_classes.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 2b1654df02..6ac9d54605 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -54,7 +54,6 @@ InferenceError, NoDefault, ParentMissingError, - StatementMissing, ) from astroid.manager import AstroidManager from astroid.nodes.const import OP_PRECEDENCE @@ -445,16 +444,15 @@ def _filter_stmts(self, stmts, frame, offset): ): myframe = myframe.parent.frame() - # nodes.Module don't have a parent attribute - try: + mystmt = None + if self.parent: mystmt = self.statement(future=True) - except StatementMissing: - mystmt = self + # line filtering if we are in the same frame # # take care node may be missing lineno information (this is the case for # nodes inserted for living objects) - if myframe is frame and mystmt.fromlineno is not None: + if myframe is frame and mystmt and mystmt.fromlineno is not None: assert mystmt.fromlineno is not None, mystmt mylineno = mystmt.fromlineno + offset else: @@ -555,7 +553,7 @@ def _filter_stmts(self, stmts, frame, offset): _stmt_parents = [] else: continue - elif not optional_assign and stmt.parent is mystmt.parent: + elif not optional_assign and mystmt and stmt.parent is mystmt.parent: _stmts = [] _stmt_parents = [] elif isinstance(node, DelName): From 9ea8f0148a7eaa0a83ef1d460c9bf4b28c5eb7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 15 Nov 2021 22:16:32 +0100 Subject: [PATCH 4/8] Add quotes to NoReturn --- astroid/nodes/node_ng.py | 4 ++-- astroid/nodes/scoped_nodes.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index 6fb242cd61..8ba5c7caed 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -32,7 +32,7 @@ from astroid import nodes if sys.version_info >= (3, 6, 2): - from typing import NoReturn + from typing import NoReturn # pylint: disable=unused-import else: from typing_extensions import NoReturn @@ -275,7 +275,7 @@ def statement(self, *, future: Literal[True]) -> "nodes.Statement": def statement( self, *, future: Literal[None, True] = None - ) -> Union["nodes.Statement", "nodes.Module", NoReturn]: + ) -> Union["nodes.Statement", "nodes.Module", "NoReturn"]: """The first parent node, including self, marked as statement node. TODO: Deprecate the future parameter and only raise StatementMissing and return diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index e672bdcfd8..57df442801 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -662,7 +662,7 @@ def statement(self, *, future: Literal[True]) -> NoReturn: def statement( self, *, future: Literal[None, True] = None - ) -> Union[NoReturn, "Module"]: + ) -> Union["NoReturn", "Module"]: """The first parent node, including self, marked as statement node. When called on a :class:`Module` with the future parameter this raises an error. From e5ae4e15373b02f3e07c8a5a972e539888a8d440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 15 Nov 2021 22:46:09 +0100 Subject: [PATCH 5/8] Change import --- astroid/nodes/node_ng.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index 8ba5c7caed..17088f91d2 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -31,10 +31,11 @@ if TYPE_CHECKING: from astroid import nodes -if sys.version_info >= (3, 6, 2): - from typing import NoReturn # pylint: disable=unused-import -else: - from typing_extensions import NoReturn + if sys.version_info >= (3, 6, 2): + # To be fixed with https://github.com/PyCQA/pylint/pull/5316 + from typing import NoReturn # pylint: disable=unused-import + else: + from typing_extensions import NoReturn if sys.version_info >= (3, 8): from typing import Literal From 3c05efc4dcc951ff7602e63036ff5dfa5567194d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:55:19 +0100 Subject: [PATCH 6/8] Add typing from ``statement()`` --- astroid/mixins.py | 10 ++++++++-- astroid/nodes/node_classes.py | 14 ++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/astroid/mixins.py b/astroid/mixins.py index b1d5b12556..3241ecea2e 100644 --- a/astroid/mixins.py +++ b/astroid/mixins.py @@ -16,10 +16,14 @@ """This module contains some mixins for the different nodes. """ import itertools +from typing import TYPE_CHECKING, Optional from astroid import decorators from astroid.exceptions import AttributeInferenceError +if TYPE_CHECKING: + from astroid import nodes + class BlockRangeMixIn: """override block range""" @@ -44,7 +48,7 @@ def _elsed_block_range(self, lineno, orelse, last=None): class FilterStmtsMixin: """Mixin for statement filtering and assignment type""" - def _get_filtered_stmts(self, _, node, _stmts, mystmt): + def _get_filtered_stmts(self, _, node, _stmts, mystmt: Optional["nodes.Statement"]): """method used in _filter_stmts to get statements and trigger break""" if self.statement(future=True) is mystmt: # original node's statement is the assignment, only keep @@ -60,7 +64,9 @@ class AssignTypeMixin: def assign_type(self): return self - def _get_filtered_stmts(self, lookup_node, node, _stmts, mystmt): + def _get_filtered_stmts( + self, lookup_node, node, _stmts, mystmt: Optional["nodes.Statement"] + ): """method used in filter_stmts""" if self is mystmt: return _stmts, True diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 624c45654f..07ba016d4a 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -386,8 +386,12 @@ def ilookup(self, name): context = InferenceContext() return _infer_stmts(stmts, context, frame) - def _get_filtered_node_statements(self, nodes): - statements = [(node, node.statement(future=True)) for node in nodes] + def _get_filtered_node_statements( + self, nodes: typing.List[NodeNG] + ) -> typing.List[typing.Tuple[NodeNG, Statement]]: + statements: typing.List[typing.Tuple[NodeNG, Statement]] = [ + (node, node.statement(future=True)) for node in nodes + ] # Next we check if we have ExceptHandlers that are parent # of the underlying variable, in which case the last one survives if len(statements) > 1 and all( @@ -444,7 +448,7 @@ def _filter_stmts(self, stmts, frame, offset): ): myframe = myframe.parent.frame() - mystmt = None + mystmt: Optional[Statement] = None if self.parent: mystmt = self.statement(future=True) @@ -1837,7 +1841,9 @@ def assign_type(self): """ return self - def _get_filtered_stmts(self, lookup_node, node, stmts, mystmt): + def _get_filtered_stmts( + self, lookup_node, node, stmts, mystmt: Optional[Statement] + ): """method used in filter_stmts""" if self is mystmt: if isinstance(lookup_node, (Const, Name)): From 8f5e8e9645682aa818b9e637b63e7d04a0826e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 18 Nov 2021 00:06:37 +0100 Subject: [PATCH 7/8] Update astroid/nodes/node_classes.py Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- astroid/nodes/node_classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 07ba016d4a..db73de823e 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -389,7 +389,7 @@ def ilookup(self, name): def _get_filtered_node_statements( self, nodes: typing.List[NodeNG] ) -> typing.List[typing.Tuple[NodeNG, Statement]]: - statements: typing.List[typing.Tuple[NodeNG, Statement]] = [ + statements = [ (node, node.statement(future=True)) for node in nodes ] # Next we check if we have ExceptHandlers that are parent From d97b34e5cfe40b28cd00eaf9c7c01f3c2b4e8243 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 17 Nov 2021 23:07:14 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- astroid/nodes/node_classes.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index db73de823e..df2d70dff6 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -389,9 +389,7 @@ def ilookup(self, name): def _get_filtered_node_statements( self, nodes: typing.List[NodeNG] ) -> typing.List[typing.Tuple[NodeNG, Statement]]: - statements = [ - (node, node.statement(future=True)) for node in nodes - ] + statements = [(node, node.statement(future=True)) for node in nodes] # Next we check if we have ExceptHandlers that are parent # of the underlying variable, in which case the last one survives if len(statements) > 1 and all(