[ty] Remove use of ClassBase::try_from_type from super() machinery (#19902)

This commit is contained in:
Alex Waygood 2025-08-14 22:14:31 +01:00 committed by GitHub
parent ce938fe205
commit 82350a398e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 16 deletions

View file

@ -331,6 +331,9 @@ instance or a subclass of the first. If either condition is violated, a `TypeErr
runtime.
```py
import typing
import collections
def f(x: int):
# error: [invalid-super-argument] "`int` is not a valid class"
super(x, x)
@ -367,6 +370,19 @@ reveal_type(super(B, A))
reveal_type(super(B, object))
super(object, object()).__class__
# Not all objects valid in a class's bases list are valid as the first argument to `super()`.
# For example, it's valid to inherit from `typing.ChainMap`, but it's not valid as the first argument to `super()`.
#
# error: [invalid-super-argument] "`typing.ChainMap` is not a valid class"
reveal_type(super(typing.ChainMap, collections.ChainMap())) # revealed: Unknown
# Meanwhile, it's not valid to inherit from unsubscripted `typing.Generic`,
# but it *is* valid as the first argument to `super()`.
reveal_type(super(typing.Generic, typing.SupportsInt)) # revealed: <super: typing.Generic, <class 'SupportsInt'>>
def _(x: type[typing.Any], y: typing.Any):
reveal_type(super(x, y)) # revealed: <super: Any, Any>
```
### Instance Member Access via `super`

View file

@ -9526,23 +9526,32 @@ impl<'db> BoundSuperType<'db> {
));
}
// TODO: having to get a class-literal just to pass it in here is silly.
// `BoundSuperType` should probably not be using `ClassBase::try_from_type` here;
// this also leads to false negatives in some cases. See discussion in
// <https://github.com/astral-sh/ruff/pull/19560#discussion_r2271570071>.
let pivot_class = ClassBase::try_from_type(
db,
pivot_class_type,
KnownClass::Object
.to_class_literal(db)
.into_class_literal()
.expect("`object` should always exist in typeshed"),
// We don't use `Classbase::try_from_type` here because:
// - There are objects that may validly be present in a class's bases list
// but are not valid as pivot classes, e.g. `typing.ChainMap`
// - There are objects that are not valid in a class's bases list
// but are valid as pivot classes, e.g. unsubscripted `typing.Generic`
let pivot_class = match pivot_class_type {
Type::ClassLiteral(class) => ClassBase::Class(ClassType::NonGeneric(class)),
Type::GenericAlias(class) => ClassBase::Class(ClassType::Generic(class)),
Type::SubclassOf(subclass_of) if subclass_of.subclass_of().is_dynamic() => {
ClassBase::Dynamic(
subclass_of
.subclass_of()
.into_dynamic()
.expect("Checked in branch arm"),
)
.ok_or({
BoundSuperError::InvalidPivotClassType {
pivot_class: pivot_class_type,
}
})?;
Type::SpecialForm(SpecialFormType::Protocol) => ClassBase::Protocol,
Type::SpecialForm(SpecialFormType::Generic) => ClassBase::Generic,
Type::SpecialForm(SpecialFormType::TypedDict) => ClassBase::TypedDict,
Type::Dynamic(dynamic) => ClassBase::Dynamic(dynamic),
_ => {
return Err(BoundSuperError::InvalidPivotClassType {
pivot_class: pivot_class_type,
});
}
};
let owner = SuperOwnerKind::try_from_type(db, owner_type)
.and_then(|owner| {