[ty] Infer type[tuple[int, str]] as the meta-type of tuple[int, str] (#19741)

This commit is contained in:
Alex Waygood 2025-08-04 14:10:47 +01:00 committed by GitHub
parent bc6e8b58ce
commit 41207ec901
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 71 additions and 21 deletions

View file

@ -790,6 +790,13 @@ impl<'db> Type<'db> {
}
}
pub const fn into_subclass_of(self) -> Option<SubclassOfType<'db>> {
match self {
Type::SubclassOf(subclass_of) => Some(subclass_of),
_ => None,
}
}
#[track_caller]
pub fn expect_class_literal(self) -> ClassLiteral<'db> {
self.into_class_literal()
@ -5496,10 +5503,8 @@ impl<'db> Type<'db> {
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
Type::Tuple(tuple) => tuple
.to_class_type(db)
.map(Type::from)
.unwrap_or_else(Type::unknown),
.to_subclass_of(db)
.unwrap_or_else(SubclassOfType::subclass_of_unknown),
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
None => KnownClass::Type.to_instance(db),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.to_meta_type(db),

View file

@ -268,6 +268,10 @@ pub enum ClassType<'db> {
#[salsa::tracked]
impl<'db> ClassType<'db> {
pub(super) const fn is_not_generic(self) -> bool {
matches!(self, Self::NonGeneric(_))
}
pub(super) fn normalized_impl(
self,
db: &'db dyn Db,

View file

@ -143,6 +143,11 @@ impl<'db> AllMembers<'db> {
Type::ClassLiteral(class_literal) => {
self.extend_with_class_members(db, ty, class_literal);
}
Type::SubclassOf(subclass_of) => {
if let Some(class) = subclass_of.subclass_of().into_class() {
self.extend_with_class_members(db, ty, class.class_literal(db).0);
}
}
Type::GenericAlias(generic_alias) => {
let class_literal = generic_alias.origin(db);
self.extend_with_class_members(db, ty, class_literal);

View file

@ -6069,7 +6069,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// the `try_call` path below.
// TODO: it should be possible to move these special cases into the `try_call_constructor`
// path instead, or even remove some entirely once we support overloads fully.
if !matches!(
let has_special_cased_constructor = matches!(
class.known(self.db()),
Some(
KnownClass::Bool
@ -6083,20 +6083,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| KnownClass::TypeAliasType
| KnownClass::Deprecated
)
)
// Constructor calls to `tuple` and subclasses of `tuple` are handled in `Type::Bindings`,
// but constructor calls to `tuple[int]`, `tuple[int, ...]`, `tuple[int, *tuple[str, ...]]` (etc.)
// are handled by the default constructor-call logic (we synthesize a `__new__` method for them
// in `ClassType::own_class_member()`).
&& (callable_type.is_generic_alias() || !class.is_known(self.db(), KnownClass::Tuple))
) || (
// Constructor calls to `tuple` and subclasses of `tuple` are handled in `Type::Bindings`,
// but constructor calls to `tuple[int]`, `tuple[int, ...]`, `tuple[int, *tuple[str, ...]]` (etc.)
// are handled by the default constructor-call logic (we synthesize a `__new__` method for them
// in `ClassType::own_class_member()`).
class.is_known(self.db(), KnownClass::Tuple) && class.is_not_generic()
);
// temporary special-casing for all subclasses of `enum.Enum`
// until we support the functional syntax for creating enum classes
&& KnownClass::Enum
.to_class_literal(self.db())
.to_class_type(self.db())
.is_none_or(|enum_class| !class.is_subclass_of(self.db(), enum_class))
if !has_special_cased_constructor
&& KnownClass::Enum
.to_class_literal(self.db())
.to_class_type(self.db())
.is_none_or(|enum_class| !class.is_subclass_of(self.db(), enum_class))
{
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
self.infer_argument_types(arguments, &mut call_arguments, &argument_forms);
@ -8478,6 +8479,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
let tuple_generic_alias = |db: &'db dyn Db, tuple: Option<TupleType<'db>>| {
let tuple =
tuple.unwrap_or_else(|| TupleType::homogeneous(db, Type::unknown()).unwrap());
tuple
.to_class_type(db)
.map(Type::from)
.unwrap_or_else(Type::unknown)
};
// HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the
// subscript inference logic and treat this as an explicit specialization.
// TODO: Move this logic into a custom callable, and update `find_name_in_mro` to return
@ -8486,8 +8496,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// special cases, too.
if let Type::ClassLiteral(class) = value_ty {
if class.is_tuple(self.db()) {
return Type::tuple(self.infer_tuple_type_expression(slice))
.to_meta_type(self.db());
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
}
if let Some(generic_context) = class.generic_context(self.db()) {
return self.infer_explicit_class_specialization(
@ -8499,7 +8508,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
if let Type::SpecialForm(SpecialFormType::Tuple) = value_ty {
return Type::tuple(self.infer_tuple_type_expression(slice)).to_meta_type(self.db());
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
}
let slice_ty = self.infer_expression(slice);

View file

@ -22,8 +22,8 @@ use std::hash::Hash;
use itertools::{Either, EitherOrBoth, Itertools};
use crate::types::Truthiness;
use crate::types::class::{ClassType, KnownClass};
use crate::types::{SubclassOfType, Truthiness};
use crate::types::{
Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance,
UnionBuilder, UnionType, cyclic::PairVisitor,
@ -238,6 +238,11 @@ impl<'db> TupleType<'db> {
})
}
pub(crate) fn to_subclass_of(self, db: &'db dyn Db) -> Option<Type<'db>> {
self.to_class_type(db)
.map(|class| SubclassOfType::from(db, class))
}
/// Return a normalized version of `self`.
///
/// See [`Type::normalized`] for more details.