[ty] Handle decorators which return unions of Callables (#20858)

## Summary

If a function is decorated with a decorator that returns a union of
`Callable`s, also treat it as a union of function-like `Callable`s.

Labeling as `internal`, since the previous change has not been released
yet.

## Test Plan

New regression test.
This commit is contained in:
David Peter 2025-10-14 11:47:50 +02:00 committed by GitHub
parent c69fa75cd5
commit ac2c530377
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 43 additions and 9 deletions

View file

@ -145,22 +145,38 @@ class C2:
C2().method_decorated(1)
```
And with unions of `Callable` types:
```py
from typing import Callable
def expand(f: Callable[[C3, int], int]) -> Callable[[C3, int], int] | Callable[[C3, int], str]:
raise NotImplementedError
class C3:
@expand
def method_decorated(self, x: int) -> int:
return x
reveal_type(C3().method_decorated(1)) # revealed: int | str
```
Note that we currently only apply this heuristic when calling a function such as `memoize` via the
decorator syntax. This is inconsistent, because the above *should* be equivalent to the following,
but here we emit errors:
```py
def memoize3(f: Callable[[C3, int], str]) -> Callable[[C3, int], str]:
def memoize3(f: Callable[[C4, int], str]) -> Callable[[C4, int], str]:
raise NotImplementedError
class C3:
class C4:
def method(self, x: int) -> str:
return str(x)
method_decorated = memoize3(method)
# error: [missing-argument]
# error: [invalid-argument-type]
C3().method_decorated(1)
C4().method_decorated(1)
```
The reason for this is that the heuristic is problematic. We don't *know* that the `Callable` in the