# Legacy type variables

The tests in this file focus on how type variables are defined using the legacy notation. Most
_uses_ of type variables are tested in other files in this directory; we do not duplicate every test
for both type variable syntaxes.

Unless otherwise specified, all quotations come from the [Generics] section of the typing spec.

## Type variables

### Defining legacy type variables

> Generics can be parameterized by using a factory available in `typing` called `TypeVar`.

This was the only way to create type variables prior to PEP 695/Python 3.12. It is still available
in newer Python releases.

```py
from typing import TypeVar

T = TypeVar("T")
reveal_type(type(T))  # revealed: <class 'TypeVar'>
reveal_type(T)  # revealed: typing.TypeVar
reveal_type(T.__name__)  # revealed: Literal["T"]
```

### Directly assigned to a variable

> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
> part of a larger expression).

```py
from typing import TypeVar

T = TypeVar("T")
# TODO: no error
# error: [invalid-legacy-type-variable]
U: TypeVar = TypeVar("U")

# error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable"
# error: [invalid-type-form] "Function calls are not allowed in type expressions"
TestList = list[TypeVar("W")]
```

### `TypeVar` parameter must match variable name

> The argument to `TypeVar()` must be a string equal to the variable name to which it is assigned.

```py
from typing import TypeVar

# error: [invalid-legacy-type-variable] "The name of a legacy `typing.TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)"
T = TypeVar("Q")
```

### No redefinition

> Type variables must not be redefined.

```py
from typing import TypeVar

T = TypeVar("T")

# TODO: error
T = TypeVar("T")
```

### Type variables with a default

Note that the `__default__` property is only available in Python ≥3.13.

```toml
[environment]
python-version = "3.13"
```

```py
from typing import TypeVar

T = TypeVar("T", default=int)
reveal_type(T.__default__)  # revealed: int
reveal_type(T.__bound__)  # revealed: None
reveal_type(T.__constraints__)  # revealed: tuple[()]

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
from typing import TypeVar

T = TypeVar("T", bound=int)
reveal_type(T.__bound__)  # revealed: int
reveal_type(T.__constraints__)  # revealed: tuple[()]

S = TypeVar("S")
reveal_type(S.__bound__)  # revealed: None
```

### Type variables with constraints

```py
from typing import TypeVar

T = TypeVar("T", int, str)
reveal_type(T.__constraints__)  # revealed: tuple[int, str]

S = TypeVar("S")
reveal_type(S.__constraints__)  # revealed: tuple[()]
```

### Cannot have only one constraint

> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
> be at least two constraints, if any; specifying a single constraint is disallowed.

```py
from typing import TypeVar

# TODO: error: [invalid-type-variable-constraints]
T = TypeVar("T", int)
```

### Cannot be both covariant and contravariant

> To facilitate the declaration of container types where covariant or contravariant type checking is
> acceptable, type variables accept keyword arguments `covariant=True` or `contravariant=True`. At
> most one of these may be passed.

```py
from typing import TypeVar

# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=True, contravariant=True)
```

### Variance parameters must be unambiguous

```py
from typing import TypeVar

def cond() -> bool:
    return True

# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=cond())

# error: [invalid-legacy-type-variable]
U = TypeVar("U", contravariant=cond())
```

## Callability

A typevar bound to a Callable type is callable:

```py
from typing import Callable, TypeVar

T = TypeVar("T", bound=Callable[[], int])

def bound(f: T):
    reveal_type(f)  # revealed: T
    reveal_type(f())  # revealed: int
```

Same with a constrained typevar, as long as all constraints are callable:

```py
T = TypeVar("T", Callable[[], int], Callable[[], str])

def constrained(f: T):
    reveal_type(f)  # revealed: T
    reveal_type(f())  # revealed: int | str
```

## Meta-type

The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the
meta-types of the constraints:

```py
from typing import TypeVar

T_normal = TypeVar("T_normal")

def normal(x: T_normal):
    reveal_type(type(x))  # revealed: type

T_bound_object = TypeVar("T_bound_object", bound=object)

def bound_object(x: T_bound_object):
    reveal_type(type(x))  # revealed: type

T_bound_int = TypeVar("T_bound_int", bound=int)

def bound_int(x: T_bound_int):
    reveal_type(type(x))  # revealed: type[int]

T_constrained = TypeVar("T_constrained", int, str)

def constrained(x: T_constrained):
    reveal_type(type(x))  # revealed: type[int] | type[str]
```

[generics]: https://typing.python.org/en/latest/spec/generics.html
