[ty] Fix false positives for `class F(Generic[*Ts]): ...` (#21723)

This commit is contained in:
Alex Waygood 2025-12-01 13:24:07 +00:00 committed by GitHub
parent 116fd7c7af
commit 0e651b50b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 50 additions and 18 deletions

View File

@ -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

View File

@ -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"),
}
}

View File

@ -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",

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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,