[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.