Skip to content

TypeVar to represent a Callable's arguments #3028

Closed
@JelleZijlstra

Description

@JelleZijlstra

This is an idea for a new type system feature that can help more accurately type some decorators and wrapper functions, like @async() from https://github.com/quora/asynq.

I'd like to see something like this work:

from mypy_extensions import ArgumentsVar
from typing import TypeVar, Awaitable

_ArgsT = ArgumentsVar('_ArgsT')
_RetT = TypeVar('_RetT')

def make_awaitable(fn: Callable[_ArgsT, _RetT]) -> Callable[_ArgsT, Awaitable[_RetT]]:
    async def wrapper(**args: _ArgsT, **kwargs: _ArgsT) -> Awaitable[_RetT]:
        result = fn(*args, **kwargs)
        return result
    return wrapper

@make_awaitable
def f(x: int, y: str) -> int:
    return 3

reveal_type(f(1, 'x'))  # Awaitable[int]
f(1, 1)  # error, second argument must be str
f(1, z=3)  # error, z is not a valid kwarg

Having a function's args and kwargs annotated with an ArgumentsVar would mean that it takes the arguments that the ArgumentsVar resolves to. As an extension, we could also make something like def f(x: int, *args: _ArgsT, **kwargs: _ArgsT) -> _T: ... work to indicate that a function takes an argument called x plus the arguments specified in _ArgsT.

This could also improve some types in the standard library. For example, the annotations for functools.singledispatch currently don't check function arguments. With ArgumentsVar, it could be typed as follows:

_T = TypeVar('_T')
_ArgsT = ArgumentsVar('_ArgsT')

class _SingleDispatchCallable(Generic[_ArgsT, _T]):
    registry = ...  # type: Mapping[Any, Callable[_ArgsT, _T]]
    def dispatch(self, cls: Any) -> Callable[_ArgsT, _T]: ...
    @overload
    def register(self, cls: Any) -> Callable[[Callable[_ArgsT, _T]], Callable[_ArgsT, _T]]: ...
    @overload
    def register(self, cls: Any, func: Callable[_ArgsT, _T]) -> Callable[_ArgsT, _T]: ...
    def _clear_cache(self) -> None: ...
    def __call__(self, *args: _ArgsT, **kwargs: _ArgsT) -> _T: ...

def singledispatch(func: Callable[_ArgsT, _T]) -> _SingleDispatchCallable[_ArgsT, _T]: ...

This kind of thing has come up before, but I'm not sure a concrete solution has been proposed. I'll try to implement this to see how well it works.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions