Why do type checkers not warn when incorrect variance is used?

Example code (works with both mypy and pyright for Python 3.12):

from dataclasses import dataclass
from typing import TypeVar, Generic

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

@dataclass
class OldDataclass(Generic[T_co]):
    x: T_co  # error expected, not reported
    y: list[T_co]  # error expected, not reported

class OldBox(Generic[T_co]):
    def __init__(self, value: T_co) -> None:
        self.value: T_co = value  # error expected, not reported

class OldShelf(Generic[T_co]):
    def __init__(self, values: list[T_co]) -> None:
        self._values: list[T_co] = values

    def get_values(self) -> list[T_co]:  # error expected, not reported
        return self._values

    def set_values(self, new_values: list[T_co]) -> None:  # error expected, not reported
        self._values = new_values


@dataclass
class NewDataclass[T]:  # T inferred as invariant
    x: T
    y: list[T]

class NewBox[T]:  # T inferred as invariant
    def __init__(self, value: T) -> None:
        self.value: T = value

class NewShelf[T]:  # T inferred as invariant
    def __init__(self, values: list[T]) -> None:
        self._values: list[T] = values

    def get_values(self) -> list[T]:
        return self._values

    def set_values(self, new_values: list[T]) -> None:
        self._values = new_values

It seems like type checkers don’t rigorously check that generic types are parameterized with typevars of correct variance when old-style type variables are used. Is that intentional, given that type checkers can now infer the correct inference?

It seems like some checking is performed to catch the most simple errors:

class OldBox(Generic[T_co]):
    def set_value(self, value: T_co) -> None:
        #                      ^^^^
        # error: Cannot use a covariant type variable as a parameter 
        ...
2 Likes

This is for historical reasons. Mypy rigorously enforced invariance only for Protocol classes. For non-protocol classes, mypy implemented basic variance checks for parameter annotations to help developers avoid some common errors. Other type checkers, including pyre and pyright, followed mypy’s lead and implemented these same checks.

This is only a problem for old-style type parameters. If you move to the new type parameter syntax (PEP 695, introduced in Python 3.12), this isn’t an issue because the variance is inferred. This was one of the motivations for PEP 695.

I see.

Would typing_extensions.TypeVar(..., infer_variance=True) be the recommended way to use type variables before 3.12 then? (for libraries and applications that support 3.10/3.11)

It looks like mypy doesn’t yet support the infer_variance feature.