Rejecting the use of a covariant/contravariant TypeVar in a function or type alias

The typing spec (which was derived from the original text of PEP 484) says:

Note: Covariance or contravariance is not a property of a type variable, but a property of a generic class defined using this variable. Variance is only applicable to generic types; generic functions do not have this property. The latter should be defined using only type variables without covariant or contravariant keyword arguments

And it explicitly says the following is prohibited:

B_co = TypeVar('B_co', covariant=True)

def bad_func(x: B_co) -> B_co:  # Flagged as error by a type checker

Presumably, it would also prohibit the use of such type variables in generic type aliases:

MyAlias: TypeAlias = list[B_co]

None of the four major type checkers (mypy, pyright, pyre, pytype) currently reject this use. All four of them simply ignore the variance if a TypeVar is used in a generic function or type alias. I think that’s reasonable, as traditional type variables (prior to the introduction of new syntax in Python 3.12) were often reused multiple time in a file.

I would like to propose that we amend the typing spec to bring it in alignment with current implementations by removing this prohibition. Instead, the spec would say that the variance should be ignored by a type checker if the type variable is used outside of a class (in a function or type alias).

Would anyone like to argue in favor of retaining this prohibition in the spec?

1 Like

I don’t really see why it should be prohibited in a TypeAlias, since it is an alias for a type, which could just be a new name for a generic class. e.g. Seq: TypeAlias = Sequence[T_co] or a union of generics that all share the same variance of this type variable. Maybe not something that should be encouraged, but it still seems different to me from a generic function, where variance actually makes no sense.

Unless the argument is that the type alias should always infer the variance based on the generics its used in and thus you should never have to worry about specifying variance manually. Which is a different justification from generic functions, but seems fine to me, since overruling the original variance through a type alias seems like a bad idea.

What would happen if you use an invariant TypeVar in a covariant alias?

from typing import Sequence, TypeAlias, TypeVar

T = TypeVar("T")  # invariant

Alias: TypeAlias = Sequence[T]

Is Alias still covariant?

Logically it would make sense for Alias to now be invariant, but I don’t see any use for such a feature. The new PEP 695 syntax would not provide any way to create an “invariant Sequence” alias.

The variance of a TypeVar used within a type alias definition is irrelevant, just as it is when used in a function definition. None of the major type checkers pay any attention to it, nor should they.

If you specialize a generic type alias with a concrete type (e.g. Alias[int] in your example above), then variance doesn’t matter. If you specialize a generic type alias with another type variable, the variance of that TypeVar matters.

To extend your example:

from typing import Sequence, TypeAlias, TypeVar

T = TypeVar("T")
T_co = TypeVar("T_co", covariant=True)

SequenceAlias: TypeAlias = Sequence[T]  # OK
ListAlias: TypeAlias = list[T_co]  # OK

class CustomSequence1(SequenceAlias[T_co]):  # OK

class CustomSequence2(SequenceAlias[T]):  # OK, but invariance is unnecessary

class CustomSequence3[S](SequenceAlias[S]):  # OK, S inferred as covariant

class CustomList1(ListAlias[T_co]):  # Error reported here

class CustomList[S](ListAlias[S]):  # OK, S inferred as invariant

class CustomClass1(Generic[T_co]):
    def method1(self) -> SequenceAlias[T_co]:  # OK

    def method2(self) -> ListAlias[T_co]:  # Oops, T_co should be invariant

class CustomClass2[S]:  # S inferred as invariant
    def method1(self) -> SequenceAlias[S]:  # OK

    def method2(self) -> ListAlias[S]:  # OK

Thanks to everyone who provided feedback on this issue.

The TC has officially approved this proposed change to the spec. I will update the spec and conformance tests accordingly.