Skip to content

Decorator factory with generic is not properly bound #15594

Closed
@bersbersbers

Description

@bersbersbers

Bug Report

I hope I got the title approximately right. In the example below, mypy does not seem to properly bind the return type of the decorated function (int) to T (or the other way around).

To Reproduce

Example 1: decorator factory

from typing import Callable, ParamSpec, TypeVar, reveal_type

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

def decorator_factory(
    decorator_: Callable[[Callable[P, T]], Callable[P, T]]
) -> Callable[[Callable[P, T]], Callable[P, T]]:
    return decorator_

@decorator_factory
def decorator(fun_: Callable[P, T]) -> Callable[P, T]:
    return fun_

@decorator
def fun() -> int:
    return 0

var = fun()
print(type(var))  # int

reveal_type(decorator_factory)
reveal_type(decorator)
reveal_type(fun)
reveal_type(var)  # expected 'int', got 'T`-2' (Pylance reports 'int')

Example 2: decorator.decorator-style decorator without arguments

from typing import Callable, TypeVar, reveal_type

T = TypeVar("T")

# Trivial implementation of decorator.decorator[x]
# (without arguments) to investigate proper typing
def decorator(
    caller_: Callable[[Callable[[], T]], T]
) -> Callable[[Callable[[], T]], Callable[[], T]]:
    def decorator_(function_: Callable[[], T]) -> Callable[[], T]:
        def decorated_function() -> T:
            return caller_(function_)
        return decorated_function
    return decorator_

@decorator
def caller(function_: Callable[[], T]) -> T:
    return function_()

@caller
def function() -> int:
    return 0

var = function()

reveal_type(decorator)
reveal_type(caller)
reveal_type(function)
reveal_type(var)  # expected 'int', got 'T`-1' (Pylance reports 'int')

Example 3: decorator.decorator-style decorator with arguments (crashes with 1.4.1, see #15824; does not crash with #15837)

from typing import Callable, Concatenate, ParamSpec, TypeVar, reveal_type

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

# Trivial implementation of decorator.decorator[x]
# (with arguments) to investigate proper typing
def decorator(
    caller_: Callable[Concatenate[Callable[P, T], P], T]
) -> Callable[[Callable[P, T]], Callable[P, T]]:
    def decorator_(function_: Callable[P, T]) -> Callable[P, T]:
        def decorated_function(*args: P.args, **kwargs: P.kwargs) -> T:
            return caller_(function_, *args, **kwargs)
        return decorated_function
    return decorator_

@decorator
def caller(function_: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs) -> T:
    return function_(*args, **kwargs)

@caller
def function() -> int:
    return 0

var = function()

reveal_type(decorator)
reveal_type(caller)
reveal_type(function)
reveal_type(var)  # expected 'int', got 'T`-2' (Pylance reports 'int')

Expected Behavior

  • int

Actual Behavior

  • T-2 or T-1

Your Environment

  • Mypy version used: 1.4.1
  • Mypy command-line flags: none
  • Python version used: 3.11.4

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