Parameter specifications should have variance

Code sample in basedpyright playground

from typing import Callable, Protocol, runtime_checkable


@runtime_checkable
class CanCall[**InP, OutT](Protocol):
    def __call__(*args: InP.args, **kwargs: InP.kwargs) -> OutT: ...

def f1(takes_object: CanCall[[object], None]):
    takes_int: CanCall[[int], None] = takes_object  # Type parameter "InP@CanCall" is invariant, but "(object)" is not the same as "(int)"


def f2(takes_object: Callable[[object], None]):
    takes_int: Callable[[int], None] = takes_object

as we know, Callable is not a special type, it is simply a protocol with a paramspec. but there is some magic special behaviour bestowed upon it to make it behave contravariantly

this missing feature of paramspecs is blocking the correction of the definitions of Callable:

additionally, this restriction is quite limiting for other usages, and is a stark contrast in behaviour to type variables

all of these issues are also present with type variable tuples:
Code sample in basedpyright playground

class A[OutT]:
    def out(self) -> OutT: ...

a_int = A[int]()
a_obj: A[object] = a_int  # okay

class B[*OutTs]:
    def out(self) -> tuple[*OutTs]: ...

b_int = B[int]()
b_obj: B[object] = b_int  # Type parameter "OutTs@B" is invariant, but "*tuple[int]" is not the same as "*tuple[object]"

specification update:

4 Likes

While we’re here should we also allow variance inference for TypeVarTuple? If we act fast we can still get runtime support for explicitly declaring variance into Python 3.15. I think @jorenham had a use case for non-invariant TypeVarTuple?

6 Likes

sounds good, i’ll start work on that

1 Like

A general class of use-cases are that of variadic tuple wrappers, where you’d want the TypeVarTuple to be covariant to match the tuple variance. There are several of there in scipy-stubs:

In scipy-stubs there are also examples where TypeVarTuple is used exclusively for input types, e.g. for the pos-only params of a wrapped Callable signature:

So in this case it would be most appropriate for the type params “within” the TypeVarTuple to be contravariant.

2 Likes

type variable tuple variance:

implementation in basedpyright: variance for parameter specification and type variable tuples by KotlinIsland · Pull Request #1771 · DetachHead/basedpyright · GitHub