[ty] Add assertions to ensure that we never call KnownClass::Tuple.to_instance() or similar (#21027)

This commit is contained in:
Alex Waygood 2025-10-22 12:07:01 +01:00 committed by GitHub
parent 6271fba1e1
commit 40148d7b11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 28 deletions

View file

@ -4614,7 +4614,13 @@ impl KnownClass {
/// the class. If this class is generic, this will use the default specialization.
///
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
#[track_caller]
pub(crate) fn to_instance(self, db: &dyn Db) -> Type<'_> {
debug_assert_ne!(
self,
KnownClass::Tuple,
"Use `Type::heterogeneous_tuple` or `Type::homogeneous_tuple` to create `tuple` instances"
);
self.to_class_literal(db)
.to_class_type(db)
.map(|class| Type::instance(db, class))
@ -4623,7 +4629,13 @@ impl KnownClass {
/// Similar to [`KnownClass::to_instance`], but returns the Unknown-specialization where each type
/// parameter is specialized to `Unknown`.
#[track_caller]
pub(crate) fn to_instance_unknown(self, db: &dyn Db) -> Type<'_> {
debug_assert_ne!(
self,
KnownClass::Tuple,
"Use `Type::heterogeneous_tuple` or `Type::homogeneous_tuple` to create `tuple` instances"
);
self.try_to_class_literal(db)
.map(|literal| Type::instance(db, literal.unknown_specialization(db)))
.unwrap_or_else(Type::unknown)
@ -4667,11 +4679,17 @@ impl KnownClass {
///
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
/// number of types, a debug-level log message will be emitted stating this.
#[track_caller]
pub(crate) fn to_specialized_instance<'db>(
self,
db: &'db dyn Db,
specialization: impl IntoIterator<Item = Type<'db>>,
) -> Type<'db> {
debug_assert_ne!(
self,
KnownClass::Tuple,
"Use `Type::heterogeneous_tuple` or `Type::homogeneous_tuple` to create `tuple` instances"
);
self.to_specialized_class_type(db, specialization)
.and_then(|class_type| Type::from(class_type).to_instance(db))
.unwrap_or_else(Type::unknown)
@ -5566,11 +5584,19 @@ mod tests {
});
for class in KnownClass::iter() {
assert_ne!(
class.to_instance(&db),
Type::unknown(),
"Unexpectedly fell back to `Unknown` for `{class:?}`"
);
// Check the class can be looked up successfully
class.try_to_class_literal_without_logging(&db).unwrap();
// We can't call `KnownClass::Tuple.to_instance()`;
// there are assertions to ensure that we always call `Type::homogeneous_tuple()`
// or `Type::heterogeneous_tuple()` instead.`
if class != KnownClass::Tuple {
assert_ne!(
class.to_instance(&db),
Type::unknown(),
"Unexpectedly fell back to `Unknown` for `{class:?}`"
);
}
}
}
@ -5617,11 +5643,19 @@ mod tests {
current_version = version_added;
}
assert_ne!(
class.to_instance(&db),
Type::unknown(),
"Unexpectedly fell back to `Unknown` for `{class:?}` on Python {version_added}"
);
// Check the class can be looked up successfully
class.try_to_class_literal_without_logging(&db).unwrap();
// We can't call `KnownClass::Tuple.to_instance()`;
// there are assertions to ensure that we always call `Type::homogeneous_tuple()`
// or `Type::heterogeneous_tuple()` instead.`
if class != KnownClass::Tuple {
assert_ne!(
class.to_instance(&db),
Type::unknown(),
"Unexpectedly fell back to `Unknown` for `{class:?}` on Python {version_added}"
);
}
}
}
}

View file

@ -5950,7 +5950,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.unwrap_or(InferableTypeVars::None);
annotation.filter_disjoint_elements(
self.db(),
KnownClass::Tuple.to_instance(self.db()),
Type::homogeneous_tuple(self.db(), Type::unknown()),
inferable,
)
});

View file

@ -85,16 +85,6 @@ impl<'db> Type<'db> {
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::ExactTuple(tuple)))
}
/// **Private** helper function to create a `Type::NominalInstance` from a class that
/// is known not to be `Any`, a protocol class, or a typed dict class.
fn non_tuple_instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
if class.is_known(db, KnownClass::Object) {
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::Object))
} else {
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
}
}
pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
match self {
Type::NominalInstance(instance_type) => Some(instance_type),
@ -353,9 +343,9 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
Type::tuple(tuple.normalized_impl(db, visitor))
}
NominalInstanceInner::NonTuple(class) => {
Type::non_tuple_instance(db, class.normalized_impl(db, visitor))
}
NominalInstanceInner::NonTuple(class) => Type::NominalInstance(NominalInstanceType(
NominalInstanceInner::NonTuple(class.normalized_impl(db, visitor)),
)),
NominalInstanceInner::Object => Type::object(),
}
}
@ -488,10 +478,11 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
Type::tuple(tuple.apply_type_mapping_impl(db, type_mapping, tcx, visitor))
}
NominalInstanceInner::NonTuple(class) => Type::non_tuple_instance(
db,
class.apply_type_mapping_impl(db, type_mapping, tcx, visitor),
),
NominalInstanceInner::NonTuple(class) => {
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(
class.apply_type_mapping_impl(db, type_mapping, tcx, visitor),
)))
}
NominalInstanceInner::Object => Type::object(),
}
}