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 a655e397a6..9e0696a5c2 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -5,6 +5,11 @@ At its simplest, to define a generic class using the legacy syntax, you inherit from the `typing.Generic` special form, which is "specialized" with the generic class's type variables. +```toml +[environment] +python-version = "3.11" +``` + ```py from ty_extensions import generic_context from typing_extensions import Generic, TypeVar, TypeVarTuple, ParamSpec, Unpack @@ -19,7 +24,9 @@ class MultipleTypevars(Generic[T, S]): ... class SingleParamSpec(Generic[P]): ... class TypeVarAndParamSpec(Generic[P, T]): ... class SingleTypeVarTuple(Generic[Unpack[Ts]]): ... +class StarredSingleTypeVarTuple(Generic[*Ts]): ... class TypeVarAndTypeVarTuple(Generic[T, Unpack[Ts]]): ... +class StarredTypeVarAndTypeVarTuple(Generic[T, *Ts]): ... # revealed: ty_extensions.GenericContext[T@SingleTypevar] reveal_type(generic_context(SingleTypevar)) @@ -34,6 +41,8 @@ reveal_type(generic_context(TypeVarAndParamSpec)) # TODO: support `TypeVarTuple` properly (these should not reveal `None`) reveal_type(generic_context(SingleTypeVarTuple)) # revealed: None reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: None +reveal_type(generic_context(StarredSingleTypeVarTuple)) # revealed: None +reveal_type(generic_context(StarredTypeVarAndTypeVarTuple)) # revealed: None ``` Inheriting from `Generic` multiple times yields a `duplicate-base` diagnostic, just like any other diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index a81e09edc8..92aa8ae4e8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -956,8 +956,13 @@ impl<'db> Type<'db> { self.is_instance_of(db, KnownClass::NotImplementedType) } - pub(crate) const fn is_todo(&self) -> bool { - matches!(self, Type::Dynamic(DynamicType::Todo(_))) + pub(crate) fn is_todo(&self) -> bool { + self.as_dynamic().is_some_and(|dynamic| match dynamic { + DynamicType::Any | DynamicType::Unknown | DynamicType::Divergent(_) => false, + DynamicType::Todo(_) | DynamicType::TodoStarredExpression | DynamicType::TodoUnpack => { + true + } + }) } pub const fn is_generic_alias(&self) -> bool { @@ -8133,7 +8138,7 @@ impl<'db> Type<'db> { Self::AlwaysFalsy => Type::SpecialForm(SpecialFormType::AlwaysFalsy).definition(db), // These types have no definition - Self::Dynamic(DynamicType::Divergent(_) | DynamicType::Todo(_) | DynamicType::TodoUnpack) + Self::Dynamic(DynamicType::Divergent(_) | DynamicType::Todo(_) | DynamicType::TodoUnpack | DynamicType::TodoStarredExpression) | Self::Callable(_) | Self::TypeIs(_) => None, } @@ -8794,6 +8799,8 @@ pub enum DynamicType { Todo(TodoType), /// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]` TodoUnpack, + /// A special Todo-variant for `*Ts`, so that we can treat it specially in `Generic[Unpack[Ts]]` + TodoStarredExpression, /// A type that is determined to be divergent during recursive type inference. Divergent(DivergentType), } @@ -8824,13 +8831,8 @@ impl std::fmt::Display for DynamicType { // `DynamicType::Todo`'s display should be explicit that is not a valid display of // any other type DynamicType::Todo(todo) => write!(f, "@Todo{todo}"), - DynamicType::TodoUnpack => { - if cfg!(debug_assertions) { - f.write_str("@Todo(typing.Unpack)") - } else { - f.write_str("@Todo") - } - } + DynamicType::TodoUnpack => f.write_str("@Todo(typing.Unpack)"), + DynamicType::TodoStarredExpression => f.write_str("@Todo(StarredExpression)"), DynamicType::Divergent(_) => f.write_str("Divergent"), } } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 4d85a1cc75..c29f72d4f9 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -63,7 +63,9 @@ impl<'db> ClassBase<'db> { ClassBase::Class(class) => class.name(db), ClassBase::Dynamic(DynamicType::Any) => "Any", ClassBase::Dynamic(DynamicType::Unknown) => "Unknown", - ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoUnpack) => "@Todo", + ClassBase::Dynamic( + DynamicType::Todo(_) | DynamicType::TodoUnpack | DynamicType::TodoStarredExpression, + ) => "@Todo", ClassBase::Dynamic(DynamicType::Divergent(_)) => "Divergent", ClassBase::Protocol => "Protocol", ClassBase::Generic => "Generic", diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 61ac9059ae..77f587e2fc 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -8407,7 +8407,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }); // TODO - todo_type!("starred expression") + Type::Dynamic(DynamicType::TodoStarredExpression) } fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> { @@ -9571,10 +9571,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { (unknown @ Type::Dynamic(DynamicType::Unknown), _, _) | (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown), - (todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoUnpack), _, _) - | (_, todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoUnpack), _) => { - Some(todo) - } + ( + todo @ Type::Dynamic( + DynamicType::Todo(_) + | DynamicType::TodoUnpack + | DynamicType::TodoStarredExpression, + ), + _, + _, + ) + | ( + _, + todo @ Type::Dynamic( + DynamicType::Todo(_) + | DynamicType::TodoUnpack + | DynamicType::TodoStarredExpression, + ), + _, + ) => Some(todo), (Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never), @@ -11898,7 +11912,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.db(), *typevar, &|ty| match ty { - Type::Dynamic(DynamicType::TodoUnpack) => true, + Type::Dynamic( + DynamicType::TodoUnpack | DynamicType::TodoStarredExpression, + ) => true, Type::NominalInstance(nominal) => matches!( nominal.known_class(self.db()), Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec) 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 153f46dda3..0e3a326139 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 @@ -499,7 +499,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { if starred_type.exact_tuple_instance_spec(self.db()).is_some() { starred_type } else { - todo_type!("PEP 646") + Type::Dynamic(DynamicType::TodoStarredExpression) } } diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index d2c9a71208..f57855fcb4 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -274,6 +274,9 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering (DynamicType::TodoUnpack, _) => Ordering::Less, (_, DynamicType::TodoUnpack) => Ordering::Greater, + (DynamicType::TodoStarredExpression, _) => Ordering::Less, + (_, DynamicType::TodoStarredExpression) => Ordering::Greater, + (DynamicType::Divergent(left), DynamicType::Divergent(right)) => left.cmp(&right), (DynamicType::Divergent(_), _) => Ordering::Less, (_, DynamicType::Divergent(_)) => Ordering::Greater,