diff --git a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md index 0a45e9e3c4..fd0610e1aa 100644 --- a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md @@ -66,6 +66,8 @@ IntOrAnnotated = int | Annotated[str, "meta"] AnnotatedOrInt = Annotated[str, "meta"] | int IntOrOptional = int | Optional[str] OptionalOrInt = Optional[str] | int +IntOrTypeOfStr = int | type[str] +TypeOfStrOrInt = type[str] | int reveal_type(IntOrStr) # revealed: types.UnionType reveal_type(IntOrStrOrBytes1) # revealed: types.UnionType @@ -97,6 +99,8 @@ reveal_type(IntOrAnnotated) # revealed: types.UnionType reveal_type(AnnotatedOrInt) # revealed: types.UnionType reveal_type(IntOrOptional) # revealed: types.UnionType reveal_type(OptionalOrInt) # revealed: types.UnionType +reveal_type(IntOrTypeOfStr) # revealed: types.UnionType +reveal_type(TypeOfStrOrInt) # revealed: types.UnionType def _( int_or_str: IntOrStr, @@ -129,6 +133,8 @@ def _( annotated_or_int: AnnotatedOrInt, int_or_optional: IntOrOptional, optional_or_int: OptionalOrInt, + int_or_type_of_str: IntOrTypeOfStr, + type_of_str_or_int: TypeOfStrOrInt, ): reveal_type(int_or_str) # revealed: int | str reveal_type(int_or_str_or_bytes1) # revealed: int | str | bytes @@ -160,6 +166,8 @@ def _( reveal_type(annotated_or_int) # revealed: str | int reveal_type(int_or_optional) # revealed: int | str | None reveal_type(optional_or_int) # revealed: str | None | int + reveal_type(int_or_type_of_str) # revealed: int | type[str] + reveal_type(type_of_str_or_int) # revealed: type[str] | int ``` If a type is unioned with itself in a value expression, the result is just that type. No @@ -599,6 +607,158 @@ def _( reveal_type(invalid) # revealed: str | Unknown ``` +## `type[…]` and `Type[…]` + +### `type[…]` + +We support implicit type aliases using `type[…]`: + +```py +from typing import Any, Union, Protocol, TypeVar, Generic + +T = TypeVar("T") + +class A: ... +class B: ... +class G(Generic[T]): ... + +class P(Protocol): + def method(self) -> None: ... + +SubclassOfA = type[A] +SubclassOfAny = type[Any] +SubclassOfAOrB1 = type[A | B] +SubclassOfAOrB2 = type[A] | type[B] +SubclassOfAOrB3 = Union[type[A], type[B]] +SubclassOfG = type[G] +SubclassOfGInt = type[G[int]] +SubclassOfP = type[P] + +reveal_type(SubclassOfA) # revealed: GenericAlias +reveal_type(SubclassOfAny) # revealed: GenericAlias +reveal_type(SubclassOfAOrB1) # revealed: GenericAlias +reveal_type(SubclassOfAOrB2) # revealed: types.UnionType +reveal_type(SubclassOfAOrB3) # revealed: types.UnionType +reveal_type(SubclassOfG) # revealed: GenericAlias +reveal_type(SubclassOfGInt) # revealed: GenericAlias +reveal_type(SubclassOfP) # revealed: GenericAlias + +def _( + subclass_of_a: SubclassOfA, + subclass_of_any: SubclassOfAny, + subclass_of_a_or_b1: SubclassOfAOrB1, + subclass_of_a_or_b2: SubclassOfAOrB2, + subclass_of_a_or_b3: SubclassOfAOrB3, + subclass_of_g: SubclassOfG, + subclass_of_g_int: SubclassOfGInt, + subclass_of_p: SubclassOfP, +): + reveal_type(subclass_of_a) # revealed: type[A] + reveal_type(subclass_of_a()) # revealed: A + + reveal_type(subclass_of_any) # revealed: type[Any] + reveal_type(subclass_of_any()) # revealed: Any + + reveal_type(subclass_of_a_or_b1) # revealed: type[A] | type[B] + reveal_type(subclass_of_a_or_b1()) # revealed: A | B + + reveal_type(subclass_of_a_or_b2) # revealed: type[A] | type[B] + reveal_type(subclass_of_a_or_b2()) # revealed: A | B + + reveal_type(subclass_of_a_or_b3) # revealed: type[A] | type[B] + reveal_type(subclass_of_a_or_b3()) # revealed: A | B + + reveal_type(subclass_of_g) # revealed: type[G[Unknown]] + reveal_type(subclass_of_g()) # revealed: G[Unknown] + + reveal_type(subclass_of_g_int) # revealed: type[G[int]] + reveal_type(subclass_of_g_int()) # revealed: G[int] + + reveal_type(subclass_of_p) # revealed: type[P] +``` + +Invalid uses result in diagnostics: + +```py +# error: [invalid-type-form] +InvalidSubclass = type[1] +``` + +### `Type[…]` + +The same also works for `typing.Type[…]`: + +```py +from typing import Any, Union, Protocol, TypeVar, Generic, Type + +T = TypeVar("T") + +class A: ... +class B: ... +class G(Generic[T]): ... + +class P(Protocol): + def method(self) -> None: ... + +SubclassOfA = Type[A] +SubclassOfAny = Type[Any] +SubclassOfAOrB1 = Type[A | B] +SubclassOfAOrB2 = Type[A] | Type[B] +SubclassOfAOrB3 = Union[Type[A], Type[B]] +SubclassOfG = Type[G] +SubclassOfGInt = Type[G[int]] +SubclassOfP = Type[P] + +reveal_type(SubclassOfA) # revealed: GenericAlias +reveal_type(SubclassOfAny) # revealed: GenericAlias +reveal_type(SubclassOfAOrB1) # revealed: GenericAlias +reveal_type(SubclassOfAOrB2) # revealed: types.UnionType +reveal_type(SubclassOfAOrB3) # revealed: types.UnionType +reveal_type(SubclassOfG) # revealed: GenericAlias +reveal_type(SubclassOfGInt) # revealed: GenericAlias +reveal_type(SubclassOfP) # revealed: GenericAlias + +def _( + subclass_of_a: SubclassOfA, + subclass_of_any: SubclassOfAny, + subclass_of_a_or_b1: SubclassOfAOrB1, + subclass_of_a_or_b2: SubclassOfAOrB2, + subclass_of_a_or_b3: SubclassOfAOrB3, + subclass_of_g: SubclassOfG, + subclass_of_g_int: SubclassOfGInt, + subclass_of_p: SubclassOfP, +): + reveal_type(subclass_of_a) # revealed: type[A] + reveal_type(subclass_of_a()) # revealed: A + + reveal_type(subclass_of_any) # revealed: type[Any] + reveal_type(subclass_of_any()) # revealed: Any + + reveal_type(subclass_of_a_or_b1) # revealed: type[A] | type[B] + reveal_type(subclass_of_a_or_b1()) # revealed: A | B + + reveal_type(subclass_of_a_or_b2) # revealed: type[A] | type[B] + reveal_type(subclass_of_a_or_b2()) # revealed: A | B + + reveal_type(subclass_of_a_or_b3) # revealed: type[A] | type[B] + reveal_type(subclass_of_a_or_b3()) # revealed: A | B + + reveal_type(subclass_of_g) # revealed: type[G[Unknown]] + reveal_type(subclass_of_g()) # revealed: G[Unknown] + + reveal_type(subclass_of_g_int) # revealed: type[G[int]] + reveal_type(subclass_of_g_int()) # revealed: G[int] + + reveal_type(subclass_of_p) # revealed: type[P] +``` + +Invalid uses result in diagnostics: + +```py +# error: [invalid-type-form] +InvalidSubclass = Type[1] +``` + ## Stringified annotations? From the [typing spec on type aliases](https://typing.python.org/en/latest/spec/aliases.html): @@ -633,15 +793,18 @@ from typing import Union ListOfInts = list["int"] StrOrStyle = Union[str, "Style"] +SubclassOfStyle = type["Style"] class Style: ... def _( list_of_ints: ListOfInts, str_or_style: StrOrStyle, + subclass_of_style: SubclassOfStyle, ): reveal_type(list_of_ints) # revealed: list[int] reveal_type(str_or_style) # revealed: str | Style + reveal_type(subclass_of_style) # revealed: type[Style] ``` ## Recursive diff --git a/crates/ty_python_semantic/resources/mdtest/type_of/basic.md b/crates/ty_python_semantic/resources/mdtest/type_of/basic.md index 80223c37a4..3a167d528b 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_of/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/type_of/basic.md @@ -149,8 +149,7 @@ from ty_extensions import reveal_mro class Foo(type[int]): ... -# TODO: should be `tuple[, , ] -reveal_mro(Foo) # revealed: (, @Todo(GenericAlias instance), ) +reveal_mro(Foo) # revealed: (, , ) ``` ## Display of generic `type[]` types diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 194bc6db90..b7f4327ad6 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -6607,6 +6607,17 @@ impl<'db> Type<'db> { .inner(db) .in_type_expression(db, scope_id, typevar_binding_context)?) } + KnownInstanceType::TypeGenericAlias(ty) => { + // When `type[…]` appears in a value position (e.g. in an implicit type alias), + // we infer its argument as a type expression. This ensures that we can emit + // diagnostics for invalid type expressions, and more importantly, that we can + // make use of stringified annotations. The drawback is that we need to turn + // instances back into the corresponding subclass-of types here. This process + // (`int` -> instance of `int` -> subclass of `int`) can be lossy, but it is + // okay for all valid arguments to `type[…]`. + + Ok(ty.inner(db).to_meta_type(db)) + } }, Type::SpecialForm(special_form) => match special_form { @@ -7847,6 +7858,9 @@ pub enum KnownInstanceType<'db> { /// A single instance of `typing.Annotated` Annotated(InternedType<'db>), + /// An instance of `typing.GenericAlias` representing a `type[...]` expression. + TypeGenericAlias(InternedType<'db>), + /// An identity callable created with `typing.NewType(name, base)`, which behaves like a /// subtype of `base` in type expressions. See the `struct NewType` payload for an example. NewType(NewType<'db>), @@ -7881,7 +7895,9 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( visitor.visit_type(db, *element); } } - KnownInstanceType::Literal(ty) | KnownInstanceType::Annotated(ty) => { + KnownInstanceType::Literal(ty) + | KnownInstanceType::Annotated(ty) + | KnownInstanceType::TypeGenericAlias(ty) => { visitor.visit_type(db, ty.inner(db)); } KnownInstanceType::NewType(newtype) => { @@ -7928,6 +7944,7 @@ impl<'db> KnownInstanceType<'db> { Self::UnionType(list) => Self::UnionType(list.normalized_impl(db, visitor)), Self::Literal(ty) => Self::Literal(ty.normalized_impl(db, visitor)), Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)), + Self::TypeGenericAlias(ty) => Self::TypeGenericAlias(ty.normalized_impl(db, visitor)), Self::NewType(newtype) => Self::NewType( newtype .map_base_class_type(db, |class_type| class_type.normalized_impl(db, visitor)), @@ -7950,8 +7967,9 @@ impl<'db> KnownInstanceType<'db> { Self::Field(_) => KnownClass::Field, Self::ConstraintSet(_) => KnownClass::ConstraintSet, Self::UnionType(_) => KnownClass::UnionType, - Self::Literal(_) => KnownClass::GenericAlias, - Self::Annotated(_) => KnownClass::GenericAlias, + Self::Literal(_) | Self::Annotated(_) | Self::TypeGenericAlias(_) => { + KnownClass::GenericAlias + } Self::NewType(_) => KnownClass::NewType, } } @@ -8037,6 +8055,7 @@ impl<'db> KnownInstanceType<'db> { KnownInstanceType::Annotated(_) => { f.write_str("") } + KnownInstanceType::TypeGenericAlias(_) => f.write_str("GenericAlias"), KnownInstanceType::NewType(declaration) => { write!(f, "", declaration.name(self.db)) } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 9cc09acc0f..83f7cf423c 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -180,6 +180,9 @@ impl<'db> ClassBase<'db> { // wrappers are just identity callables at runtime, so this sort of inheritance // doesn't work and isn't allowed. | KnownInstanceType::NewType(_) => None, + KnownInstanceType::TypeGenericAlias(_) => { + Self::try_from_type(db, KnownClass::Type.to_class_literal(db), subclass) + } KnownInstanceType::Annotated(ty) => Self::try_from_type(db, ty.inner(db), subclass), }, diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 0f473b3e81..34c333df0e 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -9457,7 +9457,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::KnownInstance( KnownInstanceType::UnionType(_) | KnownInstanceType::Literal(_) - | KnownInstanceType::Annotated(_), + | KnownInstanceType::Annotated(_) + | KnownInstanceType::TypeGenericAlias(_), ), Type::ClassLiteral(..) | Type::SubclassOf(..) @@ -9466,7 +9467,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::KnownInstance( KnownInstanceType::UnionType(_) | KnownInstanceType::Literal(_) - | KnownInstanceType::Annotated(_), + | KnownInstanceType::Annotated(_) + | KnownInstanceType::TypeGenericAlias(_), ), ast::Operator::BitOr, ) if pep_604_unions_allowed() => { @@ -10627,7 +10629,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // special cases, too. if class.is_tuple(self.db()) { return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice)); + } else if class.is_known(self.db(), KnownClass::Type) { + let argument_ty = self.infer_type_expression(slice); + return Type::KnownInstance(KnownInstanceType::TypeGenericAlias( + InternedType::new(self.db(), argument_ty), + )); } + if let Some(generic_context) = class.generic_context(self.db()) { return self.infer_explicit_class_specialization( subscript, @@ -10764,6 +10772,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } } + Type::SpecialForm(SpecialFormType::Type) => { + // Similar to the branch above that handles `type[…]`, handle `typing.Type[…]` + let argument_ty = self.infer_type_expression(slice); + return Type::KnownInstance(KnownInstanceType::TypeGenericAlias( + InternedType::new(self.db(), argument_ty), + )); + } _ => {} } diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index 40469fdc9c..20c5362a0b 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -826,7 +826,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.infer_type_expression(slice); todo_type!("Generic specialization of types.UnionType") } - KnownInstanceType::Literal(ty) => { + KnownInstanceType::Literal(ty) | KnownInstanceType::TypeGenericAlias(ty) => { self.infer_type_expression(slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!(