[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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(">")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user