python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.44k stars 2.82k forks source link

Generic TypedDict and inferring return value #17753

Open patrick91 opened 1 month ago

patrick91 commented 1 month ago

I was working on some code that needs to be passed a generic TypedDict, like this:

from typing import TypeVar, TypedDict, Generic

T = TypeVar("T")

ReturnValue = TypeVar("ReturnValue")

class Option(TypedDict, Generic[ReturnValue]):
    name: str
    value: ReturnValue

class Menu:
    def ask(self, text: str, options: list[Option[T]]) -> T:
        return options[0]["value"]

And the usage would be this:

value = Menu().ask("text", [{"value": 123, "name": "abc"}])

reveal_type(value)

In this case the value type should int, but unfortunately I get this error:

main.py:18: error: Need type annotation for "value"  [var-annotated]
main.py:18: error: Argument 2 to "ask" of "Menu" has incompatible type "list[dict[str, int]]"; expected "list[Option[Never]]"  [arg-type]

Playground: https://mypy-play.net/?mypy=latest&python=3.12&flags=verbose%2Cstrict&gist=9a8643b51a69f6d60ab5b0b482e10865

This seems to be working well in pyright 😊

Also I didn't find yet a workaround, changing the class to this:

class Menu:
    def ask(self, text: str, options: list[dict[str, T]]) -> T:
        return options[0]["value"]

Almost works, but for example in the case above it would infer the type as object 😊

patrick91 commented 1 month ago

Ah! Found a workaround, full code:

from typing import TypeVar, TypedDict, Generic

T = TypeVar("T")

ReturnValue = TypeVar("ReturnValue")

class Option(TypedDict, Generic[ReturnValue]):
    name: str
    value: ReturnValue

class Menu:
    def ask(self, text: str, options: list[Option[T]]) -> T:
        return options[0]["value"]

value = Menu().ask("text", [Option({"value": 123, "name": "abc"})]) # 👈 this is the workaround, passing the typed dict instead of a "generic" dict

reveal_type(value)