ruff
747186f5 - [ty] Resolve dependencies within correlated inference alternatives (#28252)

Commit
1 day ago
[ty] Resolve dependencies within correlated inference alternatives (#28252) Retained inference-solution alternatives currently expose selected bindings without saying whether those bindings still depend on other inferable type variables. The intersection-inference work for [ty#3557](https://github.com/astral-sh/ty/issues/3557) needs to inspect each alternative before applying defaults. Currently, its conservative check has to fall back to the union-merged result even when a dependency could be resolved from another binding in the same alternative. For example, this call produces dependent bindings (covered by the new call-inference unit test): ```python from typing import Callable, overload class A: ... class B: ... def infer_result[T, R](callback: Callable[[T], R], consumer: Callable[[T], None]) -> R: raise NotImplementedError def identity[T](value: T) -> T: return value @overload def consume(value: A) -> None: ... @overload def consume(value: B) -> None: ... def consume(value: A | B) -> None: ... result = infer_result(identity, consume) ``` The consumer overloads select `T = A` or `T = B`, while the identity callback relates `R` to `T`: | Alternative | Selected bindings | After resolving dependencies | | --- | --- | --- | | First | `T = A, R = T` | `T = A, R = A` | | Second | `T = B, R = T` | `T = B, R = B` | The unresolved `R = T` prevents the conservative intersection consumer from using these alternatives. Resolving them supplies the separate return types `A` and `B`, which can support the more precise result `A & B` once both specializations validate against the whole call. The existing merged-specialization path already substitutes dependencies, but it does so only after merging alternatives and filling defaults, producing `A | B`; it cannot provide these separate, evidence-only results. Resolve dependencies within each alternative and distinguish resolved bindings from unresolved ones. `T = U, U = A` resolves to `T = A`, while `T = U` with no binding for `U` remains unresolved even if `U` has a declared default. Cycles and captured references that specialization cannot replace also retain their original bindings, alongside any independent resolved bindings. References to non-inferable variables preserve their identities. This changes the correlated-solution API. Existing call inference continues to use the merged specialization and returns `A | B` for this example. Producing `A & B` also requires the intersection consumer to use these resolved bindings and accept the generic callback during subtype revalidation; that separate validation limitation is not addressed here. ## Test plan Add a call-inference unit test for the generic callback and overloaded consumer above, asserting independently resolved alternatives and the unchanged merged specialization. Add unit coverage for single alternatives whose resolved bindings differ from the merged projection, missing dependencies with declared defaults, dependency cycles with independent bindings, and non-inferable variables with matching names. Cover captured dependencies in ordinary and recursive aliases, preservation of closed recursive aliases, and dependencies exposed by a partial's wrapped callable. Update existing correlated-inference assertions for the resolved/unresolved representation.
Author
Parents
Loading