Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -369,16 +369,14 @@ Using `Concatenate` as the first argument to `Callable`:
from typing_extensions import Callable, Concatenate

def _(c: Callable[Concatenate[int, str, ...], int]):
# TODO: Should reveal the correct signature
reveal_type(c) # revealed: (...) -> int
reveal_type(c) # revealed: (int, str, /, *args: Any, **kwargs: Any) -> int
```

Other type expressions can be nested inside `Concatenate`:

```py
def _(c: Callable[[Concatenate[int | str, type[str], ...], int], int]):
# TODO: Should reveal the correct signature
reveal_type(c) # revealed: (...) -> int
def _(c: Callable[Concatenate[int | str, type[str], ...], int]):
reveal_type(c) # revealed: (int | str, type[str], /, *args: Any, **kwargs: Any) -> int
```

But providing fewer than 2 arguments to `Concatenate` is an error:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _(
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
b: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a type expression"
e: ParamSpec,
f: Generic, # error: [invalid-type-form] "`typing.Generic` is not allowed in type expressions"
) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,38 @@ class Calculator:
reveal_type(Calculator().square_then_round(3.14)) # revealed: Unknown | int
```

## Use case: Wrappers with explicit receivers
Copy link
Member Author

@dhruvmanila dhruvmanila Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


`trio` defines multiple functions that takes in a callable with `Concatenate`-prepended receiver
types, and returns a wrapper function with a different receiver type. They should still preserve
descriptor behavior when the returned callable is assigned in the class body.

```py
from collections.abc import Callable, Iterable
from typing import Concatenate, ParamSpec, TypeVar

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

class RawPath:
def write_bytes(self, data: bytes) -> int:
raise NotImplementedError

def _wrap_method(
fn: Callable[Concatenate[RawPath, P], T],
) -> Callable[Concatenate["Path", P], T]:
raise NotImplementedError

class Path:
write_bytes = _wrap_method(RawPath.write_bytes)

def check(path: Path) -> None:
# TODO: shouldn't be errors, should reveal `int`
# error: [missing-argument]
# error: [invalid-argument-type]
reveal_type(path.write_bytes(b"")) # revealed: Unknown | int
```

## Use case: Treating dunder methods as bound-method descriptors

pytorch defines a `__pow__` dunder attribute on [`TensorBase`] in a similar way to the following
Expand Down
2 changes: 1 addition & 1 deletion crates/ty_python_semantic/resources/mdtest/final.md
Original file line number Diff line number Diff line change
Expand Up @@ -1325,7 +1325,7 @@ class Base(ABC):
@abstractproperty # error: [deprecated]
def value(self) -> int:
return 0

# error: [invalid-argument-type]
@abstractclassmethod # error: [deprecated]
def make(cls) -> "Base":
raise NotImplementedError
Comment on lines -1328 to 1331
Copy link
Member Author

@dhruvmanila dhruvmanila Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit unsure on why this isn't working, it might be related to the fact that it accepts type[_T] as the first parameter. Both mypy and Pyright seems to be raising an error here as well. And, ty works correctly when using the correct way i.e.,

    @classmethod
    @abstractmethod
    def make(cls) -> "Base":
        reveal_type(cls)  # revealed: type[Self@make]
        raise NotImplementedError

Expand Down
Loading
Loading