| # 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 |
| |
| |
| @common_params(node="x") |
| def test_comprehension_condition( |
| condition: str, satisfy_val: int | None, fail_val: int | None |
| ) -> None: |
| """Test constraint for a comprehension target used in the element expression.""" |
| node = builder.extract_node( |
| f"[x for x in [{satisfy_val}, {fail_val}] if {condition}]" |
| ) |
| inferred = node.elt.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == satisfy_val |
| |
| |
| def test_comprehension_condition_all_comprehension_types() -> None: |
| """Test that comprehension conditions apply in every type of comprehension.""" |
| list_comp, set_comp, gen_exp, dict_comp = builder.extract_node(""" |
| [x for x in [None, 3] if x is not None] #@ |
| {x for x in [None, 3] if x is not None} #@ |
| (x for x in [None, 3] if x is not None) #@ |
| {x: x for x in [None, 3] if x is not None} #@ |
| """) |
| for comp in (list_comp, set_comp, gen_exp): |
| msg = node_info(comp) |
| inferred = comp.elt.inferred() |
| assert len(inferred) == 1, msg |
| assert isinstance(inferred[0], nodes.Const), msg |
| assert inferred[0].value == 3, msg |
| |
| for expr in (dict_comp.key, dict_comp.value): |
| inferred = expr.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| |
| def test_comprehension_condition_unsupported_pattern() -> None: |
| """Test that a condition matching no supported constraint doesn't filter.""" |
| node = builder.extract_node("[L for L in [[1, 2], [3, 4]] if sum(L) == 3]") |
| inferred = node.elt.inferred() |
| assert [elt.as_string() for elt in inferred] == ["[1, 2]", "[3, 4]"] |
| |
| |
| def test_comprehension_condition_multiple_conditions() -> None: |
| """Test that all conditions apply to the element expression and that earlier |
| conditions also guard later ones. |
| """ |
| node = builder.extract_node("[x for x in [None, 3, 4] if x is not None if x == 3]") |
| inferred = node.elt.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| # ``x`` in the second condition is only guarded by the first condition. |
| second_condition = node.generators[0].ifs[1] |
| inferred = second_condition.left.inferred() |
| assert [const.value for const in inferred] == [3, 4] |
| |
| |
| def test_comprehension_condition_guards_later_generators() -> None: |
| """Test that a generator's conditions guard the iterables of later generators.""" |
| node = builder.extract_node("[y for x in [None, [3]] if x is not None for y in x]") |
| inferred = node.elt.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 3 |
| |
| |
| def test_comprehension_condition_does_not_guard_same_generator() -> None: |
| """Test that a generator's conditions don't constrain its own target, iterable |
| or the element of a preceding generator. |
| """ |
| node = builder.extract_node(""" |
| def f(y = None): |
| return [x for x in [y] if y is not None] #@ |
| """) |
| # ``y`` in the iterable is not constrained by the condition. |
| iter_elt = node.value.generators[0].iter.elts[0] |
| inferred = iter_elt.inferred() |
| assert len(inferred) == 2 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value is None |
| assert inferred[1] is Uninferable |
| |
| |
| def test_comprehension_condition_outer_name() -> None: |
| """Test that comprehension conditions constrain names from outer scopes |
| used in the element expression. |
| """ |
| node = builder.extract_node(""" |
| def f(y = None): |
| return [y for x in [1, 2] if y is not None] #@ |
| """) |
| inferred = node.value.elt.inferred() |
| assert inferred == [Uninferable] |
| |
| |
| def test_isinstance_multiple_inferred_values() -> None: |
| """Test that an isinstance() constraint filters every inferred value. |
| |
| Checking a value must not pollute the inference context used to check |
| the following ones. |
| """ |
| node = builder.extract_node(""" |
| def f(y): |
| x = 2 if y else "a" |
| if isinstance(x, int): |
| return ( |
| x #@ |
| ) |
| """) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 2 |
| |
| |
| def test_equality_multiple_inferred_values() -> None: |
| """Test that an equality constraint with a name operand filters every |
| inferred value. |
| |
| Checking a value must not pollute the inference context used to check |
| the following ones. |
| """ |
| node = builder.extract_node(""" |
| y = 1 |
| |
| def f(cond): |
| x = 1 if cond else 2 |
| if x == y: |
| return ( |
| x #@ |
| ) |
| """) |
| inferred = node.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |
| |
| |
| def test_comprehension_condition_nested_in_call() -> None: |
| """Test that comprehension conditions constrain a name nested inside a call.""" |
| node = builder.extract_node("[f(x) for x in [None, 1] if x is not None]") |
| inferred = node.elt.args[0].inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |
| |
| node = builder.extract_node( |
| "[y for x in [None, 1] if x is not None for y in make_iter(x)]" |
| ) |
| inferred = node.generators[1].iter.args[0].inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |
| |
| |
| def test_comprehension_condition_nested_comprehension() -> None: |
| """Test that comprehension conditions constrain a name used inside an inner |
| comprehension. |
| """ |
| node = builder.extract_node( |
| "[[y for y in x] for x in [None, [1]] if x is not None]" |
| ) |
| inner = node.elt |
| |
| inferred = inner.generators[0].iter.inferred() |
| assert [iterable.as_string() for iterable in inferred] == ["[1]"] |
| |
| inferred = inner.elt.inferred() |
| assert len(inferred) == 1 |
| assert isinstance(inferred[0], nodes.Const) |
| assert inferred[0].value == 1 |