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

Adopt "future" behavior of statement() and frame() #2221

Merged
merged 3 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ Release date: TBA

Refs #2141

* ``frame()`` raises ``ParentMissingError`` and ``statement()`` raises ``StatementMissing`` for
missing parents regardless of the value of the ``future`` argument (which gave this behavior
already).

The ``future`` argument to each method is deprecated and will be removed in astroid 4.0.

Refs #1217

* Remove deprecated ``Ellipsis``, ``ExtSlice``, ``Index`` nodes.

Refs #2152
Expand Down
2 changes: 1 addition & 1 deletion astroid/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def infer_argument(
return positional[0].infer(context=context)
if boundnode is None:
# XXX can do better ?
boundnode = funcnode.parent.frame(future=True)
boundnode = funcnode.parent.frame()

if isinstance(boundnode, nodes.ClassDef):
# Verify that we're accessing a method
Expand Down
4 changes: 2 additions & 2 deletions astroid/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ class UnboundMethod(Proxy):

def __repr__(self) -> str:
assert self._proxied.parent, "Expected a parent node"
frame = self._proxied.parent.frame(future=True)
frame = self._proxied.parent.frame()
return "<{} {} of {} at 0x{}".format(
self.__class__.__name__, self._proxied.name, frame.qname(), id(self)
)
Expand Down Expand Up @@ -472,7 +472,7 @@ def infer_call_result(
# instance of the class given as first argument.
if self._proxied.name == "__new__":
assert self._proxied.parent, "Expected a parent node"
qname = self._proxied.parent.frame(future=True).qname()
qname = self._proxied.parent.frame().qname()
# Avoid checking builtins.type: _infer_type_new_call() does more validation
if qname.startswith("builtins.") and qname != "builtins.type":
return self._infer_builtin_new(caller, context or InferenceContext())
Expand Down
2 changes: 1 addition & 1 deletion astroid/brain/brain_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ def _looks_like_dataclass_field_call(
If check_scope is False, skips checking the statement and body.
"""
if check_scope:
stmt = node.statement(future=True)
stmt = node.statement()
scope = stmt.scope()
if not (
isinstance(stmt, nodes.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 @@ -406,7 +406,7 @@ def infer_enum_class(node: nodes.ClassDef) -> nodes.ClassDef:
if any(not isinstance(value, nodes.AssignName) for value in values):
continue

stmt = values[0].statement(future=True)
stmt = values[0].statement()
if isinstance(stmt, nodes.Assign):
if isinstance(stmt.targets[0], nodes.Tuple):
targets = stmt.targets[0].itered()
Expand Down
4 changes: 2 additions & 2 deletions astroid/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def delayed_assattr(self, node: nodes.AssignAttr) -> None:
from astroid import objects # pylint: disable=import-outside-toplevel

try:
frame = node.frame(future=True)
frame = node.frame()
for inferred in node.expr.infer():
if isinstance(inferred, util.UninferableBase):
continue
Expand Down Expand Up @@ -268,7 +268,7 @@ def delayed_assattr(self, node: nodes.AssignAttr) -> None:
if (
frame.name == "__init__"
and values
and values[0].frame(future=True).name != "__init__"
and values[0].frame().name != "__init__"
):
values.insert(0, node)
else:
Expand Down
10 changes: 3 additions & 7 deletions astroid/filter_statements.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
def _get_filtered_node_statements(
base_node: nodes.NodeNG, stmt_nodes: list[nodes.NodeNG]
) -> list[tuple[nodes.NodeNG, nodes.Statement]]:
statements = [(node, node.statement(future=True)) for node in stmt_nodes]
statements = [(node, node.statement()) for node in stmt_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 @@ -83,16 +83,12 @@ def _filter_stmts(
#
# def test(b=1):
# ...
if (
base_node.parent
and base_node.statement(future=True) is myframe
and myframe.parent
):
if base_node.parent and base_node.statement() is myframe and myframe.parent:
myframe = myframe.parent.frame()

mystmt: nodes.Statement | None = None
if base_node.parent:
mystmt = base_node.statement(future=True)
mystmt = base_node.statement()

# line filtering if we are in the same frame
#
Expand Down
2 changes: 1 addition & 1 deletion astroid/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def object_len(node, context: InferenceContext | None = None):

# prevent self referential length calls from causing a recursion error
# see https://github.com/pylint-dev/astroid/issues/777
node_frame = node.frame(future=True)
node_frame = node.frame()
if (
isinstance(node_frame, scoped_nodes.FunctionDef)
and node_frame.name == "__len__"
Expand Down
2 changes: 1 addition & 1 deletion astroid/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ def infer_functiondef(
# of the wrapping frame. This means that every time we infer a property, the locals
# are mutated with a new instance of the property. To avoid this, we detect this
# scenario and avoid passing the `parent` argument to the constructor.
parent_frame = self.parent.frame(future=True)
parent_frame = self.parent.frame()
property_already_in_parent_locals = self.name in parent_frame.locals and any(
isinstance(val, objects.Property) for val in parent_frame.locals[self.name]
)
Expand Down
4 changes: 2 additions & 2 deletions astroid/nodes/_base_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class FilterStmtsBaseNode(NodeNG):

def _get_filtered_stmts(self, _, node, _stmts, mystmt: Statement | None):
"""Method used in _filter_stmts to get statements and trigger break."""
if self.statement(future=True) is mystmt:
if self.statement() is mystmt:
# original node's statement is the assignment, only keep
# current node (gen exp, list comp)
return [node], True
Expand All @@ -88,7 +88,7 @@ def _get_filtered_stmts(self, lookup_node, node, _stmts, mystmt: Statement | Non
"""Method used in filter_stmts."""
if self is mystmt:
return _stmts, True
if self.statement(future=True) is mystmt:
if self.statement() is mystmt:
# original node's statement is the assignment, only keep
# current node (gen exp, list comp)
return [node], True
Expand Down
14 changes: 10 additions & 4 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1528,7 +1528,7 @@ def _get_filtered_stmts(
if isinstance(lookup_node, (Const, Name)):
return [lookup_node], True

elif self.statement(future=True) is mystmt:
elif self.statement() is mystmt:
# original node's statement is the assignment, only keeps
# current node (gen exp, list comp)

Expand Down Expand Up @@ -3814,6 +3814,12 @@ def frame(

:returns: The first parent frame node.
"""
if future is not None:
warnings.warn(
"The future arg will be removed in astroid 4.0.",
DeprecationWarning,
stacklevel=2,
)
if not self.parent:
raise ParentMissingError(target=self)

Expand All @@ -3823,9 +3829,9 @@ def frame(
raise ParentMissingError(target=self.parent)
if not self.parent.parent.parent:
raise ParentMissingError(target=self.parent.parent)
return self.parent.parent.parent.frame(future=True)
return self.parent.parent.parent.frame()

return self.parent.frame(future=True)
return self.parent.frame()

def scope(self) -> LocalsDictNodeNG:
"""The first parent node defining a new scope.
Expand Down Expand Up @@ -3857,7 +3863,7 @@ def set_local(self, name: str, stmt: NodeNG) -> None:

:param stmt: The statement that defines the given name.
"""
self.frame(future=True).set_local(name, stmt)
self.frame().set_local(name, stmt)


class Unknown(_base_nodes.AssignTypeNode):
Expand Down
50 changes: 14 additions & 36 deletions astroid/nodes/node_ng.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,40 +288,22 @@ def parent_of(self, node) -> bool:
"""
return any(self is parent for parent in node.node_ancestors())

@overload
def statement(self, *, future: None = ...) -> nodes.Statement | nodes.Module:
...

@overload
def statement(self, *, future: Literal[True]) -> nodes.Statement:
...

def statement(
self, *, future: Literal[None, True] = None
) -> nodes.Statement | nodes.Module:
def statement(self, *, future: Literal[None, True] = None) -> nodes.Statement:
"""The first parent node, including self, marked as statement node.

TODO: Deprecate the future parameter and only raise StatementMissing and return
nodes.Statement

:raises AttributeError: If self has no parent attribute
:raises StatementMissing: If self has no parent attribute and future is True
:raises StatementMissing: If self has no parent attribute.
"""
if self.is_statement:
return cast("nodes.Statement", self)
if not self.parent:
if future:
raise StatementMissing(target=self)
if future is not None:
warnings.warn(
"In astroid 3.0.0 NodeNG.statement() will return either a nodes.Statement "
"or raise a StatementMissing exception. AttributeError will no longer be raised. "
"This behaviour can already be triggered "
"by passing 'future=True' to a statement() call.",
"The future arg will be removed in astroid 4.0.",
DeprecationWarning,
stacklevel=2,
)
raise AttributeError(f"{self} object has no attribute 'parent'")
return self.parent.statement(future=future)
if self.is_statement:
return cast("nodes.Statement", self)
if not self.parent:
raise StatementMissing(target=self)
return self.parent.statement()

def frame(
self, *, future: Literal[None, True] = None
Expand All @@ -332,20 +314,16 @@ def frame(
:class:`ClassDef` or :class:`Lambda`.

:returns: The first parent frame node.
:raises ParentMissingError: If self has no parent attribute.
"""
if self.parent is None:
if future:
raise ParentMissingError(target=self)
if future is not None:
warnings.warn(
"In astroid 3.0.0 NodeNG.frame() will return either a Frame node, "
"or raise ParentMissingError. AttributeError will no longer be raised. "
"This behaviour can already be triggered "
"by passing 'future=True' to a frame() call.",
"The future arg will be removed in astroid 4.0.",
DeprecationWarning,
stacklevel=2,
)
raise AttributeError(f"{self} object has no attribute 'parent'")

if self.parent is None:
raise ParentMissingError(target=self)
return self.parent.frame(future=future)

def scope(self) -> nodes.LocalsDictNodeNG:
Expand Down
2 changes: 1 addition & 1 deletion astroid/nodes/scoped_nodes/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def qname(self) -> str:
# pylint: disable=no-member; github.com/pylint-dev/astroid/issues/278
if self.parent is None or isinstance(self.parent, node_classes.Unknown):
return self.name
return f"{self.parent.frame(future=True).qname()}.{self.name}"
return f"{self.parent.frame().qname()}.{self.name}"

def scope(self: _T) -> _T:
"""The first parent node defining a new scope.
Expand Down
Loading
Loading