Skip to content

Commit 9e80e5a

Browse files
authored
[ty] Support type[…] and Type[…] in implicit type aliases (#21421)
## Summary Support `type[…]` in implicit type aliases, for example: ```py SubclassOfInt = type[int] reveal_type(SubclassOfInt) # GenericAlias def _(subclass_of_int: SubclassOfInt): reveal_type(subclass_of_int) # type[int] ``` part of astral-sh/ty#221 ## Typing conformance ```diff -specialtypes_type.py:138:5: error[type-assertion-failure] Argument does not have asserted type `type[Any]` -specialtypes_type.py:140:5: error[type-assertion-failure] Argument does not have asserted type `type[Any]` ``` Two new tests passing :heavy_check_mark: ```diff -specialtypes_type.py:146:1: error[unresolved-attribute] Object of type `GenericAlias` has no attribute `unknown` ``` An `TA4.unknown` attribute on a PEP 613 alias (`TA4: TypeAlias = type[Any]`) is being accessed, and the conformance suite expects this to be an error. Since we currently use the inferred type for these type aliases (and possibly in the future as well), we treat this as a direct access of the attribute on `type[Any]`, which falls back to an access on `Any` itself, which succeeds. :red_circle: ``` +specialtypes_type.py:152:16: error[invalid-type-form] `typing.TypeVar` is not a generic class +specialtypes_type.py:156:16: error[invalid-type-form] `typing.TypeVar` is not a generic class ``` New errors because we don't handle `T = TypeVar("T"); MyType = type[T]; MyType[T]` yet. Support for this is being tracked in astral-sh/ty#221 🔴 ## Ecosystem impact Looks mostly good, a few known problems. ## Test Plan New Markdown tests
1 parent f9cc26a commit 9e80e5a

File tree

6 files changed

+207
-8
lines changed

6 files changed

+207
-8
lines changed

crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ IntOrAnnotated = int | Annotated[str, "meta"]
6666
AnnotatedOrInt = Annotated[str, "meta"] | int
6767
IntOrOptional = int | Optional[str]
6868
OptionalOrInt = Optional[str] | int
69+
IntOrTypeOfStr = int | type[str]
70+
TypeOfStrOrInt = type[str] | int
6971

7072
reveal_type(IntOrStr) # revealed: types.UnionType
7173
reveal_type(IntOrStrOrBytes1) # revealed: types.UnionType
@@ -97,6 +99,8 @@ reveal_type(IntOrAnnotated) # revealed: types.UnionType
9799
reveal_type(AnnotatedOrInt) # revealed: types.UnionType
98100
reveal_type(IntOrOptional) # revealed: types.UnionType
99101
reveal_type(OptionalOrInt) # revealed: types.UnionType
102+
reveal_type(IntOrTypeOfStr) # revealed: types.UnionType
103+
reveal_type(TypeOfStrOrInt) # revealed: types.UnionType
100104

101105
def _(
102106
int_or_str: IntOrStr,
@@ -129,6 +133,8 @@ def _(
129133
annotated_or_int: AnnotatedOrInt,
130134
int_or_optional: IntOrOptional,
131135
optional_or_int: OptionalOrInt,
136+
int_or_type_of_str: IntOrTypeOfStr,
137+
type_of_str_or_int: TypeOfStrOrInt,
132138
):
133139
reveal_type(int_or_str) # revealed: int | str
134140
reveal_type(int_or_str_or_bytes1) # revealed: int | str | bytes
@@ -160,6 +166,8 @@ def _(
160166
reveal_type(annotated_or_int) # revealed: str | int
161167
reveal_type(int_or_optional) # revealed: int | str | None
162168
reveal_type(optional_or_int) # revealed: str | None | int
169+
reveal_type(int_or_type_of_str) # revealed: int | type[str]
170+
reveal_type(type_of_str_or_int) # revealed: type[str] | int
163171
```
164172

165173
If a type is unioned with itself in a value expression, the result is just that type. No
@@ -599,6 +607,158 @@ def _(
599607
reveal_type(invalid) # revealed: str | Unknown
600608
```
601609

610+
## `type[…]` and `Type[…]`
611+
612+
### `type[…]`
613+
614+
We support implicit type aliases using `type[…]`:
615+
616+
```py
617+
from typing import Any, Union, Protocol, TypeVar, Generic
618+
619+
T = TypeVar("T")
620+
621+
class A: ...
622+
class B: ...
623+
class G(Generic[T]): ...
624+
625+
class P(Protocol):
626+
def method(self) -> None: ...
627+
628+
SubclassOfA = type[A]
629+
SubclassOfAny = type[Any]
630+
SubclassOfAOrB1 = type[A | B]
631+
SubclassOfAOrB2 = type[A] | type[B]
632+
SubclassOfAOrB3 = Union[type[A], type[B]]
633+
SubclassOfG = type[G]
634+
SubclassOfGInt = type[G[int]]
635+
SubclassOfP = type[P]
636+
637+
reveal_type(SubclassOfA) # revealed: GenericAlias
638+
reveal_type(SubclassOfAny) # revealed: GenericAlias
639+
reveal_type(SubclassOfAOrB1) # revealed: GenericAlias
640+
reveal_type(SubclassOfAOrB2) # revealed: types.UnionType
641+
reveal_type(SubclassOfAOrB3) # revealed: types.UnionType
642+
reveal_type(SubclassOfG) # revealed: GenericAlias
643+
reveal_type(SubclassOfGInt) # revealed: GenericAlias
644+
reveal_type(SubclassOfP) # revealed: GenericAlias
645+
646+
def _(
647+
subclass_of_a: SubclassOfA,
648+
subclass_of_any: SubclassOfAny,
649+
subclass_of_a_or_b1: SubclassOfAOrB1,
650+
subclass_of_a_or_b2: SubclassOfAOrB2,
651+
subclass_of_a_or_b3: SubclassOfAOrB3,
652+
subclass_of_g: SubclassOfG,
653+
subclass_of_g_int: SubclassOfGInt,
654+
subclass_of_p: SubclassOfP,
655+
):
656+
reveal_type(subclass_of_a) # revealed: type[A]
657+
reveal_type(subclass_of_a()) # revealed: A
658+
659+
reveal_type(subclass_of_any) # revealed: type[Any]
660+
reveal_type(subclass_of_any()) # revealed: Any
661+
662+
reveal_type(subclass_of_a_or_b1) # revealed: type[A] | type[B]
663+
reveal_type(subclass_of_a_or_b1()) # revealed: A | B
664+
665+
reveal_type(subclass_of_a_or_b2) # revealed: type[A] | type[B]
666+
reveal_type(subclass_of_a_or_b2()) # revealed: A | B
667+
668+
reveal_type(subclass_of_a_or_b3) # revealed: type[A] | type[B]
669+
reveal_type(subclass_of_a_or_b3()) # revealed: A | B
670+
671+
reveal_type(subclass_of_g) # revealed: type[G[Unknown]]
672+
reveal_type(subclass_of_g()) # revealed: G[Unknown]
673+
674+
reveal_type(subclass_of_g_int) # revealed: type[G[int]]
675+
reveal_type(subclass_of_g_int()) # revealed: G[int]
676+
677+
reveal_type(subclass_of_p) # revealed: type[P]
678+
```
679+
680+
Invalid uses result in diagnostics:
681+
682+
```py
683+
# error: [invalid-type-form]
684+
InvalidSubclass = type[1]
685+
```
686+
687+
### `Type[…]`
688+
689+
The same also works for `typing.Type[…]`:
690+
691+
```py
692+
from typing import Any, Union, Protocol, TypeVar, Generic, Type
693+
694+
T = TypeVar("T")
695+
696+
class A: ...
697+
class B: ...
698+
class G(Generic[T]): ...
699+
700+
class P(Protocol):
701+
def method(self) -> None: ...
702+
703+
SubclassOfA = Type[A]
704+
SubclassOfAny = Type[Any]
705+
SubclassOfAOrB1 = Type[A | B]
706+
SubclassOfAOrB2 = Type[A] | Type[B]
707+
SubclassOfAOrB3 = Union[Type[A], Type[B]]
708+
SubclassOfG = Type[G]
709+
SubclassOfGInt = Type[G[int]]
710+
SubclassOfP = Type[P]
711+
712+
reveal_type(SubclassOfA) # revealed: GenericAlias
713+
reveal_type(SubclassOfAny) # revealed: GenericAlias
714+
reveal_type(SubclassOfAOrB1) # revealed: GenericAlias
715+
reveal_type(SubclassOfAOrB2) # revealed: types.UnionType
716+
reveal_type(SubclassOfAOrB3) # revealed: types.UnionType
717+
reveal_type(SubclassOfG) # revealed: GenericAlias
718+
reveal_type(SubclassOfGInt) # revealed: GenericAlias
719+
reveal_type(SubclassOfP) # revealed: GenericAlias
720+
721+
def _(
722+
subclass_of_a: SubclassOfA,
723+
subclass_of_any: SubclassOfAny,
724+
subclass_of_a_or_b1: SubclassOfAOrB1,
725+
subclass_of_a_or_b2: SubclassOfAOrB2,
726+
subclass_of_a_or_b3: SubclassOfAOrB3,
727+
subclass_of_g: SubclassOfG,
728+
subclass_of_g_int: SubclassOfGInt,
729+
subclass_of_p: SubclassOfP,
730+
):
731+
reveal_type(subclass_of_a) # revealed: type[A]
732+
reveal_type(subclass_of_a()) # revealed: A
733+
734+
reveal_type(subclass_of_any) # revealed: type[Any]
735+
reveal_type(subclass_of_any()) # revealed: Any
736+
737+
reveal_type(subclass_of_a_or_b1) # revealed: type[A] | type[B]
738+
reveal_type(subclass_of_a_or_b1()) # revealed: A | B
739+
740+
reveal_type(subclass_of_a_or_b2) # revealed: type[A] | type[B]
741+
reveal_type(subclass_of_a_or_b2()) # revealed: A | B
742+
743+
reveal_type(subclass_of_a_or_b3) # revealed: type[A] | type[B]
744+
reveal_type(subclass_of_a_or_b3()) # revealed: A | B
745+
746+
reveal_type(subclass_of_g) # revealed: type[G[Unknown]]
747+
reveal_type(subclass_of_g()) # revealed: G[Unknown]
748+
749+
reveal_type(subclass_of_g_int) # revealed: type[G[int]]
750+
reveal_type(subclass_of_g_int()) # revealed: G[int]
751+
752+
reveal_type(subclass_of_p) # revealed: type[P]
753+
```
754+
755+
Invalid uses result in diagnostics:
756+
757+
```py
758+
# error: [invalid-type-form]
759+
InvalidSubclass = Type[1]
760+
```
761+
602762
## Stringified annotations?
603763

604764
From the [typing spec on type aliases](https://typing.python.org/en/latest/spec/aliases.html):
@@ -633,15 +793,18 @@ from typing import Union
633793

634794
ListOfInts = list["int"]
635795
StrOrStyle = Union[str, "Style"]
796+
SubclassOfStyle = type["Style"]
636797

637798
class Style: ...
638799

639800
def _(
640801
list_of_ints: ListOfInts,
641802
str_or_style: StrOrStyle,
803+
subclass_of_style: SubclassOfStyle,
642804
):
643805
reveal_type(list_of_ints) # revealed: list[int]
644806
reveal_type(str_or_style) # revealed: str | Style
807+
reveal_type(subclass_of_style) # revealed: type[Style]
645808
```
646809

647810
## Recursive

crates/ty_python_semantic/resources/mdtest/type_of/basic.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,7 @@ from ty_extensions import reveal_mro
149149

150150
class Foo(type[int]): ...
151151

152-
# TODO: should be `tuple[<class 'Foo'>, <class 'type'>, <class 'object'>]
153-
reveal_mro(Foo) # revealed: (<class 'Foo'>, @Todo(GenericAlias instance), <class 'object'>)
152+
reveal_mro(Foo) # revealed: (<class 'Foo'>, <class 'type'>, <class 'object'>)
154153
```
155154

156155
## Display of generic `type[]` types

crates/ty_python_semantic/src/types.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6607,6 +6607,17 @@ impl<'db> Type<'db> {
66076607
.inner(db)
66086608
.in_type_expression(db, scope_id, typevar_binding_context)?)
66096609
}
6610+
KnownInstanceType::TypeGenericAlias(ty) => {
6611+
// When `type[…]` appears in a value position (e.g. in an implicit type alias),
6612+
// we infer its argument as a type expression. This ensures that we can emit
6613+
// diagnostics for invalid type expressions, and more importantly, that we can
6614+
// make use of stringified annotations. The drawback is that we need to turn
6615+
// instances back into the corresponding subclass-of types here. This process
6616+
// (`int` -> instance of `int` -> subclass of `int`) can be lossy, but it is
6617+
// okay for all valid arguments to `type[…]`.
6618+
6619+
Ok(ty.inner(db).to_meta_type(db))
6620+
}
66106621
},
66116622

66126623
Type::SpecialForm(special_form) => match special_form {
@@ -7847,6 +7858,9 @@ pub enum KnownInstanceType<'db> {
78477858
/// A single instance of `typing.Annotated`
78487859
Annotated(InternedType<'db>),
78497860

7861+
/// An instance of `typing.GenericAlias` representing a `type[...]` expression.
7862+
TypeGenericAlias(InternedType<'db>),
7863+
78507864
/// An identity callable created with `typing.NewType(name, base)`, which behaves like a
78517865
/// subtype of `base` in type expressions. See the `struct NewType` payload for an example.
78527866
NewType(NewType<'db>),
@@ -7881,7 +7895,9 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
78817895
visitor.visit_type(db, *element);
78827896
}
78837897
}
7884-
KnownInstanceType::Literal(ty) | KnownInstanceType::Annotated(ty) => {
7898+
KnownInstanceType::Literal(ty)
7899+
| KnownInstanceType::Annotated(ty)
7900+
| KnownInstanceType::TypeGenericAlias(ty) => {
78857901
visitor.visit_type(db, ty.inner(db));
78867902
}
78877903
KnownInstanceType::NewType(newtype) => {
@@ -7928,6 +7944,7 @@ impl<'db> KnownInstanceType<'db> {
79287944
Self::UnionType(list) => Self::UnionType(list.normalized_impl(db, visitor)),
79297945
Self::Literal(ty) => Self::Literal(ty.normalized_impl(db, visitor)),
79307946
Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)),
7947+
Self::TypeGenericAlias(ty) => Self::TypeGenericAlias(ty.normalized_impl(db, visitor)),
79317948
Self::NewType(newtype) => Self::NewType(
79327949
newtype
79337950
.map_base_class_type(db, |class_type| class_type.normalized_impl(db, visitor)),
@@ -7950,8 +7967,9 @@ impl<'db> KnownInstanceType<'db> {
79507967
Self::Field(_) => KnownClass::Field,
79517968
Self::ConstraintSet(_) => KnownClass::ConstraintSet,
79527969
Self::UnionType(_) => KnownClass::UnionType,
7953-
Self::Literal(_) => KnownClass::GenericAlias,
7954-
Self::Annotated(_) => KnownClass::GenericAlias,
7970+
Self::Literal(_) | Self::Annotated(_) | Self::TypeGenericAlias(_) => {
7971+
KnownClass::GenericAlias
7972+
}
79557973
Self::NewType(_) => KnownClass::NewType,
79567974
}
79577975
}
@@ -8037,6 +8055,7 @@ impl<'db> KnownInstanceType<'db> {
80378055
KnownInstanceType::Annotated(_) => {
80388056
f.write_str("<typing.Annotated special form>")
80398057
}
8058+
KnownInstanceType::TypeGenericAlias(_) => f.write_str("GenericAlias"),
80408059
KnownInstanceType::NewType(declaration) => {
80418060
write!(f, "<NewType pseudo-class '{}'>", declaration.name(self.db))
80428061
}

crates/ty_python_semantic/src/types/class_base.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ impl<'db> ClassBase<'db> {
180180
// wrappers are just identity callables at runtime, so this sort of inheritance
181181
// doesn't work and isn't allowed.
182182
| KnownInstanceType::NewType(_) => None,
183+
KnownInstanceType::TypeGenericAlias(_) => {
184+
Self::try_from_type(db, KnownClass::Type.to_class_literal(db), subclass)
185+
}
183186
KnownInstanceType::Annotated(ty) => Self::try_from_type(db, ty.inner(db), subclass),
184187
},
185188

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9457,7 +9457,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
94579457
| Type::KnownInstance(
94589458
KnownInstanceType::UnionType(_)
94599459
| KnownInstanceType::Literal(_)
9460-
| KnownInstanceType::Annotated(_),
9460+
| KnownInstanceType::Annotated(_)
9461+
| KnownInstanceType::TypeGenericAlias(_),
94619462
),
94629463
Type::ClassLiteral(..)
94639464
| Type::SubclassOf(..)
@@ -9466,7 +9467,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
94669467
| Type::KnownInstance(
94679468
KnownInstanceType::UnionType(_)
94689469
| KnownInstanceType::Literal(_)
9469-
| KnownInstanceType::Annotated(_),
9470+
| KnownInstanceType::Annotated(_)
9471+
| KnownInstanceType::TypeGenericAlias(_),
94709472
),
94719473
ast::Operator::BitOr,
94729474
) if pep_604_unions_allowed() => {
@@ -10627,7 +10629,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1062710629
// special cases, too.
1062810630
if class.is_tuple(self.db()) {
1062910631
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
10632+
} else if class.is_known(self.db(), KnownClass::Type) {
10633+
let argument_ty = self.infer_type_expression(slice);
10634+
return Type::KnownInstance(KnownInstanceType::TypeGenericAlias(
10635+
InternedType::new(self.db(), argument_ty),
10636+
));
1063010637
}
10638+
1063110639
if let Some(generic_context) = class.generic_context(self.db()) {
1063210640
return self.infer_explicit_class_specialization(
1063310641
subscript,
@@ -10764,6 +10772,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1076410772
}
1076510773
}
1076610774
}
10775+
Type::SpecialForm(SpecialFormType::Type) => {
10776+
// Similar to the branch above that handles `type[…]`, handle `typing.Type[…]`
10777+
let argument_ty = self.infer_type_expression(slice);
10778+
return Type::KnownInstance(KnownInstanceType::TypeGenericAlias(
10779+
InternedType::new(self.db(), argument_ty),
10780+
));
10781+
}
1076710782
_ => {}
1076810783
}
1076910784

crates/ty_python_semantic/src/types/infer/builder/type_expression.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
826826
self.infer_type_expression(slice);
827827
todo_type!("Generic specialization of types.UnionType")
828828
}
829-
KnownInstanceType::Literal(ty) => {
829+
KnownInstanceType::Literal(ty) | KnownInstanceType::TypeGenericAlias(ty) => {
830830
self.infer_type_expression(slice);
831831
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
832832
builder.into_diagnostic(format_args!(

0 commit comments

Comments
 (0)