python / python/mypy

Making a type alias of a Callable that describes a decorator discards all type information

Open
#18,842 14 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-type-alias
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Bug Report

Attempting to make a type alias for a decorator results in MyPy losing type information.

To Reproduce

from typing import *

type Fn[**P, R] = Callable[P, R]
type Decorator[**P, R] = Callable[[Fn[P, R]], Fn[P, R]]


def do_the_thing[**P, R]() -> Decorator[P, R]:
    def decorator(fn: Fn[P, R]) -> Fn[P, R]:
        return fn
    return decorator


@do_the_thing()
def blahblah() -> None:
    pass

Seems this is not an artifact of how variance is being calculated with the new type alias syntax as using the old style syntax has the same issue when declaring the type hints...

from typing import *

P = ParamSpec("P")
R_co = TypeVar("R_co", covariant=True)
Fn: TypeAlias = Callable[P, R_co]
Decorator: TypeAlias = Callable[[Fn[P, R_co]], Fn[P, R_co]]


def do_the_thing[**P, R_co]() -> Decorator[P, R_co]:
    def decorator(fn: Fn[P, R_co]) -> Fn[P, R_co]:
        return fn
    return decorator


@do_the_thing()
def blahblah() -> None:
    pass

...and the same issue if I just discard the type variables entirely in the function definition:

from typing import *

P = ParamSpec("P")
R_co = TypeVar("R_co", covariant=True)
Fn: TypeAlias = Callable[P, R_co]
Decorator: TypeAlias = Callable[[Fn[P, R_co]], Fn[P, R_co]]

def do_the_thing() -> Decorator[P, R_co]:
    def decorator(fn: Fn[P, R_co]) -> Fn[P, R_co]:
        return fn
    return decorator

@do_the_thing()
def blahblah() -> None:
    pass

Using a protocol doesn't work either:

from typing import *

type Fn[**P, R] = Callable[P, R]

class Decorator[**P, R](Protocol):
    def __call__(self, fn: Fn[P, R], /) -> Fn[P, R]: ...

def do_the_thing[**P, R]() -> Decorator[P, R]:
    def decorator(fn: Fn[P, R]) -> Fn[P, R]:
        return fn
    return decorator

@do_the_thing()
def blahblah() -> None:
    pass

...so it seems the type information is just totally discarded.

Strangely, if I don't type-alias the decorator, then it works fine (which is a workaround, but very annoying and verbose for more complicated logic).

# this is fine.
def do_the_thing[**P, R]() -> Callable[[Fn[P, R]], Fn[P, R]]:
    def decorator(fn: Fn[P, R]) -> Fn[P, R]:
        return fn
    return decorator

The problem is that I want to be able to alias Callable[[Fn[P, R]], Fn[P, R]] to a clean single identifier that I can reuse in the current type context, but I cannot find a nice way of doing that at the moment.

Expected Behavior

This should be valid.

Actual Behavior

main.py:14: error: Argument 1 has incompatible type "Callable[[], None]"; expected "Callable[[VarArg(Never), KwArg(Never)], Never]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Related Issues:

I came across GH-16512, but that was closed as fixed. Not sure if it is the same problem or not!

Your Environment

  • Mypy version used: 1.14.1, 1.15.0
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.12, 3.13
  • Reproducible in https://mypy-play.net/?mypy=1.15.0&python=3.13

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the minimal decorator type-alias reproducer in the issue and compare it with the inline Callable version, using the linked mypy-play example or the reported mypy versions. Trace where the aliased Callable loses its ParamSpec and return-type information, then add a regression test showing that the decorated function retains its signature and passes without an error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.