[ty] Handle typevars that have other typevars as a default (#17956)

It's possible for a typevar to list another typevar as its default
value:

```py
class C[T, U = T]: ...
```

When specializing this class, if a type isn't provided for `U`, we would
previously use the default as-is, leaving an unspecialized `T` typevar
in the specialization. Instead, we want to use what `T` is mapped to as
the type of `U`.

```py
reveal_type(C())  # revealed: C[Unknown, Unknown]
reveal_type(C[int]())  # revealed: C[int, int]
reveal_type(C[int, str]())  # revealed: C[int, str]
```

This is especially important for the `slice` built-in type.
This commit is contained in:
Douglas Creager 2025-05-08 19:01:27 -04:00 committed by GitHub
parent f51f1f7153
commit b705664d49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 226 additions and 102 deletions

View file

@ -1945,7 +1945,7 @@ reveal_type(C.a_complex) # revealed: int | float | complex
reveal_type(C.a_tuple) # revealed: tuple[int]
reveal_type(C.a_range) # revealed: range
# TODO: revealed: slice[Any, Literal[1], Any]
reveal_type(C.a_slice) # revealed: slice[Any, _StartT_co, _StartT_co | _StopT_co]
reveal_type(C.a_slice) # revealed: slice[Any, Any, Any]
reveal_type(C.a_type) # revealed: type
reveal_type(C.a_none) # revealed: None
```

View file

@ -86,6 +86,26 @@ S = TypeVar("S")
reveal_type(S.__default__) # revealed: NoDefault
```
### Using other typevars as a default
```py
from typing import Generic, TypeVar, Union
T = TypeVar("T")
U = TypeVar("U", default=T)
V = TypeVar("V", default=Union[T, U])
class Valid(Generic[T, U, V]): ...
reveal_type(Valid()) # revealed: Valid[Unknown, Unknown, Unknown]
reveal_type(Valid[int]()) # revealed: Valid[int, int, int]
reveal_type(Valid[int, str]()) # revealed: Valid[int, str, int | str]
reveal_type(Valid[int, str, None]()) # revealed: Valid[int, str, None]
# TODO: error, default value for U isn't available in the generic context
class Invalid(Generic[U]): ...
```
### Type variables with an upper bound
```py

View file

@ -40,6 +40,25 @@ def g[S]():
reveal_type(S.__default__) # revealed: NoDefault
```
### Using other typevars as a default
```toml
[environment]
python-version = "3.13"
```
```py
class Valid[T, U = T, V = T | U]: ...
reveal_type(Valid()) # revealed: Valid[Unknown, Unknown, Unknown]
reveal_type(Valid[int]()) # revealed: Valid[int, int, int]
reveal_type(Valid[int, str]()) # revealed: Valid[int, str, int | str]
reveal_type(Valid[int, str, None]()) # revealed: Valid[int, str, None]
# error: [unresolved-reference]
class Invalid[S = T]: ...
```
### Type variables with an upper bound
```py