python / mypy

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

`Concatenate`-ing a `TypeVar` in return type of a decorator doesn't work #16137

Open supersergiy opened 1 year ago

supersergiy commented 1 year ago

Bug Report Concatenate-ing a TypeVar in return type of a decorator results in the first constraint being selected.

To Reproduce

from __future__ import annotations

from typing import Any, Callable, TypeVar, Union

from typing_extensions import Concatenate, ParamSpec

T = TypeVar("T", int, dict)
P = ParamSpec("P")

def accepts_mapping(
    func: Callable[P, Any]
) -> Callable[Concatenate[T, P], T]:
    def wrapper(data: T, *args: P.args, **kwargs: P.kwargs) -> T:
        if isinstance(data, dict):
            return {}
        else:
            return 1

    return wrapper

@accepts_mapping
def fn():
    return

reveal_type(fn)
# main.py:26: note: Revealed type is "def (builtins.dict[Any, builtins.int]) -> builtins.dict[Any, builtins.int]"

mapping_res = fn({"a": 1}) 
# main.py:28: error: Argument 1 to "fn" has incompatible type "dict[str, int]"; expected "int"  [arg-type]

[playground link]

Expected Behavior

Decorated function should accept either dict or int. pyright works as expected -- [pyright playground link]

Same example works as expected without Concatenate:

from __future__ import annotations

from typing import Any, Callable, TypeVar, Union

from typing_extensions import Concatenate, ParamSpec

T = TypeVar("T", int, dict)
P = ParamSpec("P")

def accepts_mapping(
    func: Callable[[], Any]
) -> Callable[[T], T]:
    def wrapper(data: T, *args: P.args, **kwargs: P.kwargs) -> T:
        if isinstance(data, dict):
            return {}
        else:
            return 1

    return wrapper

@accepts_mapping
def fn():
    return

reveal_type(fn)
# main.py:26: note: Revealed type is "def [T in (builtins.int, builtins.dict[Any, Any])] (T`-1) -> T`-1"

mapping_res = fn({"a": 1}) 

Your Environment

Mypy playground, tested with master and 1.5.1

sterliakov commented 3 months ago

This seems to work as expected on master now: https://mypy-play.net/?mypy=master&python=3.11&gist=c3b411fa0131d9d88de54eb24026e964&flags=strict