What is a Type Alias Type ? Are we talking about the type of type aliases, or a concrete TypeAlias? These are different, and many would be forgiven to be frustrated if Type Aliasing in python creates Type Alias Types and not Type Aliases (!)
The correctest thing i’ve seen for this is Rust:
pub trait Deref {
type Target: ?Sized;
// Required method
fn deref(&self) -> &Self::Target;
}
Given the purpose of an alias is to serve as a shorter name for some other object,
if TypeAliasType has no Deref[T] for the aliased type,
then it’s not an alias, because it does not serve its intended function, and should be fixed.
truly, type(TypeAlias) == TypeAliasType
, but also
type(TypeAlias) == TypeAlias
that’s our quandry
To cut the knot, focus on the intended use case, which is to make type aliases, not types of type aliases, that’s too abstract, that’s like an extensibility issue for libraries, not a core language feature, so the concrete aliases rationally win because they actually work.
given
type X = MyGeneric[Thing]
if we have
type(X) == TypeAliasType
instead of
type(X) == MyGeneric[Thing]
then this is a correctness issue, because the X is a TypeAlias, not a Type Alias Type, we’ve complected (tied together) type(X) and type(type(X)) and that’s why this discussion is muddled.
No need to defend type aliases not aliasing.
Let’s make sure there is an open bug report for TypeAlias not working as intended, as this inappropriately categorizes concrete type aliases as abstract types of type aliases to the point they no longer do their job … as aliases
For me, this is a crucial issue to resolve if the Python type system is going to work in an unsurprising way. Right now, the aliases ain’t aliases, so what’s the point?
class Thing(Protocol):
@staticmethod
def yeet(x: Any) -> str:
return f"{x=} YEETED"
class API(Thing):
pass
@dataclass(frozen=True, slots=True)
class Stuff[THING: Thing]:
things: tuple[THING, ...]
@classmethod
def new(cls) -> "Stuff[THING]":
return cls(())
def from_things(things: Tuple[THING, ...]) -> "Stuff[THING]":
return Stuff[THING](things)
def test_stuff():
df1 = Stuff[API].new()
print(df1) # > Stuff(things=())
type TestStuff = Stuff[API]
df2 = TestStuff.new()
# ^^^ AttributeError: 'typing.TypeAliasType' object has no attribute 'new'
# except, TestStuff is `Deref[Stuff[API]]` which has `.new`
print(df2)
assert df1 == df2
test_stuff()
If we NEED multiple types of type aliases, then consider
@runtime_checkable
class Deref[REFERENT](Protocol):
def deref(self) -> REFERENT: ...
# ... in type.__init__ or wherever
if isinstance(self, Deref[R]):
return self.deref()
see also Deref in std::ops - Rust
which leads to the question
how do we add associated types to python