[ty] Allow specialization of generic classes with TypeVar instances

This commit is contained in:
David Peter 2025-11-21 14:13:21 +01:00
parent 1af318534a
commit f3d12c4aac
3 changed files with 39 additions and 13 deletions

View File

@ -389,9 +389,8 @@ ListOrTupleLegacy = Union[list[T], tuple[T, ...]]
MyCallable = Callable[P, T] MyCallable = Callable[P, T]
AnnotatedType = Annotated[T, "tag"] AnnotatedType = Annotated[T, "tag"]
# TODO: Consider displaying this as `<class 'list[T]'>`, … instead? (and similar for some others below) reveal_type(MyList) # revealed: <class 'list[T]'>
reveal_type(MyList) # revealed: <class 'list[typing.TypeVar]'> reveal_type(MyDict) # revealed: <class 'dict[T, U]'>
reveal_type(MyDict) # revealed: <class 'dict[typing.TypeVar, typing.TypeVar]'>
reveal_type(MyType) # revealed: GenericAlias reveal_type(MyType) # revealed: GenericAlias
reveal_type(IntAndType) # revealed: <class 'tuple[int, typing.TypeVar]'> reveal_type(IntAndType) # revealed: <class 'tuple[int, typing.TypeVar]'>
reveal_type(Pair) # revealed: <class 'tuple[typing.TypeVar, typing.TypeVar]'> reveal_type(Pair) # revealed: <class 'tuple[typing.TypeVar, typing.TypeVar]'>
@ -496,9 +495,9 @@ def _(
my_callable: MyCallable, my_callable: MyCallable,
): ):
# TODO: Should be `list[Unknown]` # TODO: Should be `list[Unknown]`
reveal_type(my_list) # revealed: list[typing.TypeVar] reveal_type(my_list) # revealed: list[T]
# TODO: Should be `dict[Unknown, Unknown]` # TODO: Should be `dict[Unknown, Unknown]`
reveal_type(my_dict) # revealed: dict[typing.TypeVar, typing.TypeVar] reveal_type(my_dict) # revealed: dict[T, U]
# TODO: Should be `(...) -> Unknown` # TODO: Should be `(...) -> Unknown`
reveal_type(my_callable) # revealed: (...) -> typing.TypeVar reveal_type(my_callable) # revealed: (...) -> typing.TypeVar
``` ```
@ -523,7 +522,7 @@ reveal_mro(Derived1)
GenericBaseAlias = GenericBase[T] GenericBaseAlias = GenericBase[T]
# TODO: No error here # TODO: No error here
# error: [non-subscriptable] "Cannot subscript object of type `<class 'GenericBase[typing.TypeVar]'>` with no `__class_getitem__` method" # error: [non-subscriptable] "Cannot subscript object of type `<class 'GenericBase[T]'>` with no `__class_getitem__` method"
class Derived2(GenericBaseAlias[int]): class Derived2(GenericBaseAlias[int]):
pass pass
``` ```

View File

@ -7197,6 +7197,8 @@ impl<'db> Type<'db> {
instance.apply_type_mapping_impl(db, type_mapping, tcx, visitor) instance.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
}, },
Type::NewTypeInstance(_) if matches!(type_mapping, TypeMapping::BindLegacyTypevars(_)) => self,
Type::NewTypeInstance(newtype) => visitor.visit(self, || { Type::NewTypeInstance(newtype) => visitor.visit(self, || {
Type::NewTypeInstance(newtype.map_base_class_type(db, |class_type| { Type::NewTypeInstance(newtype.map_base_class_type(db, |class_type| {
class_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor) class_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
@ -7275,6 +7277,8 @@ impl<'db> Type<'db> {
// TODO(jelle): Materialize should be handled differently, since TypeIs is invariant // TODO(jelle): Materialize should be handled differently, since TypeIs is invariant
Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping, tcx)), Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping, tcx)),
Type::TypeAlias(_) if matches!(type_mapping, TypeMapping::BindLegacyTypevars(_)) => self,
Type::TypeAlias(alias) => { Type::TypeAlias(alias) => {
// Do not call `value_type` here. `value_type` does the specialization internally, so `apply_type_mapping` is performed without `visitor` inheritance. // Do not call `value_type` here. `value_type` does the specialization internally, so `apply_type_mapping` is performed without `visitor` inheritance.
// In the case of recursive type aliases, this leads to infinite recursion. // In the case of recursive type aliases, this leads to infinite recursion.

View File

@ -101,14 +101,14 @@ use crate::types::typed_dict::{
}; };
use crate::types::visitor::any_over_type; use crate::types::visitor::any_over_type;
use crate::types::{ use crate::types::{
CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType, DataclassParams, BindingContext, CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType,
DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass, DataclassParams, DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass,
KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate,
PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, SubclassOfType, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, SubclassOfType,
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext, TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
TypeQualifiers, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity, TypeMapping, TypeQualifiers, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation,
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType, TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder,
UnionTypeInstance, binding_type, todo_type, UnionType, UnionTypeInstance, binding_type, todo_type,
}; };
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic}; use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
use crate::unpack::{EvaluationMode, UnpackPosition}; use crate::unpack::{EvaluationMode, UnpackPosition};
@ -11046,6 +11046,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
value_ty, value_ty,
generic_context, generic_context,
specialize, specialize,
true,
) )
} }
@ -11070,6 +11071,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
value_ty, value_ty,
generic_context, generic_context,
specialize, specialize,
false,
) )
} }
@ -11079,12 +11081,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
value_ty: Type<'db>, value_ty: Type<'db>,
generic_context: GenericContext<'db>, generic_context: GenericContext<'db>,
specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>, specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>,
bind_legacy_typevars: bool,
) -> Type<'db> { ) -> Type<'db> {
let slice_node = subscript.slice.as_ref(); let slice_node = subscript.slice.as_ref();
let db = self.db();
let do_bind_legacy_typevars = |ty: Type<'db>| {
if bind_legacy_typevars {
ty.apply_type_mapping(
db,
&TypeMapping::BindLegacyTypevars(BindingContext::Synthetic),
TypeContext::default(),
)
} else {
ty
}
};
let call_argument_types = match slice_node { let call_argument_types = match slice_node {
ast::Expr::Tuple(tuple) => { ast::Expr::Tuple(tuple) => {
let arguments = CallArguments::positional( let arguments = CallArguments::positional(
tuple.elts.iter().map(|elt| self.infer_type_expression(elt)), tuple
.elts
.iter()
.map(|elt| do_bind_legacy_typevars(self.infer_type_expression(elt))),
); );
self.store_expression_type( self.store_expression_type(
slice_node, slice_node,
@ -11092,8 +11112,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
); );
arguments arguments
} }
_ => CallArguments::positional([self.infer_type_expression(slice_node)]), _ => CallArguments::positional([do_bind_legacy_typevars(
self.infer_type_expression(slice_node),
)]),
}; };
let binding = Binding::single(value_ty, generic_context.signature(self.db())); let binding = Binding::single(value_ty, generic_context.signature(self.db()));
let bindings = match Bindings::from(binding) let bindings = match Bindings::from(binding)
.match_parameters(self.db(), &call_argument_types) .match_parameters(self.db(), &call_argument_types)