[ty] Fix super() with TypeVar-annotated self and cls parameter (#22208)

## Summary

This PR fixes `super()` handling when the first parameter (`self` or
`cls`) is annotated with a TypeVar, like `Self`.

Previously, `super()` would incorrectly resolve TypeVars to their bounds
before creating the `BoundSuperType`. So if you had `self: Self` where
`Self` is bounded by `Parent`, we'd process `Parent` as a
`NominalInstance` and end up with `SuperOwnerKind::Instance(Parent)`.

As a result:

```python
class Parent:
    @classmethod
    def create(cls) -> Self:
        return cls()

class Child(Parent):
    @classmethod
    def create(cls) -> Self:
        return super().create()  # Error: Argument type `Self@create` does not satisfy upper bound `Parent`
```

We now track two additional variants on `SuperOwnerKind` for TypeVar
owners:

- `InstanceTypeVar`: for instance methods where self is a TypeVar (e.g.,
`self: Self`).
- `ClassTypeVar`: for classmethods where `cls` is a `TypeVar` wrapped in
`type[...]` (e.g., `cls: type[Self]`).

Closes https://github.com/astral-sh/ty/issues/2122.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Charlie Marsh
2026-01-07 19:56:09 -05:00
committed by GitHub
parent abaa735e1d
commit 68a2f6c57d
6 changed files with 393 additions and 99 deletions

View File

@@ -359,6 +359,51 @@ reveal_type(GenericCircle[int].bar()) # revealed: GenericCircle[int]
reveal_type(GenericCircle.baz(1)) # revealed: GenericShape[Literal[1]]
```
### Calling `super()` in overridden methods with `Self` return type
This is a regression test for <https://github.com/astral-sh/ty/issues/2122>.
When a child class overrides a parent method with a `Self` return type and calls `super().method()`,
the return type should be the child's `Self` type variable, not the concrete child class type.
```py
from typing import Self
class Parent:
def copy(self) -> Self:
return self
class Child(Parent):
def copy(self) -> Self:
result = super().copy()
reveal_type(result) # revealed: Self@copy
return result
# When called on concrete types, Self is substituted correctly.
reveal_type(Child().copy()) # revealed: Child
```
The same applies to classmethods with `Self` return types:
```py
from typing import Self
class Parent:
@classmethod
def create(cls) -> Self:
return cls()
class Child(Parent):
@classmethod
def create(cls) -> Self:
result = super().create()
reveal_type(result) # revealed: Self@create
return result
# When called on concrete types, Self is substituted correctly.
reveal_type(Child.create()) # revealed: Child
```
## Attributes
TODO: The use of `Self` to annotate the `next_node` attribute should be

View File

@@ -168,13 +168,13 @@ class A:
class B(A):
def __init__(self, a: int):
reveal_type(super()) # revealed: <super: <class 'B'>, B>
reveal_type(super()) # revealed: <super: <class 'B'>, Self@__init__>
reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
super().__init__(a)
@classmethod
def f(cls):
reveal_type(super()) # revealed: <super: <class 'B'>, <class 'B'>>
reveal_type(super()) # revealed: <super: <class 'B'>, type[Self@f]>
super().f()
super(B, B(42)).__init__(42)
@@ -229,16 +229,16 @@ class Foo[T]:
reveal_type(super())
def method4(self: Self):
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
# revealed: <super: <class 'Foo'>, Self@method4>
reveal_type(super())
def method5[S: Foo[int]](self: S, other: S) -> S:
# revealed: <super: <class 'Foo'>, Foo[int]>
# revealed: <super: <class 'Foo'>, S@method5>
reveal_type(super())
return self
def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
# revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
# revealed: <super: <class 'Foo'>, S@method6> | <super: <class 'Foo'>, S@method6>
reveal_type(super())
return self
@@ -265,6 +265,19 @@ class Foo[T]:
# revealed: Unknown
reveal_type(super())
return self
# TypeVar bounded by `type[Foo]` rather than `Foo`
# TODO: Should error on signature - `self` is annotated as a class type, not an instance type
def method11[S: type[Foo[int]]](self: S, other: S) -> S:
# Delegates to the bound to resolve the super type
reveal_type(super()) # revealed: <super: <class 'Foo'>, <class 'Foo[int]'>>
return self
# TypeVar bounded by `type[Foo]`, used in `type[T]` position
# TODO: Should error on signature - `cls` would be `type[type[Foo[int]]]`, a metaclass
# Delegates to `type[Unknown]` since `type[type[Foo[int]]]` can't be constructed
@classmethod
def method12[S: type[Foo[int]]](cls: type[S]) -> S:
reveal_type(super()) # revealed: <super: <class 'Foo'>, Unknown>
raise NotImplementedError
type Alias = Bar
@@ -359,15 +372,15 @@ from __future__ import annotations
class A:
def test(self):
reveal_type(super()) # revealed: <super: <class 'A'>, A>
reveal_type(super()) # revealed: <super: <class 'A'>, Self@test>
class B:
def test(self):
reveal_type(super()) # revealed: <super: <class 'B'>, B>
reveal_type(super()) # revealed: <super: <class 'B'>, Self@test>
class C(A.B):
def test(self):
reveal_type(super()) # revealed: <super: <class 'C'>, C>
reveal_type(super()) # revealed: <super: <class 'C'>, Self@test>
def inner(t: C):
reveal_type(super()) # revealed: <super: <class 'B'>, C>
@@ -645,7 +658,7 @@ class A:
class B(A):
def __init__(self, a: int):
super().__init__(a)
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, Self@__init__>` has no attribute `a`"
super().a
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"
@@ -670,3 +683,58 @@ reveal_type(super(B, B()).__getitem__) # revealed: bound method B.__getitem__(k
# error: [not-subscriptable] "Cannot subscript object of type `<super: <class 'B'>, B>` with no `__getitem__` method"
super(B, B())[0]
```
## Subclass Using Concrete Type Instead of `Self`
When a parent class uses `Self` in a parameter type and a subclass overrides it with a concrete
type, passing that parameter to `super().__init__()` is a type error. This is because `Self` in the
parent could represent a further subclass. The fix is to use `Self` consistently in the subclass.
```toml
[environment]
python-version = "3.12"
```
```py
from __future__ import annotations
from collections.abc import Mapping
from typing import Self
class Parent:
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
self.children = children
class Child(Parent):
def __init__(self, children: Mapping[str, Child] | None = None) -> None:
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `Mapping[str, Self@__init__] | None`, found `Mapping[str, Child] | None`"
super().__init__(children)
# The fix is to use `Self` consistently in the subclass:
class Parent2:
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
self.children = children
class Child2(Parent2):
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
super().__init__(children) # OK
```
## Super in Protocol Classes
Using `super()` in a class that inherits from `typing.Protocol` (similar to beartype's caching
Protocol):
```py
from typing import Protocol, Generic, TypeVar
_T_co = TypeVar("_T_co", covariant=True)
class MyProtocol(Protocol, Generic[_T_co]):
def __class_getitem__(cls, item):
# Accessing parent's __class_getitem__ through super()
reveal_type(super()) # revealed: <super: <class 'MyProtocol'>, type[Self@__class_getitem__]>
parent_method = super().__class_getitem__
reveal_type(parent_method) # revealed: @Todo(super in generic class)
return parent_method(item)
```

View File

@@ -22,13 +22,13 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
7 |
8 | class B(A):
9 | def __init__(self, a: int):
10 | reveal_type(super()) # revealed: <super: <class 'B'>, B>
10 | reveal_type(super()) # revealed: <super: <class 'B'>, Self@__init__>
11 | reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
12 | super().__init__(a)
13 |
14 | @classmethod
15 | def f(cls):
16 | reveal_type(super()) # revealed: <super: <class 'B'>, <class 'B'>>
16 | reveal_type(super()) # revealed: <super: <class 'B'>, type[Self@f]>
17 | super().f()
18 |
19 | super(B, B(42)).__init__(42)
@@ -78,16 +78,16 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
63 | reveal_type(super())
64 |
65 | def method4(self: Self):
66 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
66 | # revealed: <super: <class 'Foo'>, Self@method4>
67 | reveal_type(super())
68 |
69 | def method5[S: Foo[int]](self: S, other: S) -> S:
70 | # revealed: <super: <class 'Foo'>, Foo[int]>
70 | # revealed: <super: <class 'Foo'>, S@method5>
71 | reveal_type(super())
72 | return self
73 |
74 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
75 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
75 | # revealed: <super: <class 'Foo'>, S@method6> | <super: <class 'Foo'>, S@method6>
76 | reveal_type(super())
77 | return self
78 |
@@ -114,35 +114,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
99 | # revealed: Unknown
100 | reveal_type(super())
101 | return self
102 |
103 | type Alias = Bar
104 |
105 | class Bar:
106 | def method(self: Alias):
107 | # revealed: <super: <class 'Bar'>, Bar>
108 | reveal_type(super())
109 |
110 | def pls_dont_call_me(self: Never):
111 | # revealed: <super: <class 'Bar'>, Unknown>
112 | reveal_type(super())
113 |
114 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
115 | # revealed: <super: <class 'Bar'>, Bar>
116 | reveal_type(super())
102 | # TypeVar bounded by `type[Foo]` rather than `Foo`
103 | # TODO: Should error on signature - `self` is annotated as a class type, not an instance type
104 | def method11[S: type[Foo[int]]](self: S, other: S) -> S:
105 | # Delegates to the bound to resolve the super type
106 | reveal_type(super()) # revealed: <super: <class 'Foo'>, <class 'Foo[int]'>>
107 | return self
108 | # TypeVar bounded by `type[Foo]`, used in `type[T]` position
109 | # TODO: Should error on signature - `cls` would be `type[type[Foo[int]]]`, a metaclass
110 | # Delegates to `type[Unknown]` since `type[type[Foo[int]]]` can't be constructed
111 | @classmethod
112 | def method12[S: type[Foo[int]]](cls: type[S]) -> S:
113 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Unknown>
114 | raise NotImplementedError
115 |
116 | type Alias = Bar
117 |
118 | class P(Protocol):
119 | def method(self: P):
120 | # revealed: <super: <class 'P'>, P>
118 | class Bar:
119 | def method(self: Alias):
120 | # revealed: <super: <class 'Bar'>, Bar>
121 | reveal_type(super())
122 |
123 | class E(enum.Enum):
124 | X = 1
125 |
126 | def method(self: E):
127 | match self:
128 | case E.X:
129 | # revealed: <super: <class 'E'>, E>
130 | reveal_type(super())
123 | def pls_dont_call_me(self: Never):
124 | # revealed: <super: <class 'Bar'>, Unknown>
125 | reveal_type(super())
126 |
127 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
128 | # revealed: <super: <class 'Bar'>, Bar>
129 | reveal_type(super())
130 |
131 | class P(Protocol):
132 | def method(self: P):
133 | # revealed: <super: <class 'P'>, P>
134 | reveal_type(super())
135 |
136 | class E(enum.Enum):
137 | X = 1
138 |
139 | def method(self: E):
140 | match self:
141 | case E.X:
142 | # revealed: <super: <class 'E'>, E>
143 | reveal_type(super())
```
# Diagnostics
@@ -205,6 +218,7 @@ error[invalid-super-argument]: `S@method10` is a type variable with an abstract/
100 | reveal_type(super())
| ^^^^^^^
101 | return self
102 | # TypeVar bounded by `type[Foo]` rather than `Foo`
|
info: Type variable `S` has upper bound `(...) -> str`
info: rule `invalid-super-argument` is enabled by default

View File

@@ -8,9 +8,10 @@ use crate::{
Db, DisplaySettings,
place::{Place, PlaceAndQualifiers},
types::{
ClassBase, ClassType, DynamicType, IntersectionBuilder, KnownClass, MemberLookupPolicy,
NominalInstanceType, NormalizedVisitor, SpecialFormType, SubclassOfInner, Type,
TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder,
BoundTypeVarInstance, ClassBase, ClassType, DynamicType, IntersectionBuilder, KnownClass,
MemberLookupPolicy, NominalInstanceType, NormalizedVisitor, SpecialFormType,
SubclassOfInner, SubclassOfType, Type, TypeVarBoundOrConstraints, TypeVarConstraints,
TypeVarInstance, UnionBuilder,
context::InferContext,
diagnostic::{INVALID_SUPER_ARGUMENT, UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS},
todo_type, visitor,
@@ -185,6 +186,12 @@ pub enum SuperOwnerKind<'db> {
Dynamic(DynamicType<'db>),
Class(ClassType<'db>),
Instance(NominalInstanceType<'db>),
/// An instance-like type variable owner (e.g., `self: Self` in an instance method).
/// The second element is the class extracted from the `TypeVar` bound for MRO lookup.
InstanceTypeVar(BoundTypeVarInstance<'db>, ClassType<'db>),
/// A class-like type variable owner (e.g., `cls: type[Self]` in a classmethod).
/// The second element is the class extracted from the `TypeVar` bound for MRO lookup.
ClassTypeVar(BoundTypeVarInstance<'db>, ClassType<'db>),
}
impl<'db> SuperOwnerKind<'db> {
@@ -199,6 +206,16 @@ impl<'db> SuperOwnerKind<'db> {
.as_nominal_instance()
.map(Self::Instance)
.unwrap_or(Self::Dynamic(DynamicType::Any)),
SuperOwnerKind::InstanceTypeVar(bound_typevar, class) => {
SuperOwnerKind::InstanceTypeVar(
bound_typevar.normalized_impl(db, visitor),
class.normalized_impl(db, visitor),
)
}
SuperOwnerKind::ClassTypeVar(bound_typevar, class) => SuperOwnerKind::ClassTypeVar(
bound_typevar.normalized_impl(db, visitor),
class.normalized_impl(db, visitor),
),
}
}
@@ -218,6 +235,10 @@ impl<'db> SuperOwnerKind<'db> {
SuperOwnerKind::Instance(instance) => Some(SuperOwnerKind::Instance(
instance.recursive_type_normalized_impl(db, div, nested)?,
)),
SuperOwnerKind::InstanceTypeVar(_, _) | SuperOwnerKind::ClassTypeVar(_, _) => {
// TODO: we might need to normalize the nested class here?
Some(self)
}
}
}
@@ -228,6 +249,9 @@ impl<'db> SuperOwnerKind<'db> {
}
SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)),
SuperOwnerKind::Instance(instance) => Either::Right(instance.class(db).iter_mro(db)),
SuperOwnerKind::InstanceTypeVar(_, class) | SuperOwnerKind::ClassTypeVar(_, class) => {
Either::Right(class.iter_mro(db))
}
}
}
@@ -236,16 +260,31 @@ impl<'db> SuperOwnerKind<'db> {
SuperOwnerKind::Dynamic(_) => None,
SuperOwnerKind::Class(class) => Some(class),
SuperOwnerKind::Instance(instance) => Some(instance.class(db)),
SuperOwnerKind::InstanceTypeVar(_, class) | SuperOwnerKind::ClassTypeVar(_, class) => {
Some(class)
}
}
}
}
impl<'db> From<SuperOwnerKind<'db>> for Type<'db> {
fn from(owner: SuperOwnerKind<'db>) -> Self {
match owner {
/// Returns the `TypeVar` instance if this owner is a `TypeVar` variant.
fn typevar(self, db: &'db dyn Db) -> Option<TypeVarInstance<'db>> {
match self {
SuperOwnerKind::InstanceTypeVar(bound_typevar, _)
| SuperOwnerKind::ClassTypeVar(bound_typevar, _) => Some(bound_typevar.typevar(db)),
_ => None,
}
}
/// Returns the type representation of this owner.
pub(super) fn owner_type(self, db: &'db dyn Db) -> Type<'db> {
match self {
SuperOwnerKind::Dynamic(dynamic) => Type::Dynamic(dynamic),
SuperOwnerKind::Class(class) => class.into(),
SuperOwnerKind::Instance(instance) => instance.into(),
SuperOwnerKind::InstanceTypeVar(bound_typevar, _) => Type::TypeVar(bound_typevar),
SuperOwnerKind::ClassTypeVar(bound_typevar, _) => {
SubclassOfType::from(db, SubclassOfInner::TypeVar(bound_typevar))
}
}
}
}
@@ -266,7 +305,7 @@ pub(super) fn walk_bound_super_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
visitor: &V,
) {
visitor.visit_type(db, Type::from(bound_super.pivot_class(db)));
visitor.visit_type(db, Type::from(bound_super.owner(db)));
visitor.visit_type(db, bound_super.owner(db).owner_type(db));
}
impl<'db> BoundSuperType<'db> {
@@ -285,6 +324,7 @@ impl<'db> BoundSuperType<'db> {
let delegate_to =
|type_to_delegate_to| BoundSuperType::build(db, pivot_class_type, type_to_delegate_to);
// Delegate but rewrite errors to preserve TypeVar context.
let delegate_with_error_mapped =
|type_to_delegate_to, error_context: Option<TypeVarInstance<'db>>| {
delegate_to(type_to_delegate_to).map_err(|err| match err {
@@ -315,19 +355,128 @@ impl<'db> BoundSuperType<'db> {
})
};
// 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::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
SubclassOfInner::Dynamic(dynamic) => ClassBase::Dynamic(dynamic),
_ => match subclass_of.subclass_of().into_class(db) {
Some(class) => ClassBase::Class(class),
None => {
return Err(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,
});
}
};
// Helper to build a union of bound-super instances for constrained TypeVars.
// Each constraint must be a subclass of the pivot class.
let build_constrained_union =
|constraints: TypeVarConstraints<'db>,
bound_typevar: BoundTypeVarInstance<'db>,
typevar: TypeVarInstance<'db>,
make_owner: fn(BoundTypeVarInstance<'db>, ClassType<'db>) -> SuperOwnerKind<'db>|
-> Result<Type<'db>, BoundSuperError<'db>> {
let pivot_class_literal = pivot_class.into_class().map(|c| c.class_literal(db).0);
let mut builder = UnionBuilder::new(db);
for constraint in constraints.elements(db) {
let class = match constraint {
Type::NominalInstance(instance) => Some(instance.class(db)),
_ => constraint.to_class_type(db),
};
match class {
Some(class) => {
// Validate constraint is a subclass of pivot class.
if let Some(pivot) = pivot_class_literal {
if !class.iter_mro(db).any(|superclass| match superclass {
ClassBase::Dynamic(_) => true,
ClassBase::Generic
| ClassBase::Protocol
| ClassBase::TypedDict => false,
ClassBase::Class(superclass) => {
superclass.class_literal(db).0 == pivot
}
}) {
return Err(BoundSuperError::FailingConditionCheck {
pivot_class: pivot_class_type,
owner: owner_type,
typevar_context: Some(typevar),
});
}
}
let owner = make_owner(bound_typevar, class);
builder = builder.add(Type::BoundSuper(BoundSuperType::new(
db,
pivot_class,
owner,
)));
}
None => {
// Delegate to the constraint to get better error messages
// if the constraint is incompatible with the pivot class.
builder = builder.add(delegate_to(*constraint)?);
}
}
}
Ok(builder.build())
};
let owner = match owner_type {
Type::Never => SuperOwnerKind::Dynamic(DynamicType::Unknown),
Type::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic),
Type::ClassLiteral(class) => SuperOwnerKind::Class(ClassType::NonGeneric(class)),
Type::SubclassOf(subclass_of_type) => {
match subclass_of_type.subclass_of().with_transposed_type_var(db) {
SubclassOfInner::Class(class) => SuperOwnerKind::Class(class),
SubclassOfInner::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic),
SubclassOfInner::TypeVar(bound_typevar) => {
return delegate_to(Type::TypeVar(bound_typevar));
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
SubclassOfInner::Class(class) => SuperOwnerKind::Class(class),
SubclassOfInner::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic),
SubclassOfInner::TypeVar(bound_typevar) => {
let typevar = bound_typevar.typevar(db);
match typevar.bound_or_constraints(db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
let class = match bound {
Type::NominalInstance(instance) => Some(instance.class(db)),
Type::ProtocolInstance(protocol) => protocol
.to_nominal_instance()
.map(|instance| instance.class(db)),
_ => None,
};
if let Some(class) = class {
SuperOwnerKind::ClassTypeVar(bound_typevar, class)
} else {
let subclass_of = SubclassOfType::try_from_instance(db, bound)
.unwrap_or_else(SubclassOfType::subclass_of_unknown);
return delegate_with_error_mapped(subclass_of, Some(typevar));
}
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
return build_constrained_union(
constraints,
bound_typevar,
typevar,
SuperOwnerKind::ClassTypeVar,
);
}
None => {
// No bound means the implicit upper bound is `object`.
SuperOwnerKind::ClassTypeVar(bound_typevar, ClassType::object(db))
}
}
}
}
},
Type::NominalInstance(instance) => SuperOwnerKind::Instance(instance),
Type::ProtocolInstance(protocol) => {
@@ -375,19 +524,38 @@ impl<'db> BoundSuperType<'db> {
return Ok(builder.build());
}
Type::TypeAlias(alias) => {
return delegate_with_error_mapped(alias.value_type(db), None);
return delegate_to(alias.value_type(db));
}
Type::TypeVar(type_var) => {
let type_var = type_var.typevar(db);
return match type_var.bound_or_constraints(db) {
Type::TypeVar(bound_typevar) => {
let typevar = bound_typevar.typevar(db);
match typevar.bound_or_constraints(db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
delegate_with_error_mapped(bound, Some(type_var))
let class = match bound {
Type::NominalInstance(instance) => Some(instance.class(db)),
Type::ProtocolInstance(protocol) => protocol
.to_nominal_instance()
.map(|instance| instance.class(db)),
_ => None,
};
if let Some(class) = class {
SuperOwnerKind::InstanceTypeVar(bound_typevar, class)
} else {
return delegate_with_error_mapped(bound, Some(typevar));
}
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
delegate_with_error_mapped(constraints.as_type(db), Some(type_var))
return build_constrained_union(
constraints,
bound_typevar,
typevar,
SuperOwnerKind::InstanceTypeVar,
);
}
None => delegate_with_error_mapped(Type::object(), Some(type_var)),
};
None => {
// No bound means the implicit upper bound is `object`.
SuperOwnerKind::InstanceTypeVar(bound_typevar, ClassType::object(db))
}
}
}
Type::BooleanLiteral(_) | Type::TypeIs(_) | Type::TypeGuard(_) => {
return delegate_to(KnownClass::Bool.to_instance(db));
@@ -456,35 +624,6 @@ impl<'db> BoundSuperType<'db> {
}
};
// 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::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
SubclassOfInner::Dynamic(dynamic) => ClassBase::Dynamic(dynamic),
_ => match subclass_of.subclass_of().into_class(db) {
Some(class) => ClassBase::Class(class),
None => {
return Err(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,
});
}
};
if let Some(pivot_class) = pivot_class.into_class()
&& let Some(owner_class) = owner.into_class(db)
{
@@ -497,7 +636,7 @@ impl<'db> BoundSuperType<'db> {
return Err(BoundSuperError::FailingConditionCheck {
pivot_class: pivot_class_type,
owner: owner_type,
typevar_context: None,
typevar_context: owner.typevar(db),
});
}
}
@@ -556,18 +695,30 @@ impl<'db> BoundSuperType<'db> {
db,
attribute,
Type::none(db),
Type::from(owner),
owner.owner_type(db),
)
.0,
),
SuperOwnerKind::Instance(_) => {
let owner = Type::from(owner);
SuperOwnerKind::Instance(_) | SuperOwnerKind::InstanceTypeVar(..) => {
let owner_type = owner.owner_type(db);
Some(
Type::try_call_dunder_get_on_attribute(
db,
attribute,
owner,
owner.to_meta_type(db),
owner_type,
owner_type.to_meta_type(db),
)
.0,
)
}
SuperOwnerKind::ClassTypeVar(..) => {
let owner_type = owner.owner_type(db);
Some(
Type::try_call_dunder_get_on_attribute(
db,
attribute,
Type::none(db),
owner_type,
)
.0,
)
@@ -592,6 +743,9 @@ impl<'db> BoundSuperType<'db> {
}
SuperOwnerKind::Class(class) => class,
SuperOwnerKind::Instance(instance) => instance.class(db),
SuperOwnerKind::InstanceTypeVar(_, class) | SuperOwnerKind::ClassTypeVar(_, class) => {
class
}
};
let (class_literal, _) = class.class_literal(db);

View File

@@ -988,7 +988,9 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
.display_with(self.db, self.settings.singleline())
.fmt_detailed(f)?;
f.write_str(", ")?;
Type::from(bound_super.owner(self.db))
bound_super
.owner(self.db)
.owner_type(self.db)
.display_with(self.db, self.settings.singleline())
.fmt_detailed(f)?;
f.write_str(">")

View File

@@ -189,6 +189,17 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
}
(SuperOwnerKind::Instance(_), _) => Ordering::Less,
(_, SuperOwnerKind::Instance(_)) => Ordering::Greater,
(
SuperOwnerKind::InstanceTypeVar(left, _),
SuperOwnerKind::InstanceTypeVar(right, _),
) => left.cmp(&right),
(SuperOwnerKind::InstanceTypeVar(..), _) => Ordering::Less,
(_, SuperOwnerKind::InstanceTypeVar(..)) => Ordering::Greater,
(SuperOwnerKind::ClassTypeVar(left, _), SuperOwnerKind::ClassTypeVar(right, _)) => {
left.cmp(&right)
}
(SuperOwnerKind::ClassTypeVar(..), _) => Ordering::Less,
(_, SuperOwnerKind::ClassTypeVar(..)) => Ordering::Greater,
(SuperOwnerKind::Dynamic(left), SuperOwnerKind::Dynamic(right)) => {
dynamic_elements_ordering(left, right)
}