I just realised that a related question is how unparameterized
aliases should behave.
The easiest thing would be to have a general rule: "If you omit the type parameter list, you replace `*Ts` with an arbitrary number of `Any`". Then we could do:
```
DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')
class Array(Generic[DType, *Shape]): ...
Float32Array = Array[np.float32, *Shape]
def takes_float_array_of_any_shape(x: Float32Array): ...
x: Array[np.float32, Height]
takes_float_array_of_any_shape(x) # Valid
y: Array[np.float32, Height, Width]
takes_float_array_of_any_shape(y) # Also valid
```
One complication, though, is that it implies:
```
IntTuple = Tuple[int, *Ts]
IntTuple # Behaves like Tuple[int, Any, ...]!?
```
I'm guessing there was a good reason that heterogeneous tuples of arbitrary length like this weren't supported by PEP 484?
Other options I can think of are:
- Disallow unparameterized variadic generic aliases in general
- This doesn't seem great because it would decrease backwards compatibility. Ideally, if a function in new library code takes a `Float32Array` (`== Array[Any, Any, ...]`), we want to let legacy code pass in a plain `Array` (`== Array[Any, ...]`).
- Also, it would be inconsistent with non-variadic generic aliases, where I think the type variables are just replaced as `Any` when unparameterized?
- Pull the same trick we did with unparameterized classes: not making `Tuple[int, Any, ...]` valid, but just saying that an unparameterized alias behaves as if it had been written like that.