diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md index e6009f2b5b..2190fd8796 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md @@ -29,22 +29,25 @@ The dynamic type at the top-level is replaced with `object`. ```py from typing import Any, Callable -from ty_extensions import Unknown, top_materialization +from ty_extensions import Unknown, Top -reveal_type(top_materialization(Any)) # revealed: object -reveal_type(top_materialization(Unknown)) # revealed: object +def _(top_any: Top[Any], top_unknown: Top[Unknown]): + reveal_type(top_any) # revealed: object + reveal_type(top_unknown) # revealed: object ``` The contravariant position is replaced with `Never`. ```py -reveal_type(top_materialization(Callable[[Any], None])) # revealed: (Never, /) -> None +def _(top_callable: Top[Callable[[Any], None]]): + reveal_type(top_callable) # revealed: (Never, /) -> None ``` The invariant position is replaced with an unresolved type variable. ```py -reveal_type(top_materialization(list[Any])) # revealed: list[T_all] +def _(top_list: Top[list[Any]]): + reveal_type(top_list) # revealed: list[T_all] ``` ### Bottom materialization @@ -53,24 +56,26 @@ The dynamic type at the top-level is replaced with `Never`. ```py from typing import Any, Callable -from ty_extensions import Unknown, bottom_materialization +from ty_extensions import Unknown, Bottom -reveal_type(bottom_materialization(Any)) # revealed: Never -reveal_type(bottom_materialization(Unknown)) # revealed: Never +def _(bottom_any: Bottom[Any], bottom_unknown: Bottom[Unknown]): + reveal_type(bottom_any) # revealed: Never + reveal_type(bottom_unknown) # revealed: Never ``` The contravariant position is replaced with `object`. ```py -# revealed: (object, object, /) -> None -reveal_type(bottom_materialization(Callable[[Any, Unknown], None])) +def _(bottom_callable: Bottom[Callable[[Any, Unknown], None]]): + reveal_type(bottom_callable) # revealed: (object, object, /) -> None ``` The invariant position is replaced in the same way as the top materialization, with an unresolved type variable. ```py -reveal_type(bottom_materialization(list[Any])) # revealed: list[T_all] +def _(bottom_list: Bottom[list[Any]]): + reveal_type(bottom_list) # revealed: list[T_all] ``` ## Fully static types @@ -79,30 +84,30 @@ The top / bottom (and only) materialization of any fully static type is just its ```py from typing import Any, Literal -from ty_extensions import TypeOf, bottom_materialization, top_materialization +from ty_extensions import TypeOf, Bottom, Top, is_equivalent_to, static_assert from enum import Enum class Answer(Enum): NO = 0 YES = 1 -reveal_type(top_materialization(int)) # revealed: int -reveal_type(bottom_materialization(int)) # revealed: int +static_assert(is_equivalent_to(Top[int], int)) +static_assert(is_equivalent_to(Bottom[int], int)) -reveal_type(top_materialization(Literal[1])) # revealed: Literal[1] -reveal_type(bottom_materialization(Literal[1])) # revealed: Literal[1] +static_assert(is_equivalent_to(Top[Literal[1]], Literal[1])) +static_assert(is_equivalent_to(Bottom[Literal[1]], Literal[1])) -reveal_type(top_materialization(Literal[True])) # revealed: Literal[True] -reveal_type(bottom_materialization(Literal[True])) # revealed: Literal[True] +static_assert(is_equivalent_to(Top[Literal[True]], Literal[True])) +static_assert(is_equivalent_to(Bottom[Literal[True]], Literal[True])) -reveal_type(top_materialization(Literal["abc"])) # revealed: Literal["abc"] -reveal_type(bottom_materialization(Literal["abc"])) # revealed: Literal["abc"] +static_assert(is_equivalent_to(Top[Literal["abc"]], Literal["abc"])) +static_assert(is_equivalent_to(Bottom[Literal["abc"]], Literal["abc"])) -reveal_type(top_materialization(Literal[Answer.YES])) # revealed: Literal[Answer.YES] -reveal_type(bottom_materialization(Literal[Answer.YES])) # revealed: Literal[Answer.YES] +static_assert(is_equivalent_to(Top[Literal[Answer.YES]], Literal[Answer.YES])) +static_assert(is_equivalent_to(Bottom[Literal[Answer.YES]], Literal[Answer.YES])) -reveal_type(top_materialization(int | str)) # revealed: int | str -reveal_type(bottom_materialization(int | str)) # revealed: int | str +static_assert(is_equivalent_to(Top[int | str], int | str)) +static_assert(is_equivalent_to(Bottom[int | str], int | str)) ``` We currently treat function literals as fully static types, so they remain unchanged even though the @@ -114,11 +119,17 @@ def function(x: Any) -> None: ... class A: def method(self, x: Any) -> None: ... -reveal_type(top_materialization(TypeOf[function])) # revealed: def function(x: Any) -> None -reveal_type(bottom_materialization(TypeOf[function])) # revealed: def function(x: Any) -> None +def _( + top_func: Top[TypeOf[function]], + bottom_func: Bottom[TypeOf[function]], + top_meth: Top[TypeOf[A().method]], + bottom_meth: Bottom[TypeOf[A().method]], +): + reveal_type(top_func) # revealed: def function(x: Any) -> None + reveal_type(bottom_func) # revealed: def function(x: Any) -> None -reveal_type(top_materialization(TypeOf[A().method])) # revealed: bound method A.method(x: Any) -> None -reveal_type(bottom_materialization(TypeOf[A().method])) # revealed: bound method A.method(x: Any) -> None + reveal_type(top_meth) # revealed: bound method A.method(x: Any) -> None + reveal_type(bottom_meth) # revealed: bound method A.method(x: Any) -> None ``` ## Callable @@ -126,27 +137,30 @@ reveal_type(bottom_materialization(TypeOf[A().method])) # revealed: bound metho For a callable, the parameter types are in a contravariant position, and the return type is in a covariant position. +```toml +[environment] +python-version = "3.12" +``` + ```py from typing import Any, Callable -from ty_extensions import TypeOf, Unknown, bottom_materialization, top_materialization +from ty_extensions import TypeOf, Unknown, Bottom, Top -def _(callable: Callable[[Any, Unknown], Any]) -> None: - # revealed: (Never, Never, /) -> object - reveal_type(top_materialization(TypeOf[callable])) +type C1 = Callable[[Any, Unknown], Any] - # revealed: (object, object, /) -> Never - reveal_type(bottom_materialization(TypeOf[callable])) +def _(top: Top[C1], bottom: Bottom[C1]) -> None: + reveal_type(top) # revealed: (Never, Never, /) -> object + reveal_type(bottom) # revealed: (object, object, /) -> Never ``` The parameter types in a callable inherits the contravariant position. ```py -def _(callable: Callable[[int, tuple[int | Any]], tuple[Any]]) -> None: - # revealed: (int, tuple[int], /) -> tuple[object] - reveal_type(top_materialization(TypeOf[callable])) +type C2 = Callable[[int, tuple[int | Any]], tuple[Any]] - # revealed: (int, tuple[object], /) -> Never - reveal_type(bottom_materialization(TypeOf[callable])) +def _(top: Top[C2], bottom: Bottom[C2]) -> None: + reveal_type(top) # revealed: (int, tuple[int], /) -> tuple[object] + reveal_type(bottom) # revealed: (int, tuple[object], /) -> Never ``` But, if the callable itself is in a contravariant position, then the variance is flipped i.e., if @@ -154,30 +168,37 @@ the outer variance is covariant, it's flipped to contravariant, and if it's cont flipped to covariant, invariant remains invariant. ```py -def _(callable: Callable[[Any, Callable[[Unknown], Any]], Callable[[Any, int], Any]]) -> None: +type C3 = Callable[[Any, Callable[[Unknown], Any]], Callable[[Any, int], Any]] + +def _(top: Top[C3], bottom: Bottom[C3]) -> None: # revealed: (Never, (object, /) -> Never, /) -> (Never, int, /) -> object - reveal_type(top_materialization(TypeOf[callable])) + reveal_type(top) # revealed: (object, (Never, /) -> object, /) -> (object, int, /) -> Never - reveal_type(bottom_materialization(TypeOf[callable])) + reveal_type(bottom) ``` ## Tuple All positions in a tuple are covariant. +```toml +[environment] +python-version = "3.12" +``` + ```py -from typing import Any -from ty_extensions import Unknown, bottom_materialization, top_materialization +from typing import Any, Never +from ty_extensions import Unknown, Bottom, Top, is_equivalent_to, static_assert -reveal_type(top_materialization(tuple[Any, int])) # revealed: tuple[object, int] -reveal_type(bottom_materialization(tuple[Any, int])) # revealed: Never +static_assert(is_equivalent_to(Top[tuple[Any, int]], tuple[object, int])) +static_assert(is_equivalent_to(Bottom[tuple[Any, int]], Never)) -reveal_type(top_materialization(tuple[Unknown, int])) # revealed: tuple[object, int] -reveal_type(bottom_materialization(tuple[Unknown, int])) # revealed: Never +static_assert(is_equivalent_to(Top[tuple[Unknown, int]], tuple[object, int])) +static_assert(is_equivalent_to(Bottom[tuple[Unknown, int]], Never)) -reveal_type(top_materialization(tuple[Any, int, Unknown])) # revealed: tuple[object, int, object] -reveal_type(bottom_materialization(tuple[Any, int, Unknown])) # revealed: Never +static_assert(is_equivalent_to(Top[tuple[Any, int, Unknown]], tuple[object, int, object])) +static_assert(is_equivalent_to(Bottom[tuple[Any, int, Unknown]], Never)) ``` Except for when the tuple itself is in a contravariant position, then all positions in the tuple @@ -187,43 +208,59 @@ inherit the contravariant position. from typing import Callable from ty_extensions import TypeOf -def _(callable: Callable[[tuple[Any, int], tuple[str, Unknown]], None]) -> None: - # revealed: (Never, Never, /) -> None - reveal_type(top_materialization(TypeOf[callable])) +type C = Callable[[tuple[Any, int], tuple[str, Unknown]], None] - # revealed: (tuple[object, int], tuple[str, object], /) -> None - reveal_type(bottom_materialization(TypeOf[callable])) +def _(top: Top[C], bottom: Bottom[C]) -> None: + reveal_type(top) # revealed: (Never, Never, /) -> None + reveal_type(bottom) # revealed: (tuple[object, int], tuple[str, object], /) -> None ``` And, similarly for an invariant position. ```py -reveal_type(top_materialization(list[tuple[Any, int]])) # revealed: list[tuple[T_all, int]] -reveal_type(bottom_materialization(list[tuple[Any, int]])) # revealed: list[tuple[T_all, int]] +type LTAnyInt = list[tuple[Any, int]] +type LTStrUnknown = list[tuple[str, Unknown]] +type LTAnyIntUnknown = list[tuple[Any, int, Unknown]] -reveal_type(top_materialization(list[tuple[str, Unknown]])) # revealed: list[tuple[str, T_all]] -reveal_type(bottom_materialization(list[tuple[str, Unknown]])) # revealed: list[tuple[str, T_all]] +def _( + top_ai: Top[LTAnyInt], + bottom_ai: Bottom[LTAnyInt], + top_su: Top[LTStrUnknown], + bottom_su: Bottom[LTStrUnknown], + top_aiu: Top[LTAnyIntUnknown], + bottom_aiu: Bottom[LTAnyIntUnknown], +): + reveal_type(top_ai) # revealed: list[tuple[T_all, int]] + reveal_type(bottom_ai) # revealed: list[tuple[T_all, int]] -reveal_type(top_materialization(list[tuple[Any, int, Unknown]])) # revealed: list[tuple[T_all, int, T_all]] -reveal_type(bottom_materialization(list[tuple[Any, int, Unknown]])) # revealed: list[tuple[T_all, int, T_all]] + reveal_type(top_su) # revealed: list[tuple[str, T_all]] + reveal_type(bottom_su) # revealed: list[tuple[str, T_all]] + + reveal_type(top_aiu) # revealed: list[tuple[T_all, int, T_all]] + reveal_type(bottom_aiu) # revealed: list[tuple[T_all, int, T_all]] ``` ## Union All positions in a union are covariant. +```toml +[environment] +python-version = "3.12" +``` + ```py from typing import Any -from ty_extensions import Unknown, bottom_materialization, top_materialization +from ty_extensions import Unknown, Bottom, Top, static_assert, is_equivalent_to -reveal_type(top_materialization(Any | int)) # revealed: object -reveal_type(bottom_materialization(Any | int)) # revealed: int +static_assert(is_equivalent_to(Top[Any | int], object)) +static_assert(is_equivalent_to(Bottom[Any | int], int)) -reveal_type(top_materialization(Unknown | int)) # revealed: object -reveal_type(bottom_materialization(Unknown | int)) # revealed: int +static_assert(is_equivalent_to(Top[Unknown | int], object)) +static_assert(is_equivalent_to(Bottom[Unknown | int], int)) -reveal_type(top_materialization(int | str | Any)) # revealed: object -reveal_type(bottom_materialization(int | str | Any)) # revealed: int | str +static_assert(is_equivalent_to(Top[int | str | Any], object)) +static_assert(is_equivalent_to(Bottom[int | str | Any], int | str)) ``` Except for when the union itself is in a contravariant position, then all positions in the union @@ -234,24 +271,29 @@ from typing import Callable from ty_extensions import TypeOf def _(callable: Callable[[Any | int, str | Unknown], None]) -> None: - # revealed: (int, str, /) -> None - reveal_type(top_materialization(TypeOf[callable])) - - # revealed: (object, object, /) -> None - reveal_type(bottom_materialization(TypeOf[callable])) + static_assert(is_equivalent_to(Top[TypeOf[callable]], Callable[[int, str], None])) + static_assert(is_equivalent_to(Bottom[TypeOf[callable]], Callable[[object, object], None])) ``` And, similarly for an invariant position. ```py -reveal_type(top_materialization(list[Any | int])) # revealed: list[T_all | int] -reveal_type(bottom_materialization(list[Any | int])) # revealed: list[T_all | int] +def _( + top_ai: Top[list[Any | int]], + bottom_ai: Bottom[list[Any | int]], + top_su: Top[list[str | Unknown]], + bottom_su: Bottom[list[str | Unknown]], + top_aiu: Top[list[Any | int | Unknown]], + bottom_aiu: Bottom[list[Any | int | Unknown]], +): + reveal_type(top_ai) # revealed: list[T_all | int] + reveal_type(bottom_ai) # revealed: list[T_all | int] -reveal_type(top_materialization(list[str | Unknown])) # revealed: list[str | T_all] -reveal_type(bottom_materialization(list[str | Unknown])) # revealed: list[str | T_all] + reveal_type(top_su) # revealed: list[str | T_all] + reveal_type(bottom_su) # revealed: list[str | T_all] -reveal_type(top_materialization(list[Any | int | Unknown])) # revealed: list[T_all | int] -reveal_type(bottom_materialization(list[Any | int | Unknown])) # revealed: list[T_all | int] + reveal_type(top_aiu) # revealed: list[T_all | int] + reveal_type(bottom_aiu) # revealed: list[T_all | int] ``` ## Intersection @@ -260,24 +302,26 @@ All positions in an intersection are covariant. ```py from typing import Any -from ty_extensions import Intersection, Unknown, bottom_materialization, top_materialization +from typing_extensions import Never +from ty_extensions import Intersection, Unknown, Bottom, Top, static_assert, is_equivalent_to -reveal_type(top_materialization(Intersection[Any, int])) # revealed: int -reveal_type(bottom_materialization(Intersection[Any, int])) # revealed: Never +static_assert(is_equivalent_to(Top[Intersection[Any, int]], int)) +static_assert(is_equivalent_to(Bottom[Intersection[Any, int]], Never)) # Here, the top materialization of `Any | int` is `object` and the intersection of it with tuple -# revealed: tuple[str, object] -reveal_type(top_materialization(Intersection[Any | int, tuple[str, Unknown]])) -# revealed: Never -reveal_type(bottom_materialization(Intersection[Any | int, tuple[str, Unknown]])) +static_assert(is_equivalent_to(Top[Intersection[Any | int, tuple[str, Unknown]]], tuple[str, object])) +static_assert(is_equivalent_to(Bottom[Intersection[Any | int, tuple[str, Unknown]]], Never)) class Foo: ... -# revealed: Foo & tuple[str] -reveal_type(bottom_materialization(Intersection[Any | Foo, tuple[str]])) +static_assert(is_equivalent_to(Bottom[Intersection[Any | Foo, tuple[str]]], Intersection[Foo, tuple[str]])) -reveal_type(top_materialization(Intersection[list[Any], list[int]])) # revealed: list[T_all] & list[int] -reveal_type(bottom_materialization(Intersection[list[Any], list[int]])) # revealed: list[T_all] & list[int] +def _( + top: Top[Intersection[list[Any], list[int]]], + bottom: Bottom[Intersection[list[Any], list[int]]], +): + reveal_type(top) # revealed: list[T_all] & list[int] + reveal_type(bottom) # revealed: list[T_all] & list[int] ``` ## Negation (via `Not`) @@ -286,38 +330,44 @@ All positions in a negation are contravariant. ```py from typing import Any -from ty_extensions import Not, Unknown, bottom_materialization, top_materialization +from typing_extensions import Never +from ty_extensions import Not, Unknown, Bottom, Top, static_assert, is_equivalent_to # ~Any is still Any, so the top materialization is object -reveal_type(top_materialization(Not[Any])) # revealed: object -reveal_type(bottom_materialization(Not[Any])) # revealed: Never +static_assert(is_equivalent_to(Top[Not[Any]], object)) +static_assert(is_equivalent_to(Bottom[Not[Any]], Never)) # tuple[Any, int] is in a contravariant position, so the # top materialization is Never and the negation of it -# revealed: object -reveal_type(top_materialization(Not[tuple[Any, int]])) -# revealed: ~tuple[object, int] -reveal_type(bottom_materialization(Not[tuple[Any, int]])) +static_assert(is_equivalent_to(Top[Not[tuple[Any, int]]], object)) +static_assert(is_equivalent_to(Bottom[Not[tuple[Any, int]]], Not[tuple[object, int]])) ``` ## `type` +```toml +[environment] +python-version = "3.12" +``` + ```py from typing import Any -from ty_extensions import Unknown, bottom_materialization, top_materialization +from typing_extensions import Never +from ty_extensions import Unknown, Bottom, Top, static_assert, is_equivalent_to -reveal_type(top_materialization(type[Any])) # revealed: type -reveal_type(bottom_materialization(type[Any])) # revealed: Never +static_assert(is_equivalent_to(Top[type[Any]], type)) +static_assert(is_equivalent_to(Bottom[type[Any]], Never)) -reveal_type(top_materialization(type[Unknown])) # revealed: type -reveal_type(bottom_materialization(type[Unknown])) # revealed: Never +static_assert(is_equivalent_to(Top[type[Unknown]], type)) +static_assert(is_equivalent_to(Bottom[type[Unknown]], Never)) -reveal_type(top_materialization(type[int | Any])) # revealed: type -reveal_type(bottom_materialization(type[int | Any])) # revealed: type[int] +static_assert(is_equivalent_to(Top[type[int | Any]], type)) +static_assert(is_equivalent_to(Bottom[type[int | Any]], type[int])) # Here, `T` has an upper bound of `type` -reveal_type(top_materialization(list[type[Any]])) # revealed: list[T_all] -reveal_type(bottom_materialization(list[type[Any]])) # revealed: list[T_all] +def _(top: Top[list[type[Any]]], bottom: Bottom[list[type[Any]]]): + reveal_type(top) # revealed: list[T_all] + reveal_type(bottom) # revealed: list[T_all] ``` ## Type variables @@ -329,26 +379,19 @@ python-version = "3.12" ```py from typing import Any, Never, TypeVar -from ty_extensions import ( - TypeOf, - Unknown, - bottom_materialization, - top_materialization, - static_assert, - is_subtype_of, -) +from ty_extensions import Unknown, Bottom, Top, static_assert, is_subtype_of def bounded_by_gradual[T: Any](t: T) -> None: # Top materialization of `T: Any` is `T: object` # Bottom materialization of `T: Any` is `T: Never` - static_assert(is_subtype_of(TypeOf[bottom_materialization(T)], Never)) + static_assert(is_subtype_of(Bottom[T], Never)) def constrained_by_gradual[T: (int, Any)](t: T) -> None: # Top materialization of `T: (int, Any)` is `T: (int, object)` # Bottom materialization of `T: (int, Any)` is `T: (int, Never)` - static_assert(is_subtype_of(TypeOf[bottom_materialization(T)], int)) + static_assert(is_subtype_of(Bottom[T], int)) ``` ## Generics @@ -361,9 +404,14 @@ variable itself. - If the type variable is contravariant, the materialization happens as per the surrounding variance, but the variance is flipped +```toml +[environment] +python-version = "3.12" +``` + ```py -from typing import Any, Generic, TypeVar -from ty_extensions import bottom_materialization, top_materialization +from typing import Any, Generic, TypeVar, Never +from ty_extensions import Bottom, Top, static_assert, is_equivalent_to T = TypeVar("T") T_co = TypeVar("T_co", covariant=True) @@ -378,14 +426,15 @@ class GenericCovariant(Generic[T_co]): class GenericContravariant(Generic[T_contra]): pass -reveal_type(top_materialization(GenericInvariant[Any])) # revealed: GenericInvariant[T_all] -reveal_type(bottom_materialization(GenericInvariant[Any])) # revealed: GenericInvariant[T_all] +def _(top: Top[GenericInvariant[Any]], bottom: Bottom[GenericInvariant[Any]]): + reveal_type(top) # revealed: GenericInvariant[T_all] + reveal_type(bottom) # revealed: GenericInvariant[T_all] -reveal_type(top_materialization(GenericCovariant[Any])) # revealed: GenericCovariant[object] -reveal_type(bottom_materialization(GenericCovariant[Any])) # revealed: GenericCovariant[Never] +static_assert(is_equivalent_to(Top[GenericCovariant[Any]], GenericCovariant[object])) +static_assert(is_equivalent_to(Bottom[GenericCovariant[Any]], GenericCovariant[Never])) -reveal_type(top_materialization(GenericContravariant[Any])) # revealed: GenericContravariant[Never] -reveal_type(bottom_materialization(GenericContravariant[Any])) # revealed: GenericContravariant[object] +static_assert(is_equivalent_to(Top[GenericContravariant[Any]], GenericContravariant[Never])) +static_assert(is_equivalent_to(Bottom[GenericContravariant[Any]], GenericContravariant[object])) ``` Parameters in callable are contravariant, so the variance should be flipped: @@ -394,24 +443,52 @@ Parameters in callable are contravariant, so the variance should be flipped: from typing import Callable from ty_extensions import TypeOf -def invariant(callable: Callable[[GenericInvariant[Any]], None]) -> None: - # revealed: (GenericInvariant[T_all], /) -> None - reveal_type(top_materialization(TypeOf[callable])) +type InvariantCallable = Callable[[GenericInvariant[Any]], None] +type CovariantCallable = Callable[[GenericCovariant[Any]], None] +type ContravariantCallable = Callable[[GenericContravariant[Any]], None] - # revealed: (GenericInvariant[T_all], /) -> None - reveal_type(bottom_materialization(TypeOf[callable])) +def invariant(top: Top[InvariantCallable], bottom: Bottom[InvariantCallable]) -> None: + reveal_type(top) # revealed: (GenericInvariant[T_all], /) -> None + reveal_type(bottom) # revealed: (GenericInvariant[T_all], /) -> None -def covariant(callable: Callable[[GenericCovariant[Any]], None]) -> None: - # revealed: (GenericCovariant[Never], /) -> None - reveal_type(top_materialization(TypeOf[callable])) +def covariant(top: Top[CovariantCallable], bottom: Bottom[CovariantCallable]) -> None: + reveal_type(top) # revealed: (GenericCovariant[Never], /) -> None + reveal_type(bottom) # revealed: (GenericCovariant[object], /) -> None - # revealed: (GenericCovariant[object], /) -> None - reveal_type(bottom_materialization(TypeOf[callable])) - -def contravariant(callable: Callable[[GenericContravariant[Any]], None]) -> None: - # revealed: (GenericContravariant[object], /) -> None - reveal_type(top_materialization(TypeOf[callable])) - - # revealed: (GenericContravariant[Never], /) -> None - reveal_type(bottom_materialization(TypeOf[callable])) +def contravariant(top: Top[ContravariantCallable], bottom: Bottom[ContravariantCallable]) -> None: + reveal_type(top) # revealed: (GenericContravariant[object], /) -> None + reveal_type(bottom) # revealed: (GenericContravariant[Never], /) -> None +``` + +## Invalid use + +`Top[]` and `Bottom[]` are special forms that take a single argument. + +It is invalid to use them without a type argument. + +```py +from ty_extensions import Bottom, Top + +def _( + just_top: Top, # error: [invalid-type-form] + just_bottom: Bottom, # error: [invalid-type-form] +): ... +``` + +It is also invalid to use multiple arguments: + +```py +def _( + top_two: Top[int, str], # error: [invalid-type-form] + bottom_two: Bottom[int, str], # error: [invalid-type-form] +): ... +``` + +The argument must be a type expression: + +```py +def _( + top_1: Top[1], # error: [invalid-type-form] + bottom_1: Bottom[1], # error: [invalid-type-form] +): ... ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index fe88b3fa7d..b1fb7dddc6 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -4143,21 +4143,6 @@ impl<'db> Type<'db> { .into() } - Some(KnownFunction::TopMaterialization | KnownFunction::BottomMaterialization) => { - Binding::single( - self, - Signature::new( - Parameters::new([Parameter::positional_only(Some(Name::new_static( - "type", - ))) - .type_form() - .with_annotated_type(Type::any())]), - Some(Type::any()), - ), - ) - .into() - } - Some(KnownFunction::AssertType) => Binding::single( self, Signature::new( @@ -5741,6 +5726,8 @@ impl<'db> Type<'db> { SpecialFormType::Optional | SpecialFormType::Not + | SpecialFormType::Top + | SpecialFormType::Bottom | SpecialFormType::TypeOf | SpecialFormType::TypeIs | SpecialFormType::TypeGuard diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 8d728840d9..d2d46a2baa 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -726,18 +726,6 @@ impl<'db> Bindings<'db> { } } - Some(KnownFunction::TopMaterialization) => { - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(ty.top_materialization(db)); - } - } - - Some(KnownFunction::BottomMaterialization) => { - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(ty.bottom_materialization(db)); - } - } - Some(KnownFunction::Len) => { if let [Some(first_arg)] = overload.parameter_types() { if let Some(len_ty) = first_arg.len(db) { diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 2d05ca04ce..eeb83a3cbb 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -192,6 +192,8 @@ impl<'db> ClassBase<'db> { | SpecialFormType::ReadOnly | SpecialFormType::Optional | SpecialFormType::Not + | SpecialFormType::Top + | SpecialFormType::Bottom | SpecialFormType::Intersection | SpecialFormType::TypeOf | SpecialFormType::CallableTypeOf diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index dde034efc4..8f71854fd8 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1168,10 +1168,6 @@ pub enum KnownFunction { AllMembers, /// `ty_extensions.has_member` HasMember, - /// `ty_extensions.top_materialization` - TopMaterialization, - /// `ty_extensions.bottom_materialization` - BottomMaterialization, /// `ty_extensions.reveal_protocol_interface` RevealProtocolInterface, } @@ -1232,8 +1228,6 @@ impl KnownFunction { | Self::IsSingleValued | Self::IsSingleton | Self::IsSubtypeOf - | Self::TopMaterialization - | Self::BottomMaterialization | Self::GenericContext | Self::DunderAllNames | Self::EnumMembers @@ -1569,8 +1563,6 @@ pub(crate) mod tests { | KnownFunction::IsSingleValued | KnownFunction::IsAssignableTo | KnownFunction::IsEquivalentTo - | KnownFunction::TopMaterialization - | KnownFunction::BottomMaterialization | KnownFunction::HasMember | KnownFunction::RevealProtocolInterface | KnownFunction::AllMembers => KnownModule::TyExtensions, diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 0cb21ae2f9..9b8b43103e 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -10599,6 +10599,54 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } ty } + SpecialFormType::Top => { + let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { + &*tuple.elts + } else { + std::slice::from_ref(arguments_slice) + }; + let num_arguments = arguments.len(); + let arg = if num_arguments == 1 { + self.infer_type_expression(&arguments[0]) + } else { + for argument in arguments { + self.infer_type_expression(argument); + } + report_invalid_argument_number_to_special_form( + &self.context, + subscript, + special_form, + num_arguments, + 1, + ); + Type::unknown() + }; + arg.top_materialization(db) + } + SpecialFormType::Bottom => { + let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { + &*tuple.elts + } else { + std::slice::from_ref(arguments_slice) + }; + let num_arguments = arguments.len(); + let arg = if num_arguments == 1 { + self.infer_type_expression(&arguments[0]) + } else { + for argument in arguments { + self.infer_type_expression(argument); + } + report_invalid_argument_number_to_special_form( + &self.context, + subscript, + special_form, + num_arguments, + 1, + ); + Type::unknown() + }; + arg.bottom_materialization(db) + } SpecialFormType::TypeOf => { let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { &*tuple.elts diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs index 3683283dfa..494e7331ce 100644 --- a/crates/ty_python_semantic/src/types/special_form.rs +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -77,6 +77,10 @@ pub enum SpecialFormType { TypeOf, /// The symbol `ty_extensions.CallableTypeOf` CallableTypeOf, + /// The symbol `ty_extensions.Top` + Top, + /// The symbol `ty_extensions.Bottom` + Bottom, /// The symbol `typing.Callable` /// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`) Callable, @@ -151,6 +155,8 @@ impl SpecialFormType { | Self::TypeIs | Self::TypeOf | Self::Not + | Self::Top + | Self::Bottom | Self::Intersection | Self::CallableTypeOf | Self::Protocol // actually `_ProtocolMeta` at runtime but this is what typeshed says @@ -247,6 +253,8 @@ impl SpecialFormType { | Self::AlwaysTruthy | Self::AlwaysFalsy | Self::Not + | Self::Top + | Self::Bottom | Self::Intersection | Self::TypeOf | Self::CallableTypeOf => module.is_ty_extensions(), @@ -291,6 +299,8 @@ impl SpecialFormType { | Self::AlwaysTruthy | Self::AlwaysFalsy | Self::Not + | Self::Top + | Self::Bottom | Self::Intersection | Self::TypeOf | Self::CallableTypeOf @@ -352,6 +362,8 @@ impl SpecialFormType { SpecialFormType::Intersection => "ty_extensions.Intersection", SpecialFormType::TypeOf => "ty_extensions.TypeOf", SpecialFormType::CallableTypeOf => "ty_extensions.CallableTypeOf", + SpecialFormType::Top => "ty_extensions.Top", + SpecialFormType::Bottom => "ty_extensions.Bottom", SpecialFormType::Protocol => "typing.Protocol", SpecialFormType::Generic => "typing.Generic", SpecialFormType::NamedTuple => "typing.NamedTuple", diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index 6968bcb75a..9252693402 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -24,6 +24,12 @@ Not: _SpecialForm Intersection: _SpecialForm TypeOf: _SpecialForm CallableTypeOf: _SpecialForm +# Top[T] evaluates to the top materialization of T, a type that is a supertype +# of every materialization of T. +Top: _SpecialForm +# Bottom[T] evaluates to the bottom materialization of T, a type that is a subtype +# of every materialization of T. +Bottom: _SpecialForm # ty treats annotations of `float` to mean `float | int`, and annotations of `complex` # to mean `complex | float | int`. This is to support a typing-system special case [1]. @@ -56,12 +62,6 @@ def dunder_all_names(module: Any) -> Any: ... # List all members of an enum. def enum_members[E: type[Enum]](enum: E) -> tuple[str, ...]: ... -# Returns the type that's an upper bound of materializing the given (gradual) type. -def top_materialization(type: Any) -> Any: ... - -# Returns the type that's a lower bound of materializing the given (gradual) type. -def bottom_materialization(type: Any) -> Any: ... - # Returns a tuple of all members of the given object, similar to `dir(obj)` and # `inspect.getmembers(obj)`, with at least the following differences: #