PEP483 Complex example with mixed variance

Was reading PEP483 and there was this complex example at the end about creating a class when Base class instances behave contravariantly but it’s subclass instances (Derived) behavee covariantly. That a real monster :smile:

Quote from PEP:

A type checker finds from the second declaration that Derived[Manager] is a subtype of Derived[Employee], and Derived[t1] is a subtype of Base[t1]. If we denote the is-subtype-of relationship with <, then the full diagram of subtyping for this case will be:

Base[Manager]    >  Base[Employee]
    v                   v
Derived[Manager] <  Derived[Employee]

so that a type checker will also find that, e.g., Derived[Manager] is a subtype of Base[Employee].

Is this a real thing and something like this could occur in real life cases?

Though typing for this example does work as described above (at least in Pyright), Pyright and Mypy still prohibit overwriting contravariant generic type parameter in subclass with covariant parameter (and vice versa).
Is this something that’s prohibited / allowed in the spec or it’s unspecified?

from typing import TypeVar, Generic

class Employee: ...
class Manager(Employee): ...

T_co = TypeVar("T_co", Employee, Manager, covariant=True)
T_contra = TypeVar("T_contra", Employee, Manager, contravariant=True)

class Base(Generic[T_contra]): ...
# Type "T_co@Derived" cannot be assigned to type variable "T_contra@Base"
#   Variance of type argument "T_co@Derived" is incompatible with base class "Base"
class Derived(Base[T_co]): ...

It’s not something that could occur when variance is inferred. Contravariance is only inferred when the type parameter is used in only input positions, and covariance if the typar is only used in output positions. This example requires the former in Base, and the latter to be the case in Derived. But because Derive is a proper subtype of Base, this cannot happen.

So the typar variance of some child class must either be invariant, or the same as that of the base class. Anything else would be type-unsafe in a real life case (i.e. one where the typar is actually doing something).

3 Likes