mypy does not recognize derived generic with different variance than the base class

171 Views Asked by At

When type checking the following code block with mypy an error is raised.

from typing import Generic, TypeVar


class Employee:
    pass


class Manager(Employee):
    pass


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


class Base(Generic[T_contra]):
    pass


class Derived(Base[T_co]):
    pass


def consume_base_manager(_: Base[Manager]) -> None: ...
def consume_base_employee(_: Base[Employee]) -> None: ...
def consume_derived_manager(_: Derived[Manager]) -> None: ...
def consume_derived_employee(_: Derived[Employee]) -> None: ...


base_manager: Base[Manager]
base_employee: Base[Employee]
derived_manager: Derived[Manager]
derived_employee: Derived[Employee]

consume_base_manager(base_employee)
consume_base_manager(base_manager)
consume_base_manager(derived_manager)
consume_base_manager(derived_employee)

consume_base_employee(base_employee)
consume_base_employee(derived_manager)
consume_base_employee(derived_employee)

consume_derived_manager(derived_manager)

consume_derived_employee(derived_manager)
consume_derived_employee(derived_employee)

Output:
error: Argument 1 to "consume_base_employee" has incompatible type "Derived[Manager]"; expected "Base[Employee]"

Out of the four functions and four argument types, I called every function with what I thought to be every valid argument but mypy gave me an error on consume_base_employee(derived_manager). This is weird to me because according to PEP 483 this should be valid.

In complex definitions of derived generics, variance only determined from type variables used. A complex example:

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

class Base(Generic[T_contra]):
    ...

class Derived(Base[T_co]):
    ...

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].

What is even weirder to me is that the line that raises an error is the one situation that is explicitly stated in the PEP that should work.

Did I misunderstand the intended meaning of the PEP, is there a mistake in the PEP or is this a bug in mypy?

0

There are 0 best solutions below