Skip to content

Commit

Permalink
Fix frame() error on inferred node (#1263)
Browse files Browse the repository at this point in the history
Co-authored-by: Pierre Sassoulas <[email protected]>
  • Loading branch information
tushar-deepsource and Pierre-Sassoulas authored Dec 29, 2021
1 parent 6e58cdf commit 12e2e8c
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 7 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ What's New in astroid 2.9.1?
============================
Release date: TBA

* ``NodeNG.frame()`` and ``NodeNG.statement()`` will start raising ``ParentMissingError``
instead of ``AttributeError`` in astroid 3.0. This behaviour can already be triggered
by passing ``future=True`` to a ``frame()`` or ``statement()`` call.

* Prefer the module loader get_source() method in AstroidBuilder's
module_build() when possible to avoid assumptions about source
code being available on a filesystem. Otherwise the source cannot
Expand Down
5 changes: 4 additions & 1 deletion astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
from typing_extensions import Literal

if TYPE_CHECKING:
from astroid import nodes
from astroid.nodes import LocalsDictNodeNG


Expand Down Expand Up @@ -4795,7 +4796,9 @@ def postinit(self, target: NodeNG, value: NodeNG) -> None:
See astroid/protocols.py for actual implementation.
"""

def frame(self):
def frame(
self, *, future: Literal[None, True] = None
) -> Union["nodes.FunctionDef", "nodes.Module", "nodes.ClassDef", "nodes.Lambda"]:
"""The first parent frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
Expand Down
16 changes: 14 additions & 2 deletions astroid/nodes/node_ng.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def statement(
return self.parent.statement(future=future)

def frame(
self,
self, *, future: Literal[None, True] = None
) -> Union["nodes.FunctionDef", "nodes.Module", "nodes.ClassDef", "nodes.Lambda"]:
"""The first parent frame node.
Expand All @@ -323,7 +323,19 @@ def frame(
:returns: The first parent frame node.
"""
return self.parent.frame()
if self.parent is None:
if future:
raise ParentMissingError(target=self)
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.",
DeprecationWarning,
)
raise AttributeError(f"{self} object has no attribute 'parent'")

return self.parent.frame(future=future)

def scope(self) -> "nodes.LocalsDictNodeNG":
"""The first parent node defining a new scope.
Expand Down
8 changes: 4 additions & 4 deletions astroid/nodes/scoped_nodes/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ def bool_value(self, context=None):
def get_children(self):
yield from self.body

def frame(self: T) -> T:
def frame(self: T, *, future: Literal[None, True] = None) -> T:
"""The node's frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
Expand Down Expand Up @@ -1474,7 +1474,7 @@ def get_children(self):
yield self.args
yield self.body

def frame(self: T) -> T:
def frame(self: T, *, future: Literal[None, True] = None) -> T:
"""The node's frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
Expand Down Expand Up @@ -2004,7 +2004,7 @@ def scope_lookup(self, node, name, offset=0):
return self, [frame]
return super().scope_lookup(node, name, offset)

def frame(self: T) -> T:
def frame(self: T, *, future: Literal[None, True] = None) -> T:
"""The node's frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
Expand Down Expand Up @@ -3251,7 +3251,7 @@ def _get_assign_nodes(self):
)
return list(itertools.chain.from_iterable(children_assign_nodes))

def frame(self: T) -> T:
def frame(self: T, *, future: Literal[None, True] = None) -> T:
"""The node's frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
Expand Down
8 changes: 8 additions & 0 deletions tests/unittest_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
AstroidBuildingError,
AstroidSyntaxError,
AttributeInferenceError,
ParentMissingError,
StatementMissing,
)
from astroid.nodes.node_classes import (
Expand Down Expand Up @@ -641,6 +642,13 @@ def _test(self, value: Any) -> None:
with self.assertRaises(StatementMissing):
node.statement(future=True)

with self.assertRaises(AttributeError):
with pytest.warns(DeprecationWarning) as records:
node.frame()
assert len(records) == 1
with self.assertRaises(ParentMissingError):
node.frame(future=True)

def test_none(self) -> None:
self._test(None)

Expand Down

0 comments on commit 12e2e8c

Please sign in to comment.