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=True to frame calls #1305

Merged
merged 36 commits into from
Dec 29, 2021
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fff4db4
Fix frame() error on inferred node
tushar-deepsource Nov 20, 2021
2cab065
Update node_ng.py
tushar-deepsource Nov 20, 2021
8952c64
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 20, 2021
4ea13b2
Update node_ng.py
tushar-deepsource Nov 20, 2021
42ce9f4
Add FrameMissing exception
tushar-deepsource Nov 21, 2021
4629c93
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 21, 2021
9b2c483
Fix flake8 lint
tushar-deepsource Nov 21, 2021
27c1707
Fix pylint error
tushar-deepsource Nov 21, 2021
1b59f00
Remove unnecessary isinstance
tushar-deepsource Dec 14, 2021
54bcb59
Add types and remove exception type
tushar-deepsource Dec 20, 2021
7bee391
Add visit_unknown
tushar-deepsource Nov 21, 2021
9e55d84
Formatting
tushar-deepsource Dec 20, 2021
9da479c
Upgrade pylint to 2.12.2 (#1297)
Pierre-Sassoulas Dec 15, 2021
9fb55c5
Fix flake8 lint
tushar-deepsource Nov 21, 2021
dac07c6
Add types and remove exception type
tushar-deepsource Dec 20, 2021
3163665
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 20, 2021
08014ed
Remove unrelated code
tushar-deepsource Dec 20, 2021
67281cb
Hack to trigger ci
tushar-deepsource Dec 20, 2021
3bee4f5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 20, 2021
db901ce
Remove unused import
tushar-deepsource Dec 20, 2021
a01e510
something
tushar-deepsource Dec 20, 2021
410445b
Move import to TYPE_CHECKING block
tushar-deepsource Dec 20, 2021
1d6dc24
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 20, 2021
a3b2db9
Unused import
tushar-deepsource Dec 20, 2021
4a1a4ad
ci
tushar-deepsource Dec 20, 2021
9efdc91
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 20, 2021
492a20d
Merge branch 'main' of https://github.com/pycqa/astroid into tushar-d…
tushar-deepsource Dec 20, 2021
fef9154
Revert noreturn change
tushar-deepsource Dec 20, 2021
b0fab08
Add future=True to all frsme() calls
tushar-deepsource Dec 20, 2021
5754615
Merge branch 'main' of https://github.com/pycqa/astroid into frame-fu…
tushar-deepsource Dec 20, 2021
9b12936
Remove extra stuff
tushar-deepsource Dec 20, 2021
e9e865f
Apply suggestions from code review
tushar-deepsource Dec 24, 2021
36395b6
Merge branch 'main' into frame-future
tushar-deepsource Dec 24, 2021
8159eab
Merge branch 'main' into frame-future
tushar-deepsource Dec 29, 2021
20b3b1c
whitespace
tushar-deepsource Dec 29, 2021
88988e1
Add back non-future frame calls
tushar-deepsource Dec 29, 2021
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
2 changes: 1 addition & 1 deletion astroid/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def infer_argument(self, funcnode, name, context):
return positional[0].infer(context=context)
if boundnode is None:
# XXX can do better ?
boundnode = funcnode.parent.frame()
boundnode = funcnode.parent.frame(future=True)

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 @@ -368,7 +368,7 @@ class UnboundMethod(Proxy):
special_attributes = lazy_descriptor(lambda: objectmodel.UnboundMethodModel())

def __repr__(self):
frame = self._proxied.parent.frame()
frame = self._proxied.parent.frame(future=True)
return "<{} {} of {} at 0x{}".format(
self.__class__.__name__, self._proxied.name, frame.qname(), id(self)
)
Expand Down Expand Up @@ -404,7 +404,7 @@ def infer_call_result(self, caller, context):
# instance of the class given as first argument.
if (
self._proxied.name == "__new__"
and self._proxied.parent.frame().qname() == "builtins.object"
and self._proxied.parent.frame(future=True).qname() == "builtins.object"
):
if caller.args:
node_context = context.extra_context.get(caller.args[0])
Expand Down
4 changes: 2 additions & 2 deletions astroid/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def delayed_assattr(self, node):
This adds name to locals and handle members definition.
"""
try:
frame = node.frame()
frame = node.frame(future=True)
for inferred in node.expr.infer():
if inferred is util.Uninferable:
continue
Expand Down Expand Up @@ -263,7 +263,7 @@ def delayed_assattr(self, node):
if (
frame.name == "__init__"
and values
and values[0].frame().name != "__init__"
and values[0].frame(future=True).name != "__init__"
):
values.insert(0, node)
else:
Expand Down
2 changes: 1 addition & 1 deletion astroid/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def object_len(node, context=None):

# prevent self referential length calls from causing a recursion error
# see https://github.com/PyCQA/astroid/issues/777
node_frame = node.frame()
node_frame = node.frame(future=True)
if (
isinstance(node_frame, scoped_nodes.FunctionDef)
and node_frame.name == "__len__"
Expand Down
11 changes: 7 additions & 4 deletions 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 All @@ -4812,9 +4815,9 @@ def frame(self):
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.parent.parent.frame(future=True)

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

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

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


class Unknown(mixins.AssignTypeMixin, NodeNG):
Expand Down
17 changes: 14 additions & 3 deletions astroid/nodes/node_ng.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
else:
from typing_extensions import Literal


# Types for 'NodeNG.nodes_of_class()'
T_Nodes = TypeVar("T_Nodes", bound="NodeNG")
T_Nodes2 = TypeVar("T_Nodes2", bound="NodeNG")
Expand Down Expand Up @@ -309,7 +308,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 @@ -318,7 +317,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(future=True) will return either a Frame, "
tushar-deepsource marked this conversation as resolved.
Show resolved Hide resolved
"or raise ParentMissingError. AttributeError will no longer be raised. "
"This behaviour can already be triggered "
"by passing 'future=True' to a statement() call.",
tushar-deepsource marked this conversation as resolved.
Show resolved Hide resolved
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
34 changes: 18 additions & 16 deletions astroid/nodes/scoped_nodes/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def qname(self):
# pylint: disable=no-member; github.com/pycqa/astroid/issues/278
if self.parent is None:
return self.name
return f"{self.parent.frame().qname()}.{self.name}"
return f"{self.parent.frame(future=True).qname()}.{self.name}"

def scope(self: T) -> T:
"""The first parent node defining a new scope.
Expand Down Expand Up @@ -857,7 +857,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 @@ -1450,7 +1450,7 @@ def scope_lookup(self, node, name, offset=0):
:rtype: tuple(str, list(NodeNG))
"""
if node in self.args.defaults or node in self.args.kw_defaults:
frame = self.parent.frame()
frame = self.parent.frame(future=True)
# line offset to avoid that def func(f=func) resolve the default
# value to the defined function
offset = -1
Expand All @@ -1472,7 +1472,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 @@ -1593,7 +1593,7 @@ def __init__(
parent=parent,
)
if parent:
frame = parent.frame()
frame = parent.frame(future=True)
frame.set_local(name, self)

# pylint: disable=arguments-differ; different than Lambdas
Expand Down Expand Up @@ -1639,7 +1639,7 @@ def extra_decorators(self):

:type: list(NodeNG)
"""
frame = self.parent.frame()
frame = self.parent.frame(future=True)
if not isinstance(frame, ClassDef):
return []

Expand All @@ -1666,7 +1666,7 @@ def extra_decorators(self):
# original method.
if (
isinstance(meth, FunctionDef)
and assign_node.frame() == frame
and assign_node.frame(future=True) == frame
):
decorators.append(assign.value)
return decorators
Expand All @@ -1685,7 +1685,7 @@ def type(
if decorator.func.name in BUILTIN_DESCRIPTORS:
return decorator.func.name

frame = self.parent.frame()
frame = self.parent.frame(future=True)
type_name = "function"
if isinstance(frame, ClassDef):
if self.name == "__new__":
Expand Down Expand Up @@ -1813,7 +1813,9 @@ def is_method(self):
"""
# check we are defined in a ClassDef, because this is usually expected
# (e.g. pylint...) when is_method() return True
return self.type != "function" and isinstance(self.parent.frame(), ClassDef)
return self.type != "function" and isinstance(
self.parent.frame(future=True), ClassDef
)

@decorators_mod.cached
def decoratornames(self, context=None):
Expand Down Expand Up @@ -1997,12 +1999,12 @@ def scope_lookup(self, node, name, offset=0):
# if any methods in a class body refer to either __class__ or super.
# In our case, we want to be able to look it up in the current scope
# when `__class__` is being used.
frame = self.parent.frame()
frame = self.parent.frame(future=True)
if isinstance(frame, ClassDef):
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 @@ -2122,12 +2124,12 @@ def get_wrapping_class(node):
:rtype: ClassDef or None
"""

klass = node.frame()
klass = node.frame(future=True)
while klass is not None and not isinstance(klass, ClassDef):
if klass.parent is None:
klass = None
else:
klass = klass.parent.frame()
klass = klass.parent.frame(future=True)
return klass


Expand Down Expand Up @@ -2258,7 +2260,7 @@ def __init__(
parent=parent,
)
if parent is not None:
parent.frame().set_local(name, self)
parent.frame(future=True).set_local(name, self)

for local_name, node in self.implicit_locals():
self.add_local_node(node, local_name)
Expand Down Expand Up @@ -2515,7 +2517,7 @@ def scope_lookup(self, node, name, offset=0):
# class A(name.Name):
# def name(self): ...

frame = self.parent.frame()
frame = self.parent.frame(future=True)
# line offset to avoid that class A(A) resolve the ancestor to
# the defined class
offset = -1
Expand Down Expand Up @@ -3249,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
2 changes: 1 addition & 1 deletion astroid/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ def arguments_assigned_stmts(
if (
context.callcontext
and node
and getattr(callee, "name", None) == node.frame().name
and getattr(callee, "name", None) == node.frame(future=True).name
):
# reset call context/name
callcontext = context.callcontext
Expand Down
10 changes: 5 additions & 5 deletions tests/unittest_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ def test_module_base_props(self) -> None:
self.assertEqual(module.doc, "test module for astroid\n")
self.assertEqual(module.fromlineno, 0)
self.assertIsNone(module.parent)
self.assertEqual(module.frame(), module)
self.assertEqual(module.frame(future=True), module)
self.assertEqual(module.root(), module)
self.assertEqual(module.file, os.path.abspath(resources.find("data/module.py")))
self.assertEqual(module.pure_python, 1)
Expand Down Expand Up @@ -649,8 +649,8 @@ def test_function_base_props(self) -> None:
self.assertEqual(function.doc, "function test")
self.assertEqual(function.fromlineno, 11)
self.assertTrue(function.parent)
self.assertEqual(function.frame(), function)
self.assertEqual(function.parent.frame(), module)
self.assertEqual(function.frame(future=True), function)
self.assertEqual(function.parent.frame(future=True), module)
self.assertEqual(function.root(), module)
self.assertEqual([n.name for n in function.args.args], ["key", "val"])
self.assertEqual(function.type, "function")
Expand All @@ -670,8 +670,8 @@ def test_class_base_props(self) -> None:
self.assertEqual(klass.doc, "hehe\n haha")
self.assertEqual(klass.fromlineno, 25)
self.assertTrue(klass.parent)
self.assertEqual(klass.frame(), klass)
self.assertEqual(klass.parent.frame(), module)
self.assertEqual(klass.frame(future=True), klass)
self.assertEqual(klass.parent.frame(future=True), module)
self.assertEqual(klass.root(), module)
self.assertEqual(klass.basenames, [])
self.assertTrue(klass.newstyle)
Expand Down
4 changes: 2 additions & 2 deletions tests/unittest_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,15 +336,15 @@ def test_unbound_method_inference(self) -> None:
meth1 = next(inferred)
self.assertIsInstance(meth1, UnboundMethod)
self.assertEqual(meth1.name, "meth1")
self.assertEqual(meth1.parent.frame().name, "C")
self.assertEqual(meth1.parent.frame(future=True).name, "C")
self.assertRaises(StopIteration, partial(next, inferred))

def test_bound_method_inference(self) -> None:
inferred = self.ast["m_bound"].infer()
meth1 = next(inferred)
self.assertIsInstance(meth1, BoundMethod)
self.assertEqual(meth1.name, "meth1")
self.assertEqual(meth1.parent.frame().name, "C")
self.assertEqual(meth1.parent.frame(future=True).name, "C")
self.assertRaises(StopIteration, partial(next, inferred))

def test_args_default_inference1(self) -> None:
Expand Down
8 changes: 4 additions & 4 deletions tests/unittest_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,22 +279,22 @@ def test_ast_from_module_cache(self) -> None:
def test_ast_from_class(self) -> None:
ast = self.manager.ast_from_class(int)
self.assertEqual(ast.name, "int")
self.assertEqual(ast.parent.frame().name, "builtins")
self.assertEqual(ast.parent.frame(future=True).name, "builtins")

ast = self.manager.ast_from_class(object)
self.assertEqual(ast.name, "object")
self.assertEqual(ast.parent.frame().name, "builtins")
self.assertEqual(ast.parent.frame(future=True).name, "builtins")
self.assertIn("__setattr__", ast)

def test_ast_from_class_with_module(self) -> None:
"""check if the method works with the module name"""
ast = self.manager.ast_from_class(int, int.__module__)
self.assertEqual(ast.name, "int")
self.assertEqual(ast.parent.frame().name, "builtins")
self.assertEqual(ast.parent.frame(future=True).name, "builtins")

ast = self.manager.ast_from_class(object, object.__module__)
self.assertEqual(ast.name, "object")
self.assertEqual(ast.parent.frame().name, "builtins")
self.assertEqual(ast.parent.frame(future=True).name, "builtins")
self.assertIn("__setattr__", ast)

def test_ast_from_class_attr_error(self) -> None:
Expand Down
Loading