Skip to content

Wrong type inference when using a decorator returning a Protocol that uses a concatenated ParamSpec #15753

Closed as not planned
@vxgmichel

Description

@vxgmichel

Bug Report

I found a really weird bug while writing a decorator that returns a Protocol that itself uses a concatenated ParamSpec. I managed to get to a small reproducible example:

P = ParamSpec("P")
T = TypeVar("T")
X = TypeVar("X")

class Wrapped(Protocol[P, T]):
    __call__: Callable[P, T]

def decorator(
    func: Callable[Concatenate[X, P], T],
) -> Wrapped[Concatenate[X, P], T]:
    # Do something with X
    return func

@decorator
def first(
    source: Iterable[T],
) -> T:
    return next(iter(source))

item = first(range(2))
reveal_type(item)
print(item.real)

Playground link

Expected Behavior

It works as expected using either one of those two decorators instead:

# Decorator without `Concatenate`
def decorator(
    func: Callable[P, T],
) -> Wrapped[P, T]:
    # Do something with X
    return func

# Decorator without `Protocol`
def decorator(
    func: Callable[Concatenate[X, P], T],
) -> Callable[Concatenate[X, P], T]:
    # Do something with X
    return func

In those two cases, mypy is happy and reports an int as expected:

note: Revealed type is "builtins.int"

Actual Behavior

Instead, mypy is confused and expects i to be a range:

note: Revealed type is "builtins.range"
error: "range" has no attribute "upper"  [attr-defined]

Your Environment

  • Mypy version used: v1.4.1 and mypy 1.6.0+dev.d2022a0007c0eb176ccaf37a9aa54c958be7fb10 (compiled: no)
  • Python version used: 3.10.4 and 3.11

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongtopic-paramspecPEP 612, ParamSpec, Concatenate

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions