"""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)