Should list arguments of a type statement that refer to a ParamSpec be converted to a tuple?

This topic is about a runtime design choice for type statements arguments and possibly minor clarification in the specification of on how to handle list arguments for ParamSpec substitution in general during runtime. I’ve opened an issue about this, but I think this likely needs some discussion first.

For Generic a list argument for a ParamSpec is converted to a tuple in their __args__ attribute. For a Callable its slightly different but still a tuple. However, for a type statement / TypeAliasType this is not the case. The list argument stays a list which in turn results in an unhashable object as the list is part of the hash. My suggestion is to also convert the arguments to a tuple during this substitution. Or, do you think it should stay the way it currently is?

In the example of PEP 612 the list vs. non-list case is described as equivalent. For Generics it is also runtime equivalent. Z[[int, str, bool]]) == Z[int, str, bool] with class Z(Generic[P]). However, this specification does not explicitly extend beyond generic classes.
@tomasr8, who is working on a PR for a related, but different TypeAliasType issue, rightfully noted that its not clear whether “equivalent” should extend to runtime equivalence and the __args__ argument should be converted to a tuple or kept as a list.


The following code shows some of the equivalences and differences:

from typing import Protocol, TypeVar
from collections.abc import Callable

class GenP[**P]: ...
class GenT[T]: ...

T = TypeVar('T')
P = ParamSpec('P')
CallableAlias = Callable[P, object]

# List is flattened for ParamSpec for Generic or Callable:
assert GenP[T] == GenP[[T]]
assert GenP[T].__args__ == GenP[[T]].__args__ 
# args also does contain object
assert CallableAlias[T].__args__  == CallableAlias[[T]].__args__
assert CallableAlias[[T]].__args__[:1] == (T,) == GenP[T]
assert hash(GenP[T]) == hash(GenP[[T]]) == hash(GenP[(T,)])

# For a normal type var this is not the case
# (T, ) vs ([T], )
assert GenT[T] != GenT[[T]]

# --------------------------------

# type statement / TypeAliasType
type CallableTypeAlias[**P] = Callable[P, object]
print(CallableTypeAlias[T].__args__, "vs", CallableTypeAlias[[T]].__args__)

# >>> This will fail:
# (T, ) vs ([T], )
assert CallableTypeAlias[T].__args__ == CallableTypeAlias[[T]].__args__

# This will fail because the list cannot be hashed:
hash(CallableTypeAlias[[T]])
# ~~~~^^^^^^^^^^^^^^^^^^^
# TypeError: unhashable type: 'list'