[ty] Support single-starred argument for overload call (#20223)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary

closes: https://github.com/astral-sh/ty/issues/247

This PR adds support for variadic arguments to overload call evaluation.

This basically boils down to making sure that the overloads are not
filtered out incorrectly during the step 5 in the overload call
evaluation algorithm. For context, the step 5 tries to filter out the
remaining overloads after finding an overload where the materialization
of argument types are assignable to the parameter types.

The issue with the previous implementation was that it wouldn't unpack
the variadic argument and wouldn't consider the many-to-one (multiple
arguments mapping to a single variadic parameter) correctly. This PR
fixes that.

## Test Plan

Update existing test cases and resolve the TODOs.
This commit is contained in:
Dhruv Manilawala 2025-10-02 20:11:56 +05:30 committed by GitHub
parent 0639da2552
commit 4e94b22815
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 99 additions and 71 deletions

View file

@ -139,8 +139,7 @@ reveal_type(f(A())) # revealed: A
reveal_type(f(*(A(),))) # revealed: A
reveal_type(f(B())) # revealed: A
# TODO: revealed: A
reveal_type(f(*(B(),))) # revealed: Unknown
reveal_type(f(*(B(),))) # revealed: A
# But, in this case, the arity check filters out the first overload, so we only have one match:
reveal_type(f(B(), 1)) # revealed: B
@ -551,16 +550,13 @@ from overloaded import MyEnumSubclass, ActualEnum, f
def _(actual_enum: ActualEnum, my_enum_instance: MyEnumSubclass):
reveal_type(f(actual_enum)) # revealed: Both
# TODO: revealed: Both
reveal_type(f(*(actual_enum,))) # revealed: Unknown
reveal_type(f(*(actual_enum,))) # revealed: Both
reveal_type(f(ActualEnum.A)) # revealed: OnlyA
# TODO: revealed: OnlyA
reveal_type(f(*(ActualEnum.A,))) # revealed: Unknown
reveal_type(f(*(ActualEnum.A,))) # revealed: OnlyA
reveal_type(f(ActualEnum.B)) # revealed: OnlyB
# TODO: revealed: OnlyB
reveal_type(f(*(ActualEnum.B,))) # revealed: Unknown
reveal_type(f(*(ActualEnum.B,))) # revealed: OnlyB
reveal_type(f(my_enum_instance)) # revealed: MyEnumSubclass
reveal_type(f(*(my_enum_instance,))) # revealed: MyEnumSubclass
@ -1097,12 +1093,10 @@ reveal_type(f(*(1,))) # revealed: str
def _(list_int: list[int], list_any: list[Any]):
reveal_type(f(list_int)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(list_int,))) # revealed: Unknown
reveal_type(f(*(list_int,))) # revealed: int
reveal_type(f(list_any)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(list_any,))) # revealed: Unknown
reveal_type(f(*(list_any,))) # revealed: int
```
### Single list argument (ambiguous)
@ -1136,8 +1130,7 @@ def _(list_int: list[int], list_any: list[Any]):
# All materializations of `list[int]` are assignable to `list[int]`, so it matches the first
# overload.
reveal_type(f(list_int)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(list_int,))) # revealed: Unknown
reveal_type(f(*(list_int,))) # revealed: int
# All materializations of `list[Any]` are assignable to `list[int]` and `list[Any]`, but the
# return type of first and second overloads are not equivalent, so the overload matching
@ -1170,25 +1163,21 @@ reveal_type(f("a")) # revealed: str
reveal_type(f(*("a",))) # revealed: str
reveal_type(f((1, "b"))) # revealed: int
# TODO: revealed: int
reveal_type(f(*((1, "b"),))) # revealed: Unknown
reveal_type(f(*((1, "b"),))) # revealed: int
reveal_type(f((1, 2))) # revealed: int
# TODO: revealed: int
reveal_type(f(*((1, 2),))) # revealed: Unknown
reveal_type(f(*((1, 2),))) # revealed: int
def _(int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]):
# All materializations are assignable to first overload, so second and third overloads are
# eliminated
reveal_type(f(int_str)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(int_str,))) # revealed: Unknown
reveal_type(f(*(int_str,))) # revealed: int
# All materializations are assignable to second overload, so the third overload is eliminated;
# the return type of first and second overload is equivalent
reveal_type(f(int_any)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(int_any,))) # revealed: Unknown
reveal_type(f(*(int_any,))) # revealed: int
# All materializations of `tuple[Any, Any]` are assignable to the parameters of all the
# overloads, but the return types aren't equivalent, so the overload matching is ambiguous
@ -1266,26 +1255,22 @@ def _(list_int: list[int], list_any: list[Any], int_str: tuple[int, str], int_an
# All materializations of both argument types are assignable to the first overload, so the
# second and third overloads are filtered out
reveal_type(f(list_int, int_str)) # revealed: A
# TODO: revealed: A
reveal_type(f(*(list_int, int_str))) # revealed: Unknown
reveal_type(f(*(list_int, int_str))) # revealed: A
# All materialization of first argument is assignable to first overload and for the second
# argument, they're assignable to the second overload, so the third overload is filtered out
reveal_type(f(list_int, int_any)) # revealed: A
# TODO: revealed: A
reveal_type(f(*(list_int, int_any))) # revealed: Unknown
reveal_type(f(*(list_int, int_any))) # revealed: A
# All materialization of first argument is assignable to second overload and for the second
# argument, they're assignable to the first overload, so the third overload is filtered out
reveal_type(f(list_any, int_str)) # revealed: A
# TODO: revealed: A
reveal_type(f(*(list_any, int_str))) # revealed: Unknown
reveal_type(f(*(list_any, int_str))) # revealed: A
# All materializations of both arguments are assignable to the second overload, so the third
# overload is filtered out
reveal_type(f(list_any, int_any)) # revealed: A
# TODO: revealed: A
reveal_type(f(*(list_any, int_any))) # revealed: Unknown
reveal_type(f(*(list_any, int_any))) # revealed: A
# All materializations of first argument is assignable to the second overload and for the second
# argument, they're assignable to the third overload, so no overloads are filtered out; the
@ -1316,8 +1301,7 @@ from overloaded import f
def _(literal: LiteralString, string: str, any: Any):
reveal_type(f(literal)) # revealed: LiteralString
# TODO: revealed: LiteralString
reveal_type(f(*(literal,))) # revealed: Unknown
reveal_type(f(*(literal,))) # revealed: LiteralString
reveal_type(f(string)) # revealed: str
reveal_type(f(*(string,))) # revealed: str
@ -1355,12 +1339,10 @@ from overloaded import f
def _(list_int: list[int], list_str: list[str], list_any: list[Any], any: Any):
reveal_type(f(list_int)) # revealed: A
# TODO: revealed: A
reveal_type(f(*(list_int,))) # revealed: Unknown
reveal_type(f(*(list_int,))) # revealed: A
reveal_type(f(list_str)) # revealed: str
# TODO: Should be `str`
reveal_type(f(*(list_str,))) # revealed: Unknown
reveal_type(f(*(list_str,))) # revealed: str
reveal_type(f(list_any)) # revealed: Unknown
reveal_type(f(*(list_any,))) # revealed: Unknown
@ -1561,12 +1543,10 @@ def _(any: Any):
reveal_type(f(*(any,), flag=False)) # revealed: str
def _(args: tuple[Any, Literal[True]]):
# TODO: revealed: int
reveal_type(f(*args)) # revealed: Unknown
reveal_type(f(*args)) # revealed: int
def _(args: tuple[Any, Literal[False]]):
# TODO: revealed: str
reveal_type(f(*args)) # revealed: Unknown
reveal_type(f(*args)) # revealed: str
```
### Argument type expansion