mypy icon indicating copy to clipboard operation
mypy copied to clipboard

str counts as a Callable[..., str], but not as a Callable[..., TypeVar]

Open rish-shadra opened this issue 1 year ago • 1 comments

Bug Report

Hey folks!

I ran into what I think is a bug while trying to write a config retrieval library for a Flask server. I'd like to pass a return_type argument into my get_config function. When the argument is provided, I'd like the function to return a value as that type.

To Reproduce

Here's a pared-down version of my function that still causes MyPy to error out. Pyright seems to think this code is valid.

from typing import Callable, TypeVar
ValueType = TypeVar("ValueType")

def get_config(return_type: Callable[..., ValueType] = str) -> ValueType:
    return return_type(42)

# mypy get_config_typed.py
# error: Incompatible default for argument "return_type" (default has type "type[str]", argument has type "Callable[..., ValueType]")  [assignment]

The same code works fine when ValueType is replaced with str:

from typing import Callable

def get_config(return_type: Callable[..., str] = str) -> str:
    return return_type(42)

# mypy get_config_typed.py
# Success: no issues found in 1 source file

Actual Behavior

error: Incompatible default for argument "return_type" (default has type "type[str]", argument has type "Callable[..., ValueType]") [assignment]

Your Environment

% mypy --version
mypy 1.8.0 (compiled: yes)
% python --version
Python 3.9.17
% uname -a
Darwin <...> 23.2.0 Darwin Kernel Version 23.2.0: Wed Nov 15 21:54:51 PST 2023; root:xnu-10002.61.3~2/RELEASE_ARM64_T6030 arm64
  • Mypy version used: 1.8.0
  • Mypy command-line flags: N/A
  • Mypy configuration options from mypy.ini (and other config files): N/A
  • Python version used: 3.9.17

rish-shadra avatar Feb 03 '24 22:02 rish-shadra

One workaround is to use overloads.

from typing import Any, Callable, TypeVar, overload

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

@overload
def get_config() -> str: ...
@overload
def get_config(return_type: Callable[..., T_co]) -> T_co: ...
def get_config(return_type: Callable[..., Any] = str) -> Any:
    return return_type(42)

def foo(x: Any) -> dict[str, Any]: 
    return {}

reveal_type(get_config())     # builtins.str
reveal_type(get_config(int))  # builtins.int
reveal_type(get_config(foo))  # builtins.dict[builtins.str, Any]

picnixz avatar Feb 04 '24 10:02 picnixz