Skip to content

Commit

Permalink
Add future=True to frame calls (#1305)
Browse files Browse the repository at this point in the history
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
  • Loading branch information
3 people authored Dec 29, 2021
1 parent 7afd696 commit aa4f5be
Show file tree
Hide file tree
Showing 12 changed files with 54 additions and 22 deletions.
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
6 changes: 3 additions & 3 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4815,9 +4815,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()
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 @@ -4849,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
26 changes: 14 additions & 12 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 @@ -1452,7 +1452,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 Down Expand Up @@ -1595,7 +1595,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 @@ -1641,7 +1641,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 @@ -1668,7 +1668,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 @@ -1687,7 +1687,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 @@ -1815,7 +1815,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 @@ -1999,7 +2001,7 @@ 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)
Expand Down Expand Up @@ -2124,12 +2126,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 @@ -2260,7 +2262,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 @@ -2517,7 +2519,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
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
5 changes: 5 additions & 0 deletions tests/unittest_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ def test_module_base_props(self) -> None:
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 @@ -651,6 +652,8 @@ def test_function_base_props(self) -> None:
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 @@ -672,6 +675,8 @@ def test_class_base_props(self) -> None:
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
2 changes: 2 additions & 0 deletions tests/unittest_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ def test_unbound_method_inference(self) -> None:
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:
Expand All @@ -345,6 +346,7 @@ def test_bound_method_inference(self) -> None:
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
4 changes: 4 additions & 0 deletions tests/unittest_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,21 +280,25 @@ 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
10 changes: 10 additions & 0 deletions tests/unittest_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -741,25 +741,35 @@ def func_with_lambda(
)
function = module.body[0]
assert function.args.frame() == function
assert function.args.frame(future=True) == function

function_two = module.body[1]
assert function_two.args.args[0].frame() == function_two
assert function_two.args.args[0].frame(future=True) == function_two
assert function_two.args.args[1].frame() == function_two
assert function_two.args.args[1].frame(future=True) == function_two
assert function_two.args.defaults[0].frame() == module
assert function_two.args.defaults[0].frame(future=True) == module

inherited_class = module.body[3]
assert inherited_class.keywords[0].frame() == inherited_class
assert inherited_class.keywords[0].frame(future=True) == inherited_class
assert inherited_class.keywords[0].value.frame() == module
assert inherited_class.keywords[0].value.frame(future=True) == module

lambda_assignment = module.body[4].value
assert lambda_assignment.args.args[0].frame() == lambda_assignment
assert lambda_assignment.args.args[0].frame(future=True) == lambda_assignment
assert lambda_assignment.args.defaults[0].frame() == module
assert lambda_assignment.args.defaults[0].frame(future=True) == module

lambda_named_expr = module.body[5].args.defaults[0]
assert lambda_named_expr.value.args.defaults[0].frame() == module
assert lambda_named_expr.value.args.defaults[0].frame(future=True) == module

comprehension = module.body[6].value
assert comprehension.generators[0].ifs[0].frame() == module
assert comprehension.generators[0].ifs[0].frame(future=True) == module

@staticmethod
def test_scope() -> None:
Expand Down
9 changes: 9 additions & 0 deletions tests/unittest_scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2315,17 +2315,24 @@ def method():
)
function = module.body[0]
assert function.frame() == function
assert function.frame(future=True) == function
assert function.body[0].frame() == function
assert function.body[0].frame(future=True) == function

class_node = module.body[1]
assert class_node.frame() == class_node
assert class_node.frame(future=True) == class_node
assert class_node.body[0].frame() == class_node
assert class_node.body[0].frame(future=True) == class_node
assert class_node.body[1].frame() == class_node.body[1]
assert class_node.body[1].frame(future=True) == class_node.body[1]

lambda_assignment = module.body[2].value
assert lambda_assignment.args.args[0].frame() == lambda_assignment
assert lambda_assignment.args.args[0].frame(future=True) == lambda_assignment

assert module.frame() == module
assert module.frame(future=True) == module

@staticmethod
def test_non_frame_node():
Expand All @@ -2338,8 +2345,10 @@ def test_non_frame_node():
"""
)
assert module.body[0].frame() == module
assert module.body[0].frame(future=True) == module

assert module.body[1].value.locals["x"][0].frame() == module
assert module.body[1].value.locals["x"][0].frame(future=True) == module


if __name__ == "__main__":
Expand Down

0 comments on commit aa4f5be

Please sign in to comment.