Solve set of constraints that may depend on each other, like T <: List[S]. The whole algorithm consists of five steps: * Propagate via linear constraints and use secondary constraints to get transitive closure * Find dependencies between type variables, group them in SCCs, and sort
(
vars: list[TypeVarId],
constraints: list[Constraint],
original_vars: list[TypeVarId],
originals: dict[TypeVarId, TypeVarLikeType],
)
| 128 | |
| 129 | |
| 130 | def solve_with_dependent( |
| 131 | vars: list[TypeVarId], |
| 132 | constraints: list[Constraint], |
| 133 | original_vars: list[TypeVarId], |
| 134 | originals: dict[TypeVarId, TypeVarLikeType], |
| 135 | ) -> tuple[Solutions, list[TypeVarLikeType]]: |
| 136 | """Solve set of constraints that may depend on each other, like T <: List[S]. |
| 137 | |
| 138 | The whole algorithm consists of five steps: |
| 139 | * Propagate via linear constraints and use secondary constraints to get transitive closure |
| 140 | * Find dependencies between type variables, group them in SCCs, and sort topologically |
| 141 | * Check that all SCC are intrinsically linear, we can't solve (express) T <: List[T] |
| 142 | * Variables in leaf SCCs that don't have constant bounds are free (choose one per SCC) |
| 143 | * Solve constraints iteratively starting from leaves, updating bounds after each step. |
| 144 | """ |
| 145 | graph, lowers, uppers = transitive_closure(vars, constraints) |
| 146 | |
| 147 | dmap = compute_dependencies(vars, graph, lowers, uppers) |
| 148 | sccs = list(strongly_connected_components(set(vars), dmap)) |
| 149 | if not all(check_linear(scc, lowers, uppers) for scc in sccs): |
| 150 | return {}, [] |
| 151 | raw_batches = list(topsort(prepare_sccs(sccs, dmap))) |
| 152 | |
| 153 | free_vars = [] |
| 154 | free_solutions = {} |
| 155 | for scc in raw_batches[0]: |
| 156 | # If there are no bounds on this SCC, then the only meaningful solution we can |
| 157 | # express, is that each variable is equal to a new free variable. For example, |
| 158 | # if we have T <: S, S <: U, we deduce: T = S = U = <free>. |
| 159 | if all(not lowers[tv] and not uppers[tv] for tv in scc): |
| 160 | best_free = choose_free([originals[tv] for tv in scc], original_vars) |
| 161 | if best_free: |
| 162 | # TODO: failing to choose may cause leaking type variables, |
| 163 | # we need to fail gracefully instead. |
| 164 | free_vars.append(best_free.id) |
| 165 | free_solutions[best_free.id] = best_free |
| 166 | |
| 167 | # Update lowers/uppers with free vars, so these can now be used |
| 168 | # as valid solutions. |
| 169 | for l, u in graph: |
| 170 | if l in free_vars: |
| 171 | lowers[u].add(free_solutions[l]) |
| 172 | if u in free_vars: |
| 173 | uppers[l].add(free_solutions[u]) |
| 174 | |
| 175 | # Flatten the SCCs that are independent, we can solve them together, |
| 176 | # since we don't need to update any targets in between. |
| 177 | batches = [] |
| 178 | for batch in raw_batches: |
| 179 | next_bc = [] |
| 180 | for scc in batch: |
| 181 | next_bc.extend(list(scc)) |
| 182 | batches.append(next_bc) |
| 183 | |
| 184 | solutions: dict[TypeVarId, Type | None] = {} |
| 185 | for flat_batch in batches: |
| 186 | res = solve_iteratively(flat_batch, graph, lowers, uppers) |
| 187 | solutions.update(res) |
no test coverage detected
searching dependent graphs…