diff --git a/crates/ty_python_semantic/resources/mdtest/class/super.md b/crates/ty_python_semantic/resources/mdtest/class/super.md index ecffca0fb6..c7b6e0ef80 100644 --- a/crates/ty_python_semantic/resources/mdtest/class/super.md +++ b/crates/ty_python_semantic/resources/mdtest/class/super.md @@ -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: > + +def _(x: type[typing.Any], y: typing.Any): + reveal_type(super(x, y)) # revealed: ``` ### Instance Member Access via `super` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 3c6a9d2e8c..2ae65d7f90 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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 - // . - 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"), - ) - .ok_or({ - BoundSuperError::InvalidPivotClassType { - pivot_class: pivot_class_type, + // 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"), + ) } - })?; + 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| {