diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 96e86f0dbf..84779ba979 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -7,18 +7,30 @@ At its simplest, to define a generic class using the legacy syntax, you inherit ```py from ty_extensions import generic_context -from typing import Generic, TypeVar +from typing_extensions import Generic, TypeVar, TypeVarTuple, ParamSpec, Unpack T = TypeVar("T") S = TypeVar("S") +P = ParamSpec("P") +Ts = TypeVarTuple("Ts") class SingleTypevar(Generic[T]): ... class MultipleTypevars(Generic[T, S]): ... +class SingleParamSpec(Generic[P]): ... +class TypeVarAndParamSpec(Generic[P, T]): ... +class SingleTypeVarTuple(Generic[Unpack[Ts]]): ... +class TypeVarAndTypeVarTuple(Generic[T, Unpack[Ts]]): ... # revealed: tuple[T@SingleTypevar] reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] reveal_type(generic_context(MultipleTypevars)) + +# TODO: support `ParamSpec`/`TypeVarTuple` properly (these should not reveal `None`) +reveal_type(generic_context(SingleParamSpec)) # revealed: None +reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: None +reveal_type(generic_context(SingleTypeVarTuple)) # revealed: None +reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: None ``` Inheriting from `Generic` multiple times yields a `duplicate-base` diagnostic, just like any other diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index f14205b118..0dddaf2df2 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -7,19 +7,30 @@ python-version = "3.13" ## Defining a generic class -At its simplest, to define a generic class using PEP 695 syntax, you add a list of typevars after -the class name. +At its simplest, to define a generic class using PEP 695 syntax, you add a list of `TypeVar`s, +`ParamSpec`s or `TypeVarTuple`s after the class name. ```py from ty_extensions import generic_context class SingleTypevar[T]: ... class MultipleTypevars[T, S]: ... +class SingleParamSpec[**P]: ... +class TypeVarAndParamSpec[T, **P]: ... +class SingleTypeVarTuple[*Ts]: ... +class TypeVarAndTypeVarTuple[T, *Ts]: ... # revealed: tuple[T@SingleTypevar] reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] reveal_type(generic_context(MultipleTypevars)) + +# TODO: support `ParamSpec`/`TypeVarTuple` properly +# (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts) +reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()] +reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec] +reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()] +reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple] ``` You cannot use the same typevar more than once. diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 6f85606830..52b54ce936 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -6374,6 +6374,8 @@ pub enum DynamicType { /// A special Todo-variant for type aliases declared using `typing.TypeAlias`. /// A temporary variant to detect and special-case the handling of these aliases in autocomplete suggestions. TodoTypeAlias, + /// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]` + TodoUnpack, } impl DynamicType { @@ -6398,6 +6400,13 @@ impl std::fmt::Display for DynamicType { f.write_str("@Todo") } } + DynamicType::TodoUnpack => { + if cfg!(debug_assertions) { + f.write_str("@Todo(typing.Unpack)") + } else { + f.write_str("@Todo") + } + } DynamicType::TodoTypeAlias => { if cfg!(debug_assertions) { f.write_str("@Todo(Support for `typing.TypeAlias`)") diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 9cbe28464a..4c5363971b 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -52,7 +52,8 @@ impl<'db> ClassBase<'db> { ClassBase::Dynamic( DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec - | DynamicType::TodoTypeAlias, + | DynamicType::TodoTypeAlias + | DynamicType::TodoUnpack, ) => "@Todo", ClassBase::Protocol => "Protocol", ClassBase::Generic => "Generic", diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index d6e6a91f59..e29056c01b 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -7286,6 +7286,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { todo @ Type::Dynamic( DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec + | DynamicType::TodoUnpack | DynamicType::TodoTypeAlias, ), _, @@ -7296,6 +7297,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { todo @ Type::Dynamic( DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec + | DynamicType::TodoUnpack | DynamicType::TodoTypeAlias, ), _, @@ -8803,6 +8805,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Some(todo_type!("doubly-specialized typing.Generic")) } + (Type::SpecialForm(SpecialFormType::Unpack), _) => { + Some(Type::Dynamic(DynamicType::TodoUnpack)) + } + (Type::SpecialForm(special_form), _) if special_form.class().is_special_form() => { Some(todo_type!("Inference of subscript on special form")) } @@ -8969,6 +8975,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .iter() .map(|typevar| match typevar { Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => Ok(*typevar), + Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported), Type::NominalInstance(NominalInstanceType { class, .. }) if matches!( class.known(self.db()), diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 23f9c61625..729a181b15 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -262,6 +262,9 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering (DynamicType::TodoPEP695ParamSpec, _) => Ordering::Less, (_, DynamicType::TodoPEP695ParamSpec) => Ordering::Greater, + (DynamicType::TodoUnpack, _) => Ordering::Less, + (_, DynamicType::TodoUnpack) => Ordering::Greater, + (DynamicType::TodoTypeAlias, _) => Ordering::Less, (_, DynamicType::TodoTypeAlias) => Ordering::Greater, }