Tipragot
628be439b8
Cela permet de ne pas avoir de problèmes de compatibilité car python est dans le git.
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)
|