Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/class/super.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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`
Expand Down
41 changes: 25 additions & 16 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9389,23 +9389,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"),
)
.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| {
Expand Down
Loading