286 lines
9.8 KiB
Python
286 lines
9.8 KiB
Python
|
"""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)
|