Add the ability for functions to inherit another functions' parameters.

Review Request #14507 — Created July 14, 2025 and updated — Latest diff uploaded

Information

typelets
main

Reviewers

One of the big missing gaps in the world of Python's type annotation
support is a way for a function to reference another function's
arguments in a signature. It's common for a function to take *args
and/or **kwargs and pass them somewhere else, but doing this loses all
typing.

The "official" way to deal with this is to effectively make all
arguments keyword-only and to define them in a function and in a
TypedDict, and use that for all wrapping functions. This is good in
many cases, but is not always a suitable approach.

Instead, this change introduces some new utilities for typing the
*args and **kwargs based on another function. The way it works is
through a combination of a decorator and a "ParamsFrom" class.

The "ParamsFrom" classes take in a callable, reference its ParamSpec,
and mixes it into a signature in that class's __call__. This gives us
a new ParamSpec that we can then reference in the decorator.

The decorator takes in the "ParamsFrom" class as a
Callable[TParams, Any]. That gives the decorator access to the
modified signature. It can then return a variation on the decorated
function that uses that signature.

We complete that by fixing up the annotations and signature to use the
right typing for the *args and **kwargs, allowing those to pass
through.

This has some initial startup cost (though minimal) and no runtime cost
when calling functions.

The end result is that any calls to the function will properly type the
parameters going in, based on that signature.

In practice, there are a few constraints to be aware of:

  1. ParamSpec doesn't do well with positional-only parameters. A
    function that has a self and then references positional-only
    parameters seems to reliably break when trying to match the type for
    the decorated function. This is solved with a # type: ignore, and
    does not appear to affect calls to the function.

  2. Function signatures need to be compatible between the
    decorated/signature and the referenced function. There's no going
    from keyword-only to positional-or-keyword, for example.

  3. Pylance/Pyright (and Visual Studio Code) have known issues around
    choosing the wrong docstrings when decorating a function and
    referencing another function as a parameter. This means that if
    hovering over a call to a function in any IDE using these as a basis
    for a LSP, we're going to get the wrong docstring for the function.
    There are many tickets/threads about this, but this is the most
    recent with any promising discussion:
    https://github.com/microsoft/pylance-release/issues/5840

  4. This does nothing useful in PyCharm, which doesn't handle ParamSpec
    fully. This notably is no worse than the behavior functions would
    have by default.

There's thorough documentation within the codebase docs, but a guide to
using this will be coming separately.

This is all very experimental, but types correctly in every major type
checker currently in use except for PyCharm. The hope is that some
wonderful person will design a PEP that makes this entire change
outdated.

Unit tests pass on all supported versions of Python.

Mypy, Pyright, and Pylance report zero typing issues.

Commits

Files