Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add future argument to all NodeNG.statement() calls #1235

Merged
merged 12 commits into from
Nov 24, 2021
Merged
2 changes: 1 addition & 1 deletion astroid/brain/brain_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion astroid/brain/brain_namedtuple_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
14 changes: 10 additions & 4 deletions astroid/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand All @@ -44,9 +48,9 @@ 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() 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
Expand All @@ -60,11 +64,13 @@ 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
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
Expand Down
31 changes: 22 additions & 9 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()) 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]] = [
DanielNoord marked this conversation as resolved.
Show resolved Hide resolved
(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(
Expand Down Expand Up @@ -437,15 +441,22 @@ 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()

mystmt: Optional[Statement] = None
if self.parent:
mystmt = self.statement(future=True)

# 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:
Expand Down Expand Up @@ -566,7 +577,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):
Expand Down Expand Up @@ -1830,13 +1841,15 @@ 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)):
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)

Expand Down
11 changes: 6 additions & 5 deletions astroid/nodes/node_ng.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
if TYPE_CHECKING:
from astroid import nodes

if sys.version_info >= (3, 6, 2):
from typing import NoReturn
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
Expand Down Expand Up @@ -275,7 +276,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"]:
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
"""The first parent node, including self, marked as statement node.

TODO: Deprecate the future parameter and only raise StatementMissing and return
Expand Down
4 changes: 2 additions & 2 deletions astroid/nodes/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion astroid/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
3 changes: 1 addition & 2 deletions tests/unittest_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,9 +614,8 @@ 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:
module.statement()
self.assertEqual(module.statement(), module)
assert len(records) == 1
with self.assertRaises(StatementMissing):
module.statement(future=True)
Expand Down