Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

@dhruvmanila dhruvmanila Mar 18, 2026

Copy link
Copy Markdown
Member Author

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

@dhruvmanila dhruvmanila Mar 18, 2026

Copy link
Copy Markdown
Member Author

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