## Summary
Part of astral-sh/ty#3557 and the second step in rebooting astral-sh#26712 as
smaller focused changes.
On `main`, generic inference does not consistently propagate the
variance of an enclosing generic class into structural-protocol and
callable comparisons. Those comparisons instead construct the forward
`actual <= formal` relation even when the surrounding position is
contravariant or invariant. This produces incorrect bounds and inferred
types:
```py
from typing import Callable
class Middle: ...
class Derived(Middle): ...
class Sink[T]:
def put(self, value: T) -> None: ...
def infer[T](sink: Sink[Callable[[], T]], value: T) -> T:
return value
reveal_type(infer(Sink[Callable[[], Middle]](), Derived()))
# main: Middle
# this PR: Derived
```
This PR centralizes relation construction so inference consistently
follows the surrounding polarity: covariance requires `actual <=
formal`, contravariance requires `formal <= actual`, invariance requires
both, and bivariance adds no constraints. The fix applies to nested
generic protocols, callable signatures, callback protocols,
TypedDict/protocol relations, and materialized protocols while
preserving existing nominal inference, callable-union alternatives,
TypedDict-union optimizations, and cycle-safe handling of recursive
protocols.
## Limitation
The new constraint solver does not yet support `ParamSpec` or
`TypeVarTuple`. Whenever an inference context includes either variadic,
all affected relations must continue through the old, forward-only
inference path, including comparisons involving ordinary type variables
alongside an unrelated variadic. This deliberately avoids regressions in
parameter inference, structural protocols, descriptor signatures, and
downstream argument diagnostics, but it also means the polarity fixes in
this PR do not apply whenever variadics are involved. Focused TODO tests
document cases that still incorrectly infer `Middle` instead of
`Derived`; fixing those cases requires adding variadic support to the
new solver.
## Test plan
Added mdtests cover nested nominal classes, generic protocols, callable
signatures, and callback protocols under covariance, contravariance, and
invariance; double contravariance, callable-union alternatives, and
invariant rejection; finite, recursive, and recursive-only materialized
protocols; invariant, contravariant, and covariant `ParamSpec` inference
through `Concatenate`; bounded and constrained prefixes, descriptor
receivers, structural protocol unions, nominal callable objects,
higher-order callbacks, and mixed ordinary/variadic inference; analogous
`TypeVarTuple` callable, protocol, structural-union, and
nominal-implementation cases; and explicit TODO coverage for unrelated
variadics suppressing otherwise-correct polarity.
Additional mdtests cover repeated comparisons of the same protocol or
callable under opposite polarities, plus overloaded callable acceptance
and rejection across covariant, contravariant, and invariant wrappers.
Summary
Part of astral-sh/ty#3557 and the second step in rebooting #26712 as smaller focused changes.
On
main, generic inference does not consistently propagate the variance of an enclosing generic class into structural-protocol and callable comparisons. Those comparisons instead construct the forwardactual <= formalrelation even when the surrounding position is contravariant or invariant. This produces incorrect bounds and inferred types:This PR centralizes relation construction so inference consistently follows the surrounding polarity: covariance requires
actual <= formal, contravariance requiresformal <= actual, invariance requires both, and bivariance adds no constraints. The fix applies to nested generic protocols, callable signatures, callback protocols, TypedDict/protocol relations, and materialized protocols while preserving existing nominal inference, callable-union alternatives, TypedDict-union optimizations, and cycle-safe handling of recursive protocols.Limitation
The new constraint solver does not yet support
ParamSpecorTypeVarTuple. Whenever an inference context includes either variadic, all affected relations must continue through the old, forward-only inference path, including comparisons involving ordinary type variables alongside an unrelated variadic. This deliberately avoids regressions in parameter inference, structural protocols, descriptor signatures, and downstream argument diagnostics, but it also means the polarity fixes in this PR do not apply whenever variadics are involved. Focused TODO tests document cases that still incorrectly inferMiddleinstead ofDerived; fixing those cases requires adding variadic support to the new solver.Test plan
Added mdtests cover nested nominal classes, generic protocols, callable signatures, and callback protocols under covariance, contravariance, and invariance; double contravariance, callable-union alternatives, and invariant rejection; finite, recursive, and recursive-only materialized protocols; invariant, contravariant, and covariant
ParamSpecinference throughConcatenate; bounded and constrained prefixes, descriptor receivers, structural protocol unions, nominal callable objects, higher-order callbacks, and mixed ordinary/variadic inference; analogousTypeVarTuplecallable, protocol, structural-union, and nominal-implementation cases; and explicit TODO coverage for unrelated variadics suppressing otherwise-correct polarity.Additional mdtests cover repeated comparisons of the same protocol or callable under opposite polarities, plus overloaded callable acceptance and rejection across covariant, contravariant, and invariant wrappers.