Why does Mypy report a subset of JSON as invalid?

Question:

I have a base class that returns a JSON type. I then have subclasses that return more specific types that should I think be valid JSON types, but Mypy reports an error:

error: Return type "List[Dict[str, JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner"  [override]

Have I misunderstood something or am I exploring the limits of the type-checking implementation?

Full example:

from typing import TypeAlias


JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None


class JsonReturner:

    def return_json(self) -> JSON:
        raise NotImplementedError("abstract base class")


class ListJsonReturner(JsonReturner):

    def return_json(self) -> list[JSON]:
        return []


class DictJsonReturner(JsonReturner):

    def return_json(self) -> dict[str, JSON]:
        return {}


class ListDictJsonReturner(JsonReturner):

    def return_json(self) -> list[dict[str, JSON]]:
        return []


class DictListJsonReturner(JsonReturner):

    def return_json(self) -> dict[str, list[JSON]]:
        return {}
$ mypy jsontypes.py
jsontypes.py:27: error: Return type "List[Dict[str, JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner"  [override]
jsontypes.py:33: error: Return type "Dict[str, List[JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner"  [override]
Asked By: wrgrs

||

Answers:

This all has to do with variance. Read PEP-483 for more information.

Generic types like list are invariant: a type list[T] is not a subtype nor a supertype of list[U] unless T and U are the same type.

Similarly, dict is invariant because a two concrete dict types are the same only if their key and value types are the same.

Function types are covariant in their return type. For a fixed argument type, Callable[..., R1] is a subtype of Callable[…, R2]whenR1is a subtype ofR2`.

Each component of a union type is a subtype of the union.

When you override a function, the type of the override must be a subtype of the parent function. As long as you don’t change the parameter types, this means the new return type must be a subtype of the original’s return type.

In your first two overrides, the return types are subtypes of JSON.

In the last two, they are not. list[dict[str, JSON]] is not a subtype of JSON because lists are invariant. dict[str, list[JSON]] is not a subtype of JSON because dicts are invariant.

Answered By: chepner
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.