| # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html |
| # For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE |
| # Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt |
| |
| """Tests for inference involving constraints.""" |
| |
| from __future__ import annotations |
| |
| from unittest.mock import patch |
| |
| import pytest |
| |
| from astroid import builder, nodes |
| from astroid.bases import Instance |
| from astroid.util import Uninferable |
| |
| |
| def node_info(node: nodes.NodeNG) -> str: |
| return f"Inference of {node.as_string()!r} at line {node.lineno}" |
| |
| |
| def common_params(node: str) -> pytest.MarkDecorator: |
| return pytest.mark.parametrize( |
| ("condition", "satisfy_val", "fail_val"), |
| ( |
| (f"{node} is None", None, 3), |
| (f"{node} is not None", 3, None), |
| (f"{node}", 3, None), |
| (f"not {node}", None, 3), |
| (f"isinstance({node}, int)", 3, None), |
| (f"isinstance({node}, (int, str))", 3, None), |
| (f"{node} == 3", 3, None), |
| (f"{node} != 3", None, 3), |
| (f"3 == {node}", 3, None), |
| (f"3 != {node}", None, 3), |
| ), |
| ) |
| |
| |
| @common_params(node="x") |
| def test_if_single_statement( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test constraint for a variable that is used in the first statement of an if body.""" |
| node1, node2 = builder.extract_node(f""" |
| def f1(x = {fail_val}): |
| if {condition}: # Filters out default value |
| return ( |
| x #@ |
| ) |
| |
| def f2(x = {satisfy_val}): |
| if {condition}: # Does not filter out default value |
| return ( |
| x #@ |
| ) |
| """) |
| |
| inferred = node1.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == satisfy_val |
| |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_if_multiple_statements( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test constraint for a variable that is used in an if body with multiple |
| statements. |
| """ |
| node1, node2 = builder.extract_node(f""" |
| def f1(x = {fail_val}): |
| if {condition}: # Filters out default value |
| print(x) |
| return ( |
| x #@ |
| ) |
| |
| def f2(x = {satisfy_val}): |
| if {condition}: # Does not filter out default value |
| print(x) |
| return ( |
| x #@ |
| ) |
| """) |
| |
| inferred = node1.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == satisfy_val |
| |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_if_irrelevant_condition( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint for a different variable doesn't apply.""" |
| nodes_ = builder.extract_node(f""" |
| def f1(x, y = {fail_val}): |
| if {condition}: # Does not filter out fail_val |
| return ( |
| y #@ |
| ) |
| |
| def f2(x, y = {satisfy_val}): |
| if {condition}: |
| return ( |
| y #@ |
| ) |
| """) |
| for node, val in zip(nodes_, (fail_val, satisfy_val)): |
| inferred = node.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == val |
| |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_outside_if( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply outside of the if.""" |
| nodes_ = builder.extract_node(f""" |
| def f1(x = {fail_val}): |
| if {condition}: |
| pass |
| return ( |
| x #@ |
| ) |
| |
| def f2(x = {satisfy_val}): |
| if {condition}: |
| pass |
| |
| return ( |
| x #@ |
| ) |
| """) |
| for node, val in zip(nodes_, (fail_val, satisfy_val)): |
| inferred = node.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == val |
| |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_nested_if( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition applies within inner if statements.""" |
| node1, node2 = builder.extract_node(f""" |
| def f1(y, x = {fail_val}): |
| if {condition}: |
| if y is not None: |
| return ( |
| x #@ |
| ) |
| |
| def f2(y, x = {satisfy_val}): |
| if {condition}: |
| if y is not None: |
| return ( |
| x #@ |
| ) |
| """) |
| inferred = node1.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == satisfy_val |
| |
| assert inferred[1] is Uninferable |
| |
| |
| def test_if_uninferable() -> None: |
| """Test that when no inferred values satisfy all constraints, Uninferable is |
| inferred. |
| """ |
| node1, node2 = builder.extract_node(""" |
| def f1(): |
| x = None |
| if x is not None: |
| x #@ |
| |
| def f2(): |
| x = 1 |
| if x is not None: |
| pass |
| else: |
| x #@ |
| """) |
| inferred = node1.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_if_reassignment_in_body( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply when the variable |
| is assigned to a failing value inside the if body. |
| """ |
| node = builder.extract_node(f""" |
| def f(x, y): |
| if {condition}: |
| if y: |
| x = {fail_val} |
| return ( |
| x #@ |
| ) |
| """) |
| inferred = node.inferred() |
| assert len(inferred) == 2 |
| assert inferred[0] is Uninferable |
| |
| assert isinstance(inferred[1], nodes.Const) |
| assert inferred[1].value == fail_val |
| |
| |
| @common_params(node="x") |
| def test_if_elif_else_negates( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition is negated when the variable |
| is used in the elif and else branches. |
| """ |
| node1, node2, node3, node4 = builder.extract_node(f""" |
| def f1(y, x = {fail_val}): |
| if {condition}: |
| pass |
| elif y: # Does not filter out default value |
| return ( |
| x #@ |
| ) |
| else: # Does not filter out default value |
| return ( |
| x #@ |
| ) |
| |
| def f2(y, x = {satisfy_val}): |
| if {condition}: |
| pass |
| elif y: # Filters out default value |
| return ( |
| x #@ |
| ) |
| else: # Filters out default value |
| return ( |
| x #@ |
| ) |
| """) |
| for node in (node1, node2): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 2, msg |
| assert isinstance(inferred[0], nodes.Const), msg |
| assert inferred[0].value == fail_val, msg |
| |
| assert inferred[1] is Uninferable, msg |
| |
| for node in (node3, node4): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert inferred[0] is Uninferable, msg |
| |
| |
| @common_params(node="x") |
| def test_if_reassignment_in_else( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply when the variable |
| is assigned to a failing value inside the else branch. |
| """ |
| node = builder.extract_node(f""" |
| def f(x, y): |
| if {condition}: |
| return x |
| else: |
| if y: |
| x = {satisfy_val} |
| return ( |
| x #@ |
| ) |
| """) |
| inferred = node.inferred() |
| assert len(inferred) == 2 |
| assert inferred[0] is Uninferable |
| |
| assert isinstance(inferred[1], nodes.Const) |
| assert inferred[1].value == satisfy_val |
| |
| |
| @common_params(node="x") |
| def test_if_comprehension_shadow( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply when the variable |
| is shadowed by an inner comprehension scope. |
| """ |
| node = builder.extract_node(f""" |
| def f(x): |
| if {condition}: |
| return [ |
| x #@ |
| for x in [{satisfy_val}, {fail_val}] |
| ] |
| """) |
| inferred = node.inferred() |
| assert len(inferred) == 2 |
| |
| for actual, expected in zip(inferred, (satisfy_val, fail_val)): |
| assert isinstance(actual, nodes.Const) |
| assert actual.value == expected |
| |
| |
| @common_params(node="x") |
| def test_if_function_shadow( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply when the variable |
| is shadowed by an inner function scope. |
| """ |
| node = builder.extract_node(f""" |
| x = {satisfy_val} |
| if {condition}: |
| def f(x = {fail_val}): |
| return ( |
| x #@ |
| ) |
| """) |
| inferred = node.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == fail_val |
| |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_if_function_call( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply for a parameter |
| a different function call, but with the same name. |
| """ |
| node = builder.extract_node(f""" |
| def f(x = {satisfy_val}): |
| if {condition}: |
| g({fail_val}) #@ |
| |
| def g(x): |
| return x |
| """) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == fail_val |
| |
| |
| @common_params(node="self.x") |
| def test_if_instance_attr( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test constraint for an instance attribute in an if statement.""" |
| node1, node2 = builder.extract_node(f""" |
| class A1: |
| def __init__(self, x = {fail_val}): |
| self.x = x |
| |
| def method(self): |
| if {condition}: |
| self.x #@ |
| |
| class A2: |
| def __init__(self, x = {satisfy_val}): |
| self.x = x |
| |
| def method(self): |
| if {condition}: |
| self.x #@ |
| """) |
| |
| inferred = node1.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == satisfy_val |
| |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="self.x") |
| def test_if_instance_attr_reassignment_in_body( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply to an instance attribute |
| when it is assigned inside the if body. |
| """ |
| node1, node2 = builder.extract_node(f""" |
| class A1: |
| def __init__(self, x): |
| self.x = x |
| |
| def method1(self): |
| if {condition}: |
| self.x = {satisfy_val} |
| self.x #@ |
| |
| def method2(self): |
| if {condition}: |
| self.x = {fail_val} |
| self.x #@ |
| """) |
| |
| inferred = node1.inferred() |
| assert len(inferred) == 2 |
| assert inferred[0] is Uninferable |
| |
| assert isinstance(inferred[1], nodes.Const) |
| assert inferred[1].value == satisfy_val |
| |
| inferred = node2.inferred() |
| assert len(inferred) == 3 |
| assert inferred[0] is Uninferable |
| |
| assert isinstance(inferred[1], nodes.Const) |
| assert inferred[1].value == satisfy_val |
| |
| assert isinstance(inferred[2], nodes.Const) |
| assert inferred[2].value == fail_val |
| |
| |
| @common_params(node="x") |
| def test_if_instance_attr_varname_collision1( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply to an instance attribute |
| when the constraint refers to a variable with the same name. |
| """ |
| node1, node2 = builder.extract_node(f""" |
| class A1: |
| def __init__(self, x = {fail_val}): |
| self.x = x |
| |
| def method(self, x = {fail_val}): |
| if {condition}: |
| x #@ |
| self.x #@ |
| """) |
| |
| inferred = node1.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == fail_val |
| |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="self.x") |
| def test_if_instance_attr_varname_collision2( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply to a variable with the same |
| name. |
| """ |
| node1, node2 = builder.extract_node(f""" |
| class A1: |
| def __init__(self, x = {fail_val}): |
| self.x = x |
| |
| def method(self, x = {fail_val}): |
| if {condition}: |
| x #@ |
| self.x #@ |
| """) |
| |
| inferred = node1.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == fail_val |
| |
| assert inferred[1] is Uninferable |
| |
| inferred = node2.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| @common_params(node="self.x") |
| def test_if_instance_attr_varname_collision3( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply to an instance attribute |
| for an object of a different class. |
| """ |
| node = builder.extract_node(f""" |
| class A1: |
| def __init__(self, x = {fail_val}): |
| self.x = x |
| |
| def method(self): |
| obj = A2() |
| if {condition}: |
| obj.x #@ |
| |
| class A2: |
| def __init__(self): |
| self.x = {fail_val} |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == fail_val |
| |
| |
| @common_params(node="self.x") |
| def test_if_instance_attr_varname_collision4( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if condition doesn't apply to a variable of the same name, |
| when that variable is used to infer the value of the instance attribute. |
| """ |
| node = builder.extract_node(f""" |
| class A1: |
| def __init__(self, x): |
| self.x = x |
| |
| def method(self): |
| x = {fail_val} |
| if {condition}: |
| self.x = x |
| self.x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 2 |
| assert inferred[0] is Uninferable |
| |
| assert isinstance(inferred[1], nodes.Const) |
| assert inferred[1].value == fail_val |
| |
| |
| @common_params(node="x") |
| def test_if_exp_body( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test constraint for a variable that is used in an if exp body.""" |
| node1, node2 = builder.extract_node(f""" |
| def f1(x = {fail_val}): |
| return ( |
| x if {condition} else None #@ |
| ) |
| |
| def f2(x = {satisfy_val}): |
| return ( |
| x if {condition} else None #@ |
| ) |
| """) |
| |
| inferred = node1.body.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.body.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == satisfy_val |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_if_exp_else( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test constraint for a variable that is used in an if exp else block.""" |
| node1, node2 = builder.extract_node(f""" |
| def f1(x = {satisfy_val}): |
| return ( |
| None if {condition} else x #@ |
| ) |
| |
| def f2(x = {fail_val}): |
| return ( |
| None if {condition} else x #@ |
| ) |
| """) |
| |
| inferred = node1.orelse.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.orelse.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == fail_val |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_outside_if_exp( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if exp condition doesn't apply outside of the if exp.""" |
| nodes_ = builder.extract_node(f""" |
| def f1(x = {fail_val}): |
| x if {condition} else None |
| return ( |
| x #@ |
| ) |
| |
| def f2(x = {satisfy_val}): |
| None if {condition} else x |
| return ( |
| x #@ |
| ) |
| """) |
| for node, val in zip(nodes_, (fail_val, satisfy_val)): |
| inferred = node.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == val |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="x") |
| def test_nested_if_exp( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if exp condition applies within inner if exp.""" |
| node1, node2 = builder.extract_node(f""" |
| def f1(y, x = {fail_val}): |
| return ( |
| (x if y else None) if {condition} else None #@ |
| ) |
| |
| def f2(y, x = {satisfy_val}): |
| return ( |
| (x if y else None) if {condition} else None #@ |
| ) |
| """) |
| |
| inferred = node1.body.body.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.body.body.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == satisfy_val |
| assert inferred[1] is Uninferable |
| |
| |
| @common_params(node="self.x") |
| def test_if_exp_instance_attr( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test constraint for an instance attribute in an if exp.""" |
| node1, node2 = builder.extract_node(f""" |
| class A1: |
| def __init__(self, x = {fail_val}): |
| self.x = x |
| |
| def method(self): |
| return ( |
| self.x if {condition} else None #@ |
| ) |
| |
| class A2: |
| def __init__(self, x = {satisfy_val}): |
| self.x = x |
| |
| def method(self): |
| return ( |
| self.x if {condition} else None #@ |
| ) |
| """) |
| |
| inferred = node1.body.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| inferred = node2.body.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == satisfy_val |
| assert inferred[1].value is Uninferable |
| |
| |
| @common_params(node="self.x") |
| def test_if_exp_instance_attr_varname_collision( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test that constraint in an if exp condition doesn't apply to a variable with the same name.""" |
| node = builder.extract_node(f""" |
| class A: |
| def __init__(self, x = {fail_val}): |
| self.x = x |
| |
| def method(self, x = {fail_val}): |
| return ( |
| x if {condition} else None #@ |
| ) |
| """) |
| |
| inferred = node.body.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == fail_val |
| assert inferred[1].value is Uninferable |
| |
| |
| def test_isinstance_equal_types() -> None: |
| """Test constraint for an object whose type is equal to the checked type.""" |
| node = builder.extract_node(""" |
| class A: |
| pass |
| |
| x = A() |
| |
| if isinstance(x, A): |
| x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], Instance) |
| assert isinstance(inferred[0]._proxied, nodes.ClassDef) |
| assert inferred[0].name == "A" |
| |
| |
| def test_isinstance_subtype() -> None: |
| """Test constraint for an object whose type is a strict subtype of the checked type.""" |
| node = builder.extract_node(""" |
| class A: |
| pass |
| |
| class B(A): |
| pass |
| |
| x = B() |
| |
| if isinstance(x, A): |
| x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], Instance) |
| assert isinstance(inferred[0]._proxied, nodes.ClassDef) |
| assert inferred[0].name == "B" |
| |
| |
| def test_isinstance_unrelated_types(): |
| """Test constraint for an object whose type is not related to the checked type.""" |
| node = builder.extract_node(""" |
| class A: |
| pass |
| |
| class B: |
| pass |
| |
| x = A() |
| |
| if isinstance(x, B): |
| x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_isinstance_supertype(): |
| """Test constraint for an object whose type is a strict supertype of the checked type.""" |
| node = builder.extract_node(""" |
| class A: |
| pass |
| |
| class B(A): |
| pass |
| |
| x = A() |
| |
| if isinstance(x, B): |
| x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert inferred[0] is Uninferable |
| |
| |
| def test_isinstance_multiple_inheritance(): |
| """Test constraint for an object that inherits from more than one parent class.""" |
| n1, n2, n3 = builder.extract_node(""" |
| class A: |
| pass |
| |
| class B: |
| pass |
| |
| class C(A, B): |
| pass |
| |
| x = C() |
| |
| if isinstance(x, C): |
| x #@ |
| |
| if isinstance(x, A): |
| x #@ |
| |
| if isinstance(x, B): |
| x #@ |
| """) |
| |
| for node in (n1, n2, n3): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert isinstance(inferred[0], Instance), msg |
| assert isinstance(inferred[0]._proxied, nodes.ClassDef), msg |
| assert inferred[0].name == "C", msg |
| |
| |
| def test_isinstance_diamond_inheritance(): |
| """Test constraint for an object that inherits from parent classes |
| in diamond inheritance. |
| """ |
| n1, n2, n3, n4 = builder.extract_node(""" |
| class A(): |
| pass |
| |
| class B(A): |
| pass |
| |
| class C(A): |
| pass |
| |
| class D(B, C): |
| pass |
| |
| x = D() |
| |
| if isinstance(x, D): |
| x #@ |
| |
| if isinstance(x, B): |
| x #@ |
| |
| if isinstance(x, C): |
| x #@ |
| |
| if isinstance(x, A): |
| x #@ |
| """) |
| |
| for node in (n1, n2, n3, n4): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert isinstance(inferred[0], Instance), msg |
| assert isinstance(inferred[0]._proxied, nodes.ClassDef), msg |
| assert inferred[0].name == "D", msg |
| |
| |
| def test_isinstance_keyword_arguments(): |
| """Test that constraint does not apply when `isinstance` is called |
| with keyword arguments. |
| """ |
| n1, n2 = builder.extract_node(""" |
| x = 3 |
| |
| if isinstance(object=x, classinfo=str): |
| x #@ |
| |
| if isinstance(x, str, object=x, classinfo=str): |
| x #@ |
| """) |
| |
| for node in (n1, n2): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert isinstance(inferred[0], nodes.Const), msg |
| assert inferred[0].value == 3, msg |
| |
| |
| def test_isinstance_extra_argument(): |
| """Test that constraint does not apply when `isinstance` is called |
| with more than two positional arguments. |
| """ |
| node = builder.extract_node(""" |
| x = 3 |
| |
| if isinstance(x, str, bool): |
| x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| |
| def test_isinstance_classinfo_inference_error(): |
| """Test that constraint is satisfied when `isinstance` is called with |
| classinfo that raises an inference error. |
| """ |
| node = builder.extract_node(""" |
| x = 3 |
| |
| if isinstance(x, undefined_type): |
| x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| |
| def test_isinstance_uninferable_classinfo(): |
| """Test that constraint is satisfied when `isinstance` is called with |
| uninferable classinfo. |
| """ |
| node = builder.extract_node(""" |
| def f(classinfo): |
| x = 3 |
| |
| if isinstance(x, classinfo): |
| x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| |
| def test_isinstance_mro_error(): |
| """Test that constraint is satisfied when computing the object's |
| method resolution order raises an MRO error. |
| """ |
| node = builder.extract_node(""" |
| class A(): |
| pass |
| |
| class B(A, A): |
| pass |
| |
| x = B() |
| |
| if isinstance(x, A): |
| x #@ |
| """) |
| |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], Instance) |
| assert isinstance(inferred[0]._proxied, nodes.ClassDef) |
| assert inferred[0].name == "B" |
| |
| |
| def test_isinstance_uninferable(): |
| """Test that constraint is satisfied when `isinstance` inference returns Uninferable.""" |
| node = builder.extract_node(""" |
| x = 3 |
| |
| if isinstance(x, str): |
| x #@ |
| """) |
| |
| with patch( |
| "astroid.constraint.helpers.object_isinstance", return_value=Uninferable |
| ): |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| |
| def test_equality_callable(): |
| """Test constraint for equality of callables.""" |
| node1, node2, node3, node4, node5, node6 = builder.extract_node(""" |
| class Foo: |
| pass |
| |
| def bar(): |
| pass |
| |
| baz = lambda i : i |
| |
| x, y, z = Foo, bar, baz |
| |
| if x == Foo: |
| x #@ |
| if x != Foo: |
| x #@ |
| |
| if y == bar: |
| y #@ |
| if y != bar: |
| y #@ |
| |
| if z == baz: |
| z #@ |
| if z != baz: |
| z #@ |
| """) |
| |
| inferred = node1.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.ClassDef) |
| assert inferred[0].name == "Foo" |
| |
| inferred = node3.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.FunctionDef) |
| assert inferred[0].name == "bar" |
| |
| inferred = node5.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Lambda) |
| |
| for node in (node2, node4, node6): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert inferred[0] is Uninferable, msg |
| |
| |
| def test_equality_uninferable_operand(): |
| """Test that equality constraint is satisfied when either operand is uninferable.""" |
| node1, node2, node3, node4 = builder.extract_node(""" |
| def f1(x): |
| if x == 3: |
| x #@ |
| |
| if x != 3: |
| x #@ |
| |
| def f2(y): |
| x = 3 |
| if x == y: |
| x #@ |
| |
| if x != y: |
| x #@ |
| """) |
| |
| for node in (node1, node2): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert inferred[0] is Uninferable, msg |
| |
| for node in (node3, node4): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert isinstance(inferred[0], nodes.Const), msg |
| assert inferred[0].value == 3, msg |
| |
| |
| def test_equality_ambiguous_operand(): |
| """Test that equality constraint is satisfied when the compared operand has multiple inferred values.""" |
| node1, node2 = builder.extract_node(""" |
| def f(y = 1): |
| x = 3 |
| if x == y: |
| x #@ |
| |
| if x != y: |
| x #@ |
| """) |
| |
| for node in (node1, node2): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert isinstance(inferred[0], nodes.Const), msg |
| assert inferred[0].value == 3, msg |
| |
| |
| def test_equality_fractions(): |
| """Test that equality constraint is satisfied when both operands are fractions.""" |
| node1, node2, node3, node4 = builder.extract_node(""" |
| from fractions import Fraction |
| |
| x = Fraction(1, 3) |
| y = Fraction(1, 3) |
| |
| if x == y: |
| x #@ |
| y #@ |
| |
| if x != y: |
| x #@ |
| y #@ |
| """) |
| |
| for node in (node1, node2, node3, node4): |
| msg = node_info(node) |
| inferred = node.inferred() |
| assert len(inferred) == 1, msg |
| assert isinstance(inferred[0], Instance), msg |
| assert isinstance(inferred[0]._proxied, nodes.ClassDef), msg |
| assert inferred[0]._proxied.name == "Fraction", msg |