blob: 6566b03ef5e99518afdcd08846b786b3dc222190 [file] [log] [blame]
"""Test cases for the constraint solver used in type inference."""
from __future__ import annotations
from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint
from mypy.solve import Bounds, Graph, solve_constraints, transitive_closure
from mypy.test.helpers import Suite, assert_equal
from mypy.test.typefixture import TypeFixture
from mypy.types import Type, TypeVarId, TypeVarLikeType, TypeVarType
class SolveSuite(Suite):
def setUp(self) -> None:
self.fx = TypeFixture()
def test_empty_input(self) -> None:
self.assert_solve([], [], [])
def test_simple_supertype_constraints(self) -> None:
self.assert_solve([self.fx.t], [self.supc(self.fx.t, self.fx.a)], [self.fx.a])
self.assert_solve(
[self.fx.t],
[self.supc(self.fx.t, self.fx.a), self.supc(self.fx.t, self.fx.b)],
[self.fx.a],
)
def test_simple_subtype_constraints(self) -> None:
self.assert_solve([self.fx.t], [self.subc(self.fx.t, self.fx.a)], [self.fx.a])
self.assert_solve(
[self.fx.t],
[self.subc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.b)],
[self.fx.b],
)
def test_both_kinds_of_constraints(self) -> None:
self.assert_solve(
[self.fx.t],
[self.supc(self.fx.t, self.fx.b), self.subc(self.fx.t, self.fx.a)],
[self.fx.b],
)
def test_unsatisfiable_constraints(self) -> None:
# The constraints are impossible to satisfy.
self.assert_solve(
[self.fx.t], [self.supc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.b)], [None]
)
def test_exactly_specified_result(self) -> None:
self.assert_solve(
[self.fx.t],
[self.supc(self.fx.t, self.fx.b), self.subc(self.fx.t, self.fx.b)],
[self.fx.b],
)
def test_multiple_variables(self) -> None:
self.assert_solve(
[self.fx.t, self.fx.s],
[
self.supc(self.fx.t, self.fx.b),
self.supc(self.fx.s, self.fx.c),
self.subc(self.fx.t, self.fx.a),
],
[self.fx.b, self.fx.c],
)
def test_no_constraints_for_var(self) -> None:
self.assert_solve([self.fx.t], [], [self.fx.uninhabited])
self.assert_solve([self.fx.t, self.fx.s], [], [self.fx.uninhabited, self.fx.uninhabited])
self.assert_solve(
[self.fx.t, self.fx.s],
[self.supc(self.fx.s, self.fx.a)],
[self.fx.uninhabited, self.fx.a],
)
def test_simple_constraints_with_dynamic_type(self) -> None:
self.assert_solve([self.fx.t], [self.supc(self.fx.t, self.fx.anyt)], [self.fx.anyt])
self.assert_solve(
[self.fx.t],
[self.supc(self.fx.t, self.fx.anyt), self.supc(self.fx.t, self.fx.anyt)],
[self.fx.anyt],
)
self.assert_solve(
[self.fx.t],
[self.supc(self.fx.t, self.fx.anyt), self.supc(self.fx.t, self.fx.a)],
[self.fx.anyt],
)
self.assert_solve([self.fx.t], [self.subc(self.fx.t, self.fx.anyt)], [self.fx.anyt])
self.assert_solve(
[self.fx.t],
[self.subc(self.fx.t, self.fx.anyt), self.subc(self.fx.t, self.fx.anyt)],
[self.fx.anyt],
)
# self.assert_solve([self.fx.t],
# [self.subc(self.fx.t, self.fx.anyt),
# self.subc(self.fx.t, self.fx.a)],
# [self.fx.anyt])
# TODO: figure out what this should be after changes to meet(any, X)
def test_both_normal_and_any_types_in_results(self) -> None:
# If one of the bounds is any, we promote the other bound to
# any as well, since otherwise the type range does not make sense.
self.assert_solve(
[self.fx.t],
[self.supc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.anyt)],
[self.fx.anyt],
)
self.assert_solve(
[self.fx.t],
[self.supc(self.fx.t, self.fx.anyt), self.subc(self.fx.t, self.fx.a)],
[self.fx.anyt],
)
def test_poly_no_constraints(self) -> None:
self.assert_solve(
[self.fx.t, self.fx.u],
[],
[self.fx.uninhabited, self.fx.uninhabited],
allow_polymorphic=True,
)
def test_poly_trivial_free(self) -> None:
self.assert_solve(
[self.fx.t, self.fx.u],
[self.subc(self.fx.t, self.fx.a)],
[self.fx.a, self.fx.u],
[self.fx.u],
allow_polymorphic=True,
)
def test_poly_free_pair(self) -> None:
self.assert_solve(
[self.fx.t, self.fx.u],
[self.subc(self.fx.t, self.fx.u)],
[self.fx.t, self.fx.t],
[self.fx.t],
allow_polymorphic=True,
)
def test_poly_free_pair_with_bounds(self) -> None:
t_prime = self.fx.t.copy_modified(upper_bound=self.fx.b)
self.assert_solve(
[self.fx.t, self.fx.ub],
[self.subc(self.fx.t, self.fx.ub)],
[t_prime, t_prime],
[t_prime],
allow_polymorphic=True,
)
def test_poly_free_pair_with_bounds_uninhabited(self) -> None:
self.assert_solve(
[self.fx.ub, self.fx.uc],
[self.subc(self.fx.ub, self.fx.uc)],
[self.fx.uninhabited, self.fx.uninhabited],
[],
allow_polymorphic=True,
)
def test_poly_bounded_chain(self) -> None:
# B <: T <: U <: S <: A
self.assert_solve(
[self.fx.t, self.fx.u, self.fx.s],
[
self.supc(self.fx.t, self.fx.b),
self.subc(self.fx.t, self.fx.u),
self.subc(self.fx.u, self.fx.s),
self.subc(self.fx.s, self.fx.a),
],
[self.fx.b, self.fx.b, self.fx.b],
allow_polymorphic=True,
)
def test_poly_reverse_overlapping_chain(self) -> None:
# A :> T <: S :> B
self.assert_solve(
[self.fx.t, self.fx.s],
[
self.subc(self.fx.t, self.fx.s),
self.subc(self.fx.t, self.fx.a),
self.supc(self.fx.s, self.fx.b),
],
[self.fx.a, self.fx.a],
allow_polymorphic=True,
)
def test_poly_reverse_split_chain(self) -> None:
# B :> T <: S :> A
self.assert_solve(
[self.fx.t, self.fx.s],
[
self.subc(self.fx.t, self.fx.s),
self.subc(self.fx.t, self.fx.b),
self.supc(self.fx.s, self.fx.a),
],
[self.fx.b, self.fx.a],
allow_polymorphic=True,
)
def test_poly_unsolvable_chain(self) -> None:
# A <: T <: U <: S <: B
self.assert_solve(
[self.fx.t, self.fx.u, self.fx.s],
[
self.supc(self.fx.t, self.fx.a),
self.subc(self.fx.t, self.fx.u),
self.subc(self.fx.u, self.fx.s),
self.subc(self.fx.s, self.fx.b),
],
[None, None, None],
allow_polymorphic=True,
)
def test_simple_chain_closure(self) -> None:
self.assert_transitive_closure(
[self.fx.t.id, self.fx.s.id],
[
self.supc(self.fx.t, self.fx.b),
self.subc(self.fx.t, self.fx.s),
self.subc(self.fx.s, self.fx.a),
],
{(self.fx.t.id, self.fx.s.id)},
{self.fx.t.id: {self.fx.b}, self.fx.s.id: {self.fx.b}},
{self.fx.t.id: {self.fx.a}, self.fx.s.id: {self.fx.a}},
)
def test_reverse_chain_closure(self) -> None:
self.assert_transitive_closure(
[self.fx.t.id, self.fx.s.id],
[
self.subc(self.fx.t, self.fx.s),
self.subc(self.fx.t, self.fx.a),
self.supc(self.fx.s, self.fx.b),
],
{(self.fx.t.id, self.fx.s.id)},
{self.fx.t.id: set(), self.fx.s.id: {self.fx.b}},
{self.fx.t.id: {self.fx.a}, self.fx.s.id: set()},
)
def test_secondary_constraint_closure(self) -> None:
self.assert_transitive_closure(
[self.fx.t.id, self.fx.s.id],
[self.supc(self.fx.s, self.fx.gt), self.subc(self.fx.s, self.fx.ga)],
set(),
{self.fx.t.id: set(), self.fx.s.id: {self.fx.gt}},
{self.fx.t.id: {self.fx.a}, self.fx.s.id: {self.fx.ga}},
)
def assert_solve(
self,
vars: list[TypeVarLikeType],
constraints: list[Constraint],
results: list[None | Type],
free_vars: list[TypeVarLikeType] | None = None,
allow_polymorphic: bool = False,
) -> None:
if free_vars is None:
free_vars = []
actual, actual_free = solve_constraints(
vars, constraints, allow_polymorphic=allow_polymorphic
)
assert_equal(actual, results)
assert_equal(actual_free, free_vars)
def assert_transitive_closure(
self,
vars: list[TypeVarId],
constraints: list[Constraint],
graph: Graph,
lowers: Bounds,
uppers: Bounds,
) -> None:
actual_graph, actual_lowers, actual_uppers = transitive_closure(vars, constraints)
# Add trivial elements.
for v in vars:
graph.add((v, v))
assert_equal(actual_graph, graph)
assert_equal(dict(actual_lowers), lowers)
assert_equal(dict(actual_uppers), uppers)
def supc(self, type_var: TypeVarType, bound: Type) -> Constraint:
return Constraint(type_var, SUPERTYPE_OF, bound)
def subc(self, type_var: TypeVarType, bound: Type) -> Constraint:
return Constraint(type_var, SUBTYPE_OF, bound)