[ty] Distribute type[] over unions (#22115)

## Summary

Closes https://github.com/astral-sh/ty/issues/2121.
This commit is contained in:
Charlie Marsh
2025-12-21 18:45:29 -05:00
committed by GitHub
parent b6e84eca16
commit fee4e2d72a
4 changed files with 94 additions and 6 deletions

View File

@@ -1179,6 +1179,34 @@ def _(
reveal_type(subclass_of_p) # revealed: type[P]
```
Using `type[]` with a union type alias distributes the `type[]` over the union elements:
```py
from typing import Union
class C: ...
class D: ...
UnionAlias1 = C | D
UnionAlias2 = Union[C, D]
SubclassOfUnionAlias1 = type[UnionAlias1]
SubclassOfUnionAlias2 = type[UnionAlias2]
reveal_type(SubclassOfUnionAlias1) # revealed: <special-form 'type[C | D]'>
reveal_type(SubclassOfUnionAlias2) # revealed: <special-form 'type[C | D]'>
def _(
subclass_of_union_alias1: SubclassOfUnionAlias1,
subclass_of_union_alias2: SubclassOfUnionAlias2,
):
reveal_type(subclass_of_union_alias1) # revealed: type[C] | type[D]
reveal_type(subclass_of_union_alias1()) # revealed: C | D
reveal_type(subclass_of_union_alias2) # revealed: type[C] | type[D]
reveal_type(subclass_of_union_alias2()) # revealed: C | D
```
Invalid uses result in diagnostics:
```py

View File

@@ -71,6 +71,29 @@ reveal_type(constrained(str)) # revealed: str
constrained(A)
```
`type[T]` with a union upper bound `T: A | B` represents the metatype of a type variable `T` where
`T` can be solved to any subtype of `A` or any subtype of `B`. It behaves similarly to a type
variable that can be solved to any subclass of `A` or any subclass of `B`. Since all classes are
instances of `type`, attributes on instances of `type` like `__name__` and `__qualname__` should
still be accessible:
```py
class Replace: ...
class Multiply: ...
def union_bound[T: Replace | Multiply](x: type[T]) -> T:
reveal_type(x) # revealed: type[T@union_bound]
# All classes have __name__ and __qualname__ from type's metaclass
reveal_type(x.__name__) # revealed: str
reveal_type(x.__qualname__) # revealed: str
reveal_type(x()) # revealed: T@union_bound
return x()
reveal_type(union_bound(Replace)) # revealed: Replace
reveal_type(union_bound(Multiply)) # revealed: Multiply
```
## Union
```py