| """Tests for function call inference""" |
| |
| from astroid import bases, builder, nodes |
| from astroid.util import Uninferable |
| |
| |
| def test_no_return() -> None: |
| """Test function with no return statements""" |
| node = builder.extract_node( |
| """ |
| def f(): |
| pass |
| |
| f() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_one_return() -> None: |
| """Test function with a single return that always executes""" |
| node = builder.extract_node( |
| """ |
| def f(): |
| return 1 |
| |
| f() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |
| |
| |
| def test_one_return_possible() -> None: |
| """Test function with a single return that only sometimes executes |
| |
| Note: currently, inference doesn't handle this type of control flow |
| """ |
| node = builder.extract_node( |
| """ |
| def f(x): |
| if x: |
| return 1 |
| |
| f(1) #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |
| |
| |
| def test_multiple_returns() -> None: |
| """Test function with multiple returns""" |
| node = builder.extract_node( |
| """ |
| def f(x): |
| if x > 10: |
| return 1 |
| elif x > 20: |
| return 2 |
| else: |
| return 3 |
| |
| f(100) #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 3 |
| assert all(isinstance(node, nodes.Const) for node in inferred) |
| assert {node.value for node in inferred} == {1, 2, 3} |
| |
| |
| def test_argument() -> None: |
| """Test function whose return value uses its arguments""" |
| node = builder.extract_node( |
| """ |
| def f(x, y): |
| return x + y |
| |
| f(1, 2) #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| |
| def test_inner_call() -> None: |
| """Test function where return value is the result of a separate function call""" |
| node = builder.extract_node( |
| """ |
| def f(): |
| return g() |
| |
| def g(): |
| return 1 |
| |
| f() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |
| |
| |
| def test_inner_call_with_const_argument() -> None: |
| """Test function where return value is the result of a separate function call, |
| with a constant value passed to the inner function. |
| """ |
| node = builder.extract_node( |
| """ |
| def f(): |
| return g(1) |
| |
| def g(y): |
| return y + 2 |
| |
| f() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| |
| def test_inner_call_with_dynamic_argument() -> None: |
| """Test function where return value is the result of a separate function call, |
| with a dynamic value passed to the inner function. |
| |
| Currently, this is Uninferable. |
| """ |
| node = builder.extract_node( |
| """ |
| def f(x): |
| return g(x) |
| |
| def g(y): |
| return y + 2 |
| |
| f(1) #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_method_const_instance_attr() -> None: |
| """Test method where the return value is based on an instance attribute with a |
| constant value. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| def __init__(self): |
| self.x = 1 |
| |
| def get_x(self): |
| return self.x |
| |
| A().get_x() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |
| |
| |
| def test_method_const_instance_attr_multiple() -> None: |
| """Test method where the return value is based on an instance attribute with |
| multiple possible constant values, across different methods. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| def __init__(self, x): |
| if x: |
| self.x = 1 |
| else: |
| self.x = 2 |
| |
| def set_x(self): |
| self.x = 3 |
| |
| def get_x(self): |
| return self.x |
| |
| A().get_x() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 3 |
| assert all(isinstance(node, nodes.Const) for node in inferred) |
| assert {node.value for node in inferred} == {1, 2, 3} |
| |
| |
| def test_method_const_instance_attr_same_method() -> None: |
| """Test method where the return value is based on an instance attribute with |
| multiple possible constant values, including in the method being called. |
| |
| Note that even with a simple control flow where the assignment in the method body |
| is guaranteed to override any previous assignments, all possible constant values |
| are returned. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| def __init__(self, x): |
| if x: |
| self.x = 1 |
| else: |
| self.x = 2 |
| |
| def set_x(self): |
| self.x = 3 |
| |
| def get_x(self): |
| self.x = 4 |
| return self.x |
| |
| A().get_x() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 4 |
| assert all(isinstance(node, nodes.Const) for node in inferred) |
| assert {node.value for node in inferred} == {1, 2, 3, 4} |
| |
| |
| def test_method_dynamic_instance_attr_1() -> None: |
| """Test method where the return value is based on an instance attribute with |
| a dynamically-set value in a different method. |
| |
| In this case, the return value is Uninferable. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| def __init__(self, x): |
| self.x = x |
| |
| def get_x(self): |
| return self.x |
| |
| A(1).get_x() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_method_dynamic_instance_attr_2() -> None: |
| """Test method where the return value is based on an instance attribute with |
| a dynamically-set value in the same method. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| # Note: no initializer, so the only assignment happens in get_x |
| |
| def get_x(self, x): |
| self.x = x |
| return self.x |
| |
| A().get_x(1) #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |
| |
| |
| def test_method_dynamic_instance_attr_3() -> None: |
| """Test method where the return value is based on an instance attribute with |
| a dynamically-set value in a different method. |
| |
| This is currently Uninferable. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| def get_x(self, x): # x is unused |
| return self.x |
| |
| def set_x(self, x): |
| self.x = x |
| |
| A().get_x(10) #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable # not 10! |
| |
| |
| def test_method_dynamic_instance_attr_4() -> None: |
| """Test method where the return value is based on an instance attribute with |
| a dynamically-set value in a different method, and is passed a constant value. |
| |
| This is currently Uninferable. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| # Note: no initializer, so the only assignment happens in get_x |
| |
| def get_x(self): |
| self.set_x(10) |
| return self.x |
| |
| def set_x(self, x): |
| self.x = x |
| |
| A().get_x() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_method_dynamic_instance_attr_5() -> None: |
| """Test method where the return value is based on an instance attribute with |
| a dynamically-set value in a different method, and is passed a constant value. |
| |
| But, where the outer and inner functions have the same signature. |
| |
| Inspired by https://github.com/PyCQA/pylint/issues/400 |
| |
| This is currently Uninferable. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| # Note: no initializer, so the only assignment happens in get_x |
| |
| def get_x(self, x): |
| self.set_x(10) |
| return self.x |
| |
| def set_x(self, x): |
| self.x = x |
| |
| A().get_x(1) #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_method_dynamic_instance_attr_6() -> None: |
| """Test method where the return value is based on an instance attribute with |
| a dynamically-set value in a different method, and is passed a dynamic value. |
| |
| This is currently Uninferable. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| # Note: no initializer, so the only assignment happens in get_x |
| |
| def get_x(self, x): |
| self.set_x(x + 1) |
| return self.x |
| |
| def set_x(self, x): |
| self.x = x |
| |
| A().get_x(1) #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_dunder_getitem() -> None: |
| """Test for the special method __getitem__ (used by Instance.getitem). |
| |
| This is currently Uninferable, until we can infer instance attribute values through |
| constructor calls. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| def __init__(self, x): |
| self.x = x |
| |
| def __getitem__(self, i): |
| return self.x + i |
| |
| A(1)[2] #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_instance_method() -> None: |
| """Tests for instance method, both bound and unbound.""" |
| nodes_ = builder.extract_node( |
| """ |
| class A: |
| def method(self, x): |
| return x |
| |
| A().method(42) #@ |
| |
| # In this case, the 1 argument is bound to self, which is ignored in the method |
| A.method(1, 42) #@ |
| """ |
| ) |
| |
| for node in nodes_: |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 42 |
| |
| |
| def test_class_method() -> None: |
| """Tests for class method calls, both instance and with the class.""" |
| nodes_ = builder.extract_node( |
| """ |
| class A: |
| @classmethod |
| def method(cls, x): |
| return x |
| |
| A.method(42) #@ |
| A().method(42) #@ |
| |
| """ |
| ) |
| |
| for node in nodes_: |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const), node |
| assert inferred[0].value == 42 |
| |
| |
| def test_static_method() -> None: |
| """Tests for static method calls, both instance and with the class.""" |
| nodes_ = builder.extract_node( |
| """ |
| class A: |
| @staticmethod |
| def method(x): |
| return x |
| |
| A.method(42) #@ |
| A().method(42) #@ |
| """ |
| ) |
| |
| for node in nodes_: |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const), node |
| assert inferred[0].value == 42 |
| |
| |
| def test_instance_method_inherited() -> None: |
| """Tests for instance methods that are inherited from a superclass. |
| |
| Based on https://github.com/PyCQA/astroid/issues/1008. |
| """ |
| nodes_ = builder.extract_node( |
| """ |
| class A: |
| def method(self): |
| return self |
| |
| class B(A): |
| pass |
| |
| A().method() #@ |
| A.method(A()) #@ |
| |
| B().method() #@ |
| B.method(B()) #@ |
| A.method(B()) #@ |
| """ |
| ) |
| expected = ["A", "A", "B", "B", "B"] |
| for node, expected in zip(nodes_, expected): |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], bases.Instance) |
| assert inferred[0].name == expected |
| |
| |
| def test_class_method_inherited() -> None: |
| """Tests for class methods that are inherited from a superclass. |
| |
| Based on https://github.com/PyCQA/astroid/issues/1008. |
| """ |
| nodes_ = builder.extract_node( |
| """ |
| class A: |
| @classmethod |
| def method(cls): |
| return cls |
| |
| class B(A): |
| pass |
| |
| A().method() #@ |
| A.method() #@ |
| |
| B().method() #@ |
| B.method() #@ |
| """ |
| ) |
| expected = ["A", "A", "B", "B"] |
| for node, expected in zip(nodes_, expected): |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.ClassDef) |
| assert inferred[0].name == expected |
| |
| |
| def test_chained_attribute_inherited() -> None: |
| """Tests for class methods that are inherited from a superclass. |
| |
| Based on https://github.com/PyCQA/pylint/issues/4220. |
| """ |
| node = builder.extract_node( |
| """ |
| class A: |
| def f(self): |
| return 42 |
| |
| |
| class B(A): |
| def __init__(self): |
| self.a = A() |
| result = self.a.f() |
| |
| def f(self): |
| pass |
| |
| |
| B().a.f() #@ |
| """ |
| ) |
| assert isinstance(node, nodes.NodeNG) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 42 |