ruff
bd05d2dd - [ty] Avoid exponential invariant constraint paths (#26538)

Commit
15 hours ago
[ty] Avoid exponential invariant constraint paths (#26538) ## Summary When a type variable is compared with a concrete type inside an invariant generic, lazy constraint construction previously represented equivalence as two independent relation constraints. Combining those constraints across an outer union multiplied their alternative paths, causing the issue's 21-arm generic-call example to exhibit exponential-looking runtime. This PR constructs a single bounded type-variable constraint directly when exactly one side is a type variable and the concrete side is not itself a union. Assignability uses the concrete type as both bounds, while subtyping uses its gradual top and bottom materializations. Materialized `Divergent` cycle markers are normalized before constructing the range so the shortcut preserves the existing cycle-recovery behavior. ### Fixed by this PR The optimization applies when the outer union contains invariant specializations whose invariant arguments are concrete, non-union types. For example: ```python class A: ... class B: ... Results = dict[int, A] | dict[int, B] Rows = list[tuple[int, A]] | list[tuple[int, B]] def map_rows[T](rows: list[tuple[int, T]]) -> dict[int, T]: ... def perform(rows: Rows) -> Results: return map_rows(rows) ``` The issue's 21-arm version of this pattern drops from 14.29 seconds on `main` to 0.04 seconds with this change. ### Not fixed by this PR The shortcut deliberately does not apply when the invariant argument is itself a union. For example: ```python class A0: ... class B0: ... class A1: ... class B1: ... Results = dict[int, A0 | B0] | dict[int, A1 | B1] Rows = list[tuple[int, A0 | B0]] | list[tuple[int, A1 | B1]] def map_rows[T](rows: list[tuple[int, T]]) -> dict[int, T]: ... def perform(rows: Rows) -> Results: return map_rows(rows) ``` A large version of this nested-union pattern remains pathological. These bounds stay on the structural relation path because storing an entire union as one exact bound regressed pandas-stubs, pydantic, scikit-learn, colour, and manticore. Fixing that residual case requires a separate optimization that preserves union distribution and negative-path semantics. With union distribution preserved, the affected ecosystem projects return to base-level runtime while the original issue reproduction remains fast. The Criterion suite benchmarks both the optimized pattern and the union-bound regression guard. Closes https://github.com/astral-sh/ty/issues/3896.
Author
Parents
Loading