[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 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. `typing.Generic` special form, which is "specialized" with the generic class's type variables.
```toml
[environment]
python-version = "3.11"
```
```py ```py
from ty_extensions import generic_context from ty_extensions import generic_context
from typing_extensions import Generic, TypeVar, TypeVarTuple, ParamSpec, Unpack from typing_extensions import Generic, TypeVar, TypeVarTuple, ParamSpec, Unpack
@ -19,7 +24,9 @@ class MultipleTypevars(Generic[T, S]): ...
class SingleParamSpec(Generic[P]): ... class SingleParamSpec(Generic[P]): ...
class TypeVarAndParamSpec(Generic[P, T]): ... class TypeVarAndParamSpec(Generic[P, T]): ...
class SingleTypeVarTuple(Generic[Unpack[Ts]]): ... class SingleTypeVarTuple(Generic[Unpack[Ts]]): ...
class StarredSingleTypeVarTuple(Generic[*Ts]): ...
class TypeVarAndTypeVarTuple(Generic[T, Unpack[Ts]]): ... class TypeVarAndTypeVarTuple(Generic[T, Unpack[Ts]]): ...
class StarredTypeVarAndTypeVarTuple(Generic[T, *Ts]): ...
# revealed: ty_extensions.GenericContext[T@SingleTypevar] # revealed: ty_extensions.GenericContext[T@SingleTypevar]
reveal_type(generic_context(SingleTypevar)) reveal_type(generic_context(SingleTypevar))
@ -34,6 +41,8 @@ reveal_type(generic_context(TypeVarAndParamSpec))
# TODO: support `TypeVarTuple` properly (these should not reveal `None`) # TODO: support `TypeVarTuple` properly (these should not reveal `None`)
reveal_type(generic_context(SingleTypeVarTuple)) # revealed: None reveal_type(generic_context(SingleTypeVarTuple)) # revealed: None
reveal_type(generic_context(TypeVarAndTypeVarTuple)) # 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 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) self.is_instance_of(db, KnownClass::NotImplementedType)
} }
pub(crate) const fn is_todo(&self) -> bool { pub(crate) fn is_todo(&self) -> bool {
matches!(self, Type::Dynamic(DynamicType::Todo(_))) 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 { pub const fn is_generic_alias(&self) -> bool {
@ -8133,7 +8138,7 @@ impl<'db> Type<'db> {
Self::AlwaysFalsy => Type::SpecialForm(SpecialFormType::AlwaysFalsy).definition(db), Self::AlwaysFalsy => Type::SpecialForm(SpecialFormType::AlwaysFalsy).definition(db),
// These types have no definition // 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::Callable(_)
| Self::TypeIs(_) => None, | Self::TypeIs(_) => None,
} }
@ -8794,6 +8799,8 @@ pub enum DynamicType {
Todo(TodoType), Todo(TodoType),
/// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]` /// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]`
TodoUnpack, 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. /// A type that is determined to be divergent during recursive type inference.
Divergent(DivergentType), 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 // `DynamicType::Todo`'s display should be explicit that is not a valid display of
// any other type // any other type
DynamicType::Todo(todo) => write!(f, "@Todo{todo}"), DynamicType::Todo(todo) => write!(f, "@Todo{todo}"),
DynamicType::TodoUnpack => { DynamicType::TodoUnpack => f.write_str("@Todo(typing.Unpack)"),
if cfg!(debug_assertions) { DynamicType::TodoStarredExpression => f.write_str("@Todo(StarredExpression)"),
f.write_str("@Todo(typing.Unpack)")
} else {
f.write_str("@Todo")
}
}
DynamicType::Divergent(_) => f.write_str("Divergent"), DynamicType::Divergent(_) => f.write_str("Divergent"),
} }
} }

View File

@ -63,7 +63,9 @@ impl<'db> ClassBase<'db> {
ClassBase::Class(class) => class.name(db), ClassBase::Class(class) => class.name(db),
ClassBase::Dynamic(DynamicType::Any) => "Any", ClassBase::Dynamic(DynamicType::Any) => "Any",
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown", 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::Dynamic(DynamicType::Divergent(_)) => "Divergent",
ClassBase::Protocol => "Protocol", ClassBase::Protocol => "Protocol",
ClassBase::Generic => "Generic", ClassBase::Generic => "Generic",

View File

@ -8407,7 +8407,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}); });
// TODO // TODO
todo_type!("starred expression") Type::Dynamic(DynamicType::TodoStarredExpression)
} }
fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> { 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), _, _)
| (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown), | (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown),
(todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoUnpack), _, _) (
| (_, todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoUnpack), _) => { todo @ Type::Dynamic(
Some(todo) DynamicType::Todo(_)
} | DynamicType::TodoUnpack
| DynamicType::TodoStarredExpression,
),
_,
_,
)
| (
_,
todo @ Type::Dynamic(
DynamicType::Todo(_)
| DynamicType::TodoUnpack
| DynamicType::TodoStarredExpression,
),
_,
) => Some(todo),
(Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never), (Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never),
@ -11898,7 +11912,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.db(), self.db(),
*typevar, *typevar,
&|ty| match ty { &|ty| match ty {
Type::Dynamic(DynamicType::TodoUnpack) => true, Type::Dynamic(
DynamicType::TodoUnpack | DynamicType::TodoStarredExpression,
) => true,
Type::NominalInstance(nominal) => matches!( Type::NominalInstance(nominal) => matches!(
nominal.known_class(self.db()), nominal.known_class(self.db()),
Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec) 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() { if starred_type.exact_tuple_instance_spec(self.db()).is_some() {
starred_type starred_type
} else { } 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::Less,
(_, DynamicType::TodoUnpack) => Ordering::Greater, (_, DynamicType::TodoUnpack) => Ordering::Greater,
(DynamicType::TodoStarredExpression, _) => Ordering::Less,
(_, DynamicType::TodoStarredExpression) => Ordering::Greater,
(DynamicType::Divergent(left), DynamicType::Divergent(right)) => left.cmp(&right), (DynamicType::Divergent(left), DynamicType::Divergent(right)) => left.cmp(&right),
(DynamicType::Divergent(_), _) => Ordering::Less, (DynamicType::Divergent(_), _) => Ordering::Less,
(_, DynamicType::Divergent(_)) => Ordering::Greater, (_, DynamicType::Divergent(_)) => Ordering::Greater,