forked from openkylin/astroid
548 lines
19 KiB
Python
548 lines
19 KiB
Python
# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
|
|
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
|
|
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
|
|
# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
|
|
# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
|
|
# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
|
# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
|
|
# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
|
|
|
|
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
|
|
# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
|
|
|
|
|
|
import unittest
|
|
from typing import List
|
|
|
|
from astroid import bases, builder, nodes, objects
|
|
from astroid.exceptions import AttributeInferenceError, InferenceError, SuperError
|
|
from astroid.objects import Super
|
|
|
|
|
|
class ObjectsTest(unittest.TestCase):
|
|
def test_frozenset(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
frozenset({1: 2, 2: 3}) #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, objects.FrozenSet)
|
|
|
|
self.assertEqual(inferred.pytype(), "builtins.frozenset")
|
|
|
|
itered = inferred.itered()
|
|
self.assertEqual(len(itered), 2)
|
|
self.assertIsInstance(itered[0], nodes.Const)
|
|
self.assertEqual([const.value for const in itered], [1, 2])
|
|
|
|
proxied = inferred._proxied
|
|
self.assertEqual(inferred.qname(), "builtins.frozenset")
|
|
self.assertIsInstance(proxied, nodes.ClassDef)
|
|
|
|
|
|
class SuperTests(unittest.TestCase):
|
|
def test_inferring_super_outside_methods(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class Module(object):
|
|
pass
|
|
class StaticMethod(object):
|
|
@staticmethod
|
|
def static():
|
|
# valid, but we don't bother with it.
|
|
return super(StaticMethod, StaticMethod) #@
|
|
# super outside methods aren't inferred
|
|
super(Module, Module) #@
|
|
# no argument super is not recognised outside methods as well.
|
|
super() #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
in_static = next(ast_nodes[0].value.infer())
|
|
self.assertIsInstance(in_static, bases.Instance)
|
|
self.assertEqual(in_static.qname(), "builtins.super")
|
|
|
|
module_level = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(module_level, bases.Instance)
|
|
self.assertEqual(in_static.qname(), "builtins.super")
|
|
|
|
no_arguments = next(ast_nodes[2].infer())
|
|
self.assertIsInstance(no_arguments, bases.Instance)
|
|
self.assertEqual(no_arguments.qname(), "builtins.super")
|
|
|
|
def test_inferring_unbound_super_doesnt_work(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
class Test(object):
|
|
def __init__(self):
|
|
super(Test) #@
|
|
"""
|
|
)
|
|
unbounded = next(node.infer())
|
|
self.assertIsInstance(unbounded, bases.Instance)
|
|
self.assertEqual(unbounded.qname(), "builtins.super")
|
|
|
|
def test_use_default_inference_on_not_inferring_args(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class Test(object):
|
|
def __init__(self):
|
|
super(Lala, self) #@
|
|
super(Test, lala) #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(first, bases.Instance)
|
|
self.assertEqual(first.qname(), "builtins.super")
|
|
|
|
second = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(second, bases.Instance)
|
|
self.assertEqual(second.qname(), "builtins.super")
|
|
|
|
def test_no_arguments_super(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class First(object): pass
|
|
class Second(First):
|
|
def test(self):
|
|
super() #@
|
|
@classmethod
|
|
def test_classmethod(cls):
|
|
super() #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(first, objects.Super)
|
|
self.assertIsInstance(first.type, bases.Instance)
|
|
self.assertEqual(first.type.name, "Second")
|
|
self.assertIsInstance(first.mro_pointer, nodes.ClassDef)
|
|
self.assertEqual(first.mro_pointer.name, "Second")
|
|
|
|
second = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(second, objects.Super)
|
|
self.assertIsInstance(second.type, nodes.ClassDef)
|
|
self.assertEqual(second.type.name, "Second")
|
|
self.assertIsInstance(second.mro_pointer, nodes.ClassDef)
|
|
self.assertEqual(second.mro_pointer.name, "Second")
|
|
|
|
def test_super_simple_cases(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class First(object): pass
|
|
class Second(First): pass
|
|
class Third(First):
|
|
def test(self):
|
|
super(Third, self) #@
|
|
super(Second, self) #@
|
|
|
|
# mro position and the type
|
|
super(Third, Third) #@
|
|
super(Third, Second) #@
|
|
super(Fourth, Fourth) #@
|
|
|
|
class Fourth(Third):
|
|
pass
|
|
"""
|
|
)
|
|
|
|
# .type is the object which provides the mro.
|
|
# .mro_pointer is the position in the mro from where
|
|
# the lookup should be done.
|
|
|
|
# super(Third, self)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(first, objects.Super)
|
|
self.assertIsInstance(first.type, bases.Instance)
|
|
self.assertEqual(first.type.name, "Third")
|
|
self.assertIsInstance(first.mro_pointer, nodes.ClassDef)
|
|
self.assertEqual(first.mro_pointer.name, "Third")
|
|
|
|
# super(Second, self)
|
|
second = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(second, objects.Super)
|
|
self.assertIsInstance(second.type, bases.Instance)
|
|
self.assertEqual(second.type.name, "Third")
|
|
self.assertIsInstance(first.mro_pointer, nodes.ClassDef)
|
|
self.assertEqual(second.mro_pointer.name, "Second")
|
|
|
|
# super(Third, Third)
|
|
third = next(ast_nodes[2].infer())
|
|
self.assertIsInstance(third, objects.Super)
|
|
self.assertIsInstance(third.type, nodes.ClassDef)
|
|
self.assertEqual(third.type.name, "Third")
|
|
self.assertIsInstance(third.mro_pointer, nodes.ClassDef)
|
|
self.assertEqual(third.mro_pointer.name, "Third")
|
|
|
|
# super(Third, second)
|
|
fourth = next(ast_nodes[3].infer())
|
|
self.assertIsInstance(fourth, objects.Super)
|
|
self.assertIsInstance(fourth.type, nodes.ClassDef)
|
|
self.assertEqual(fourth.type.name, "Second")
|
|
self.assertIsInstance(fourth.mro_pointer, nodes.ClassDef)
|
|
self.assertEqual(fourth.mro_pointer.name, "Third")
|
|
|
|
# Super(Fourth, Fourth)
|
|
fifth = next(ast_nodes[4].infer())
|
|
self.assertIsInstance(fifth, objects.Super)
|
|
self.assertIsInstance(fifth.type, nodes.ClassDef)
|
|
self.assertEqual(fifth.type.name, "Fourth")
|
|
self.assertIsInstance(fifth.mro_pointer, nodes.ClassDef)
|
|
self.assertEqual(fifth.mro_pointer.name, "Fourth")
|
|
|
|
def test_super_infer(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
class Super(object):
|
|
def __init__(self):
|
|
super(Super, self) #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, objects.Super)
|
|
reinferred = next(inferred.infer())
|
|
self.assertIsInstance(reinferred, objects.Super)
|
|
self.assertIs(inferred, reinferred)
|
|
|
|
def test_inferring_invalid_supers(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class Super(object):
|
|
def __init__(self):
|
|
# MRO pointer is not a type
|
|
super(1, self) #@
|
|
# MRO type is not a subtype
|
|
super(Super, 1) #@
|
|
# self is not a subtype of Bupper
|
|
super(Bupper, self) #@
|
|
class Bupper(Super):
|
|
pass
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(first, objects.Super)
|
|
with self.assertRaises(SuperError) as cm:
|
|
first.super_mro()
|
|
self.assertIsInstance(cm.exception.super_.mro_pointer, nodes.Const)
|
|
self.assertEqual(cm.exception.super_.mro_pointer.value, 1)
|
|
for node, invalid_type in zip(ast_nodes[1:], (nodes.Const, bases.Instance)):
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, objects.Super, node)
|
|
with self.assertRaises(SuperError) as cm:
|
|
inferred.super_mro()
|
|
self.assertIsInstance(cm.exception.super_.type, invalid_type)
|
|
|
|
def test_proxied(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
class Super(object):
|
|
def __init__(self):
|
|
super(Super, self) #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
proxied = inferred._proxied
|
|
self.assertEqual(proxied.qname(), "builtins.super")
|
|
self.assertIsInstance(proxied, nodes.ClassDef)
|
|
|
|
def test_super_bound_model(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class First(object):
|
|
def method(self):
|
|
pass
|
|
@classmethod
|
|
def class_method(cls):
|
|
pass
|
|
class Super_Type_Type(First):
|
|
def method(self):
|
|
super(Super_Type_Type, Super_Type_Type).method #@
|
|
super(Super_Type_Type, Super_Type_Type).class_method #@
|
|
@classmethod
|
|
def class_method(cls):
|
|
super(Super_Type_Type, Super_Type_Type).method #@
|
|
super(Super_Type_Type, Super_Type_Type).class_method #@
|
|
|
|
class Super_Type_Object(First):
|
|
def method(self):
|
|
super(Super_Type_Object, self).method #@
|
|
super(Super_Type_Object, self).class_method #@
|
|
"""
|
|
)
|
|
# Super(type, type) is the same for both functions and classmethods.
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(first, nodes.FunctionDef)
|
|
self.assertEqual(first.name, "method")
|
|
|
|
second = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(second, bases.BoundMethod)
|
|
self.assertEqual(second.bound.name, "First")
|
|
self.assertEqual(second.type, "classmethod")
|
|
|
|
third = next(ast_nodes[2].infer())
|
|
self.assertIsInstance(third, nodes.FunctionDef)
|
|
self.assertEqual(third.name, "method")
|
|
|
|
fourth = next(ast_nodes[3].infer())
|
|
self.assertIsInstance(fourth, bases.BoundMethod)
|
|
self.assertEqual(fourth.bound.name, "First")
|
|
self.assertEqual(fourth.type, "classmethod")
|
|
|
|
# Super(type, obj) can lead to different attribute bindings
|
|
# depending on the type of the place where super was called.
|
|
fifth = next(ast_nodes[4].infer())
|
|
self.assertIsInstance(fifth, bases.BoundMethod)
|
|
self.assertEqual(fifth.bound.name, "First")
|
|
self.assertEqual(fifth.type, "method")
|
|
|
|
sixth = next(ast_nodes[5].infer())
|
|
self.assertIsInstance(sixth, bases.BoundMethod)
|
|
self.assertEqual(sixth.bound.name, "First")
|
|
self.assertEqual(sixth.type, "classmethod")
|
|
|
|
def test_super_getattr_single_inheritance(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class First(object):
|
|
def test(self): pass
|
|
class Second(First):
|
|
def test2(self): pass
|
|
class Third(Second):
|
|
test3 = 42
|
|
def __init__(self):
|
|
super(Third, self).test2 #@
|
|
super(Third, self).test #@
|
|
# test3 is local, no MRO lookup is done.
|
|
super(Third, self).test3 #@
|
|
super(Third, self) #@
|
|
|
|
# Unbounds.
|
|
super(Third, Third).test2 #@
|
|
super(Third, Third).test #@
|
|
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(first, bases.BoundMethod)
|
|
self.assertEqual(first.bound.name, "Second")
|
|
|
|
second = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(second, bases.BoundMethod)
|
|
self.assertEqual(second.bound.name, "First")
|
|
|
|
with self.assertRaises(InferenceError):
|
|
next(ast_nodes[2].infer())
|
|
fourth = next(ast_nodes[3].infer())
|
|
with self.assertRaises(AttributeInferenceError):
|
|
fourth.getattr("test3")
|
|
with self.assertRaises(AttributeInferenceError):
|
|
next(fourth.igetattr("test3"))
|
|
|
|
first_unbound = next(ast_nodes[4].infer())
|
|
self.assertIsInstance(first_unbound, nodes.FunctionDef)
|
|
self.assertEqual(first_unbound.name, "test2")
|
|
self.assertEqual(first_unbound.parent.name, "Second")
|
|
|
|
second_unbound = next(ast_nodes[5].infer())
|
|
self.assertIsInstance(second_unbound, nodes.FunctionDef)
|
|
self.assertEqual(second_unbound.name, "test")
|
|
self.assertEqual(second_unbound.parent.name, "First")
|
|
|
|
def test_super_invalid_mro(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
class A(object):
|
|
test = 42
|
|
class Super(A, A):
|
|
def __init__(self):
|
|
super(Super, self) #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
with self.assertRaises(AttributeInferenceError):
|
|
next(inferred.getattr("test"))
|
|
|
|
def test_super_complex_mro(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class A(object):
|
|
def spam(self): return "A"
|
|
def foo(self): return "A"
|
|
@staticmethod
|
|
def static(self): pass
|
|
class B(A):
|
|
def boo(self): return "B"
|
|
def spam(self): return "B"
|
|
class C(A):
|
|
def boo(self): return "C"
|
|
class E(C, B):
|
|
def __init__(self):
|
|
super(E, self).boo #@
|
|
super(C, self).boo #@
|
|
super(E, self).spam #@
|
|
super(E, self).foo #@
|
|
super(E, self).static #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(first, bases.BoundMethod)
|
|
self.assertEqual(first.bound.name, "C")
|
|
second = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(second, bases.BoundMethod)
|
|
self.assertEqual(second.bound.name, "B")
|
|
third = next(ast_nodes[2].infer())
|
|
self.assertIsInstance(third, bases.BoundMethod)
|
|
self.assertEqual(third.bound.name, "B")
|
|
fourth = next(ast_nodes[3].infer())
|
|
self.assertEqual(fourth.bound.name, "A")
|
|
static = next(ast_nodes[4].infer())
|
|
self.assertIsInstance(static, nodes.FunctionDef)
|
|
self.assertEqual(static.parent.scope().name, "A")
|
|
|
|
def test_super_data_model(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class X(object): pass
|
|
class A(X):
|
|
def __init__(self):
|
|
super(A, self) #@
|
|
super(A, A) #@
|
|
super(X, A) #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
thisclass = first.getattr("__thisclass__")[0]
|
|
self.assertIsInstance(thisclass, nodes.ClassDef)
|
|
self.assertEqual(thisclass.name, "A")
|
|
selfclass = first.getattr("__self_class__")[0]
|
|
self.assertIsInstance(selfclass, nodes.ClassDef)
|
|
self.assertEqual(selfclass.name, "A")
|
|
self_ = first.getattr("__self__")[0]
|
|
self.assertIsInstance(self_, bases.Instance)
|
|
self.assertEqual(self_.name, "A")
|
|
cls = first.getattr("__class__")[0]
|
|
self.assertEqual(cls, first._proxied)
|
|
|
|
second = next(ast_nodes[1].infer())
|
|
thisclass = second.getattr("__thisclass__")[0]
|
|
self.assertEqual(thisclass.name, "A")
|
|
self_ = second.getattr("__self__")[0]
|
|
self.assertIsInstance(self_, nodes.ClassDef)
|
|
self.assertEqual(self_.name, "A")
|
|
|
|
third = next(ast_nodes[2].infer())
|
|
thisclass = third.getattr("__thisclass__")[0]
|
|
self.assertEqual(thisclass.name, "X")
|
|
selfclass = third.getattr("__self_class__")[0]
|
|
self.assertEqual(selfclass.name, "A")
|
|
|
|
def assertEqualMro(self, klass: Super, expected_mro: List[str]) -> None:
|
|
self.assertEqual([member.name for member in klass.super_mro()], expected_mro)
|
|
|
|
def test_super_mro(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
class A(object): pass
|
|
class B(A): pass
|
|
class C(A): pass
|
|
class E(C, B):
|
|
def __init__(self):
|
|
super(E, self) #@
|
|
super(C, self) #@
|
|
super(B, self) #@
|
|
|
|
super(B, 1) #@
|
|
super(1, B) #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertEqualMro(first, ["C", "B", "A", "object"])
|
|
second = next(ast_nodes[1].infer())
|
|
self.assertEqualMro(second, ["B", "A", "object"])
|
|
third = next(ast_nodes[2].infer())
|
|
self.assertEqualMro(third, ["A", "object"])
|
|
|
|
fourth = next(ast_nodes[3].infer())
|
|
with self.assertRaises(SuperError):
|
|
fourth.super_mro()
|
|
fifth = next(ast_nodes[4].infer())
|
|
with self.assertRaises(SuperError):
|
|
fifth.super_mro()
|
|
|
|
def test_super_yes_objects(self) -> None:
|
|
ast_nodes = builder.extract_node(
|
|
"""
|
|
from collections import Missing
|
|
class A(object):
|
|
def __init__(self):
|
|
super(Missing, self) #@
|
|
super(A, Missing) #@
|
|
"""
|
|
)
|
|
assert isinstance(ast_nodes, list)
|
|
first = next(ast_nodes[0].infer())
|
|
self.assertIsInstance(first, bases.Instance)
|
|
second = next(ast_nodes[1].infer())
|
|
self.assertIsInstance(second, bases.Instance)
|
|
|
|
def test_super_invalid_types(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
import collections
|
|
class A(object):
|
|
def __init__(self):
|
|
super(A, collections) #@
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
with self.assertRaises(SuperError):
|
|
inferred.super_mro()
|
|
with self.assertRaises(SuperError):
|
|
inferred.super_mro()
|
|
|
|
def test_super_properties(self) -> None:
|
|
node = builder.extract_node(
|
|
"""
|
|
class Foo(object):
|
|
@property
|
|
def dict(self):
|
|
return 42
|
|
|
|
class Bar(Foo):
|
|
@property
|
|
def dict(self):
|
|
return super(Bar, self).dict
|
|
|
|
Bar().dict
|
|
"""
|
|
)
|
|
inferred = next(node.infer())
|
|
self.assertIsInstance(inferred, nodes.Const)
|
|
self.assertEqual(inferred.value, 42)
|
|
|
|
def test_super_qname(self) -> None:
|
|
"""Make sure a Super object generates a qname
|
|
equivalent to super.__qname__
|
|
"""
|
|
# See issue 533
|
|
code = """
|
|
class C:
|
|
def foo(self): return super()
|
|
C().foo() #@
|
|
"""
|
|
super_obj = next(builder.extract_node(code).infer())
|
|
self.assertEqual(super_obj.qname(), "super")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|