Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#4539·ty

Overload not resolved when the function is passed through a `ParamSpec` generic

Author: tibbeCreated Sep 16, 2026Updated Sep 16, 2026
Labelsgenericsoverloadsparamspec

Summary

When an overloaded function is passed to a generic function that uses ParamSpec, ty does not resolve the overload against the actual arguments. It returns a union of the overload return types instead. mypy and pyright both resolve it correctly.

python
from typing import Callable, Literal, overload, reveal_type

@overload
def f(*, flag: Literal[False]) -> str: ...
@overload
def f(*, flag: Literal[True] = ...) -> int: ...
def f(*, flag: bool = True) -> int | str:
    return 1 if flag else 'a'

def call[**P, T](fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
    return fn(*args, **kwargs)

reveal_type(f())      # ty: int         mypy: int   pyright: int
reveal_type(call(f))  # ty: str | int   mypy: int   pyright: int

The last line should be int. No flag argument is passed, so the second overload applies via its default.

The usual way to hit this is Executor.submit, whose first parameter is Callable[P, T]:

python
takes_int(executor.submit(f).result())
# error[invalid-argument-type]: Expected `int`, found `str | int`

With three or more overloads ty sometimes picks the last overload's return type rather than a union, but the effect is the same.

This looks like the other half of #2383. That issue was closed when ParamSpec support landed, and the argument is indeed accepted now — but the return type is still wrong.

Impact: we evaluated ty on a ~1600-file codebase that is clean under mypy strict. This one pattern produces 437 of 689 diagnostics, because our database accessors are overloaded on a Literal[bool] flag that selects the return type, and they are usually called through executor.submit.

Possibly related: #2568, #3415, #2799.

Version

ty 0.0.81

Source: astral-sh/ty

View original on GitHubView discussion on GitHub