mirror of https://github.com/astral-sh/ruff
[ty] Support `type[T]` with type variables (#21650)
## Summary Adds support for `type[T]`, where `T` is a type variable. - Resolves https://github.com/astral-sh/ty/issues/501 - Resolves https://github.com/astral-sh/ty/issues/783 - Resolves https://github.com/astral-sh/ty/issues/662
This commit is contained in:
parent
53efc82989
commit
3ed537e9f1
|
|
@ -2898,7 +2898,7 @@ Answer.<CURSOR>
|
||||||
__itemsize__ :: int
|
__itemsize__ :: int
|
||||||
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT@__iter__]
|
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT@__iter__]
|
||||||
__len__ :: bound method <class 'Answer'>.__len__() -> int
|
__len__ :: bound method <class 'Answer'>.__len__() -> int
|
||||||
__members__ :: MappingProxyType[str, Unknown]
|
__members__ :: MappingProxyType[str, Answer]
|
||||||
__module__ :: str
|
__module__ :: str
|
||||||
__mro__ :: tuple[type, ...]
|
__mro__ :: tuple[type, ...]
|
||||||
__name__ :: str
|
__name__ :: str
|
||||||
|
|
|
||||||
|
|
@ -260,15 +260,13 @@ class Shape:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def bar(cls: type[Self]) -> Self:
|
def bar(cls: type[Self]) -> Self:
|
||||||
# TODO: type[Shape]
|
reveal_type(cls) # revealed: type[Self@bar]
|
||||||
reveal_type(cls) # revealed: @Todo(unsupported type[X] special form)
|
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
class Circle(Shape): ...
|
class Circle(Shape): ...
|
||||||
|
|
||||||
reveal_type(Shape().foo()) # revealed: Shape
|
reveal_type(Shape().foo()) # revealed: Shape
|
||||||
# TODO: Shape
|
reveal_type(Shape.bar()) # revealed: Shape
|
||||||
reveal_type(Shape.bar()) # revealed: Unknown
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
|
||||||
|
|
@ -2650,7 +2650,7 @@ reveal_type(C().x) # revealed: int
|
||||||
```py
|
```py
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
reveal_type(enum.Enum.__members__) # revealed: MappingProxyType[str, Unknown]
|
reveal_type(enum.Enum.__members__) # revealed: MappingProxyType[str, Enum]
|
||||||
|
|
||||||
class Answer(enum.Enum):
|
class Answer(enum.Enum):
|
||||||
NO = 0
|
NO = 0
|
||||||
|
|
@ -2658,7 +2658,7 @@ class Answer(enum.Enum):
|
||||||
|
|
||||||
reveal_type(Answer.NO) # revealed: Literal[Answer.NO]
|
reveal_type(Answer.NO) # revealed: Literal[Answer.NO]
|
||||||
reveal_type(Answer.NO.value) # revealed: Literal[0]
|
reveal_type(Answer.NO.value) # revealed: Literal[0]
|
||||||
reveal_type(Answer.__members__) # revealed: MappingProxyType[str, Unknown]
|
reveal_type(Answer.__members__) # revealed: MappingProxyType[str, Answer]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Divergent inferred implicit instance attribute types
|
## Divergent inferred implicit instance attribute types
|
||||||
|
|
|
||||||
|
|
@ -210,9 +210,7 @@ class BuilderMeta2(type):
|
||||||
) -> BuilderMeta2:
|
) -> BuilderMeta2:
|
||||||
# revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
# revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
||||||
s = reveal_type(super())
|
s = reveal_type(super())
|
||||||
# TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
|
return reveal_type(s.__new__(cls, name, bases, dct)) # revealed: BuilderMeta2
|
||||||
# revealed: Unknown
|
|
||||||
return reveal_type(s.__new__(cls, name, bases, dct))
|
|
||||||
|
|
||||||
class Foo[T]:
|
class Foo[T]:
|
||||||
x: T
|
x: T
|
||||||
|
|
@ -395,6 +393,14 @@ class E(Enum):
|
||||||
reveal_type(super(E, E.X)) # revealed: <super: <class 'E'>, E>
|
reveal_type(super(E, E.X)) # revealed: <super: <class 'E'>, E>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `type[Self]`
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Foo:
|
||||||
|
def method(self):
|
||||||
|
super(self.__class__, self)
|
||||||
|
```
|
||||||
|
|
||||||
## Descriptor Behavior with Super
|
## Descriptor Behavior with Super
|
||||||
|
|
||||||
Accessing attributes through `super` still invokes descriptor protocol. However, the behavior can
|
Accessing attributes through `super` still invokes descriptor protocol. However, the behavior can
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,8 @@ reveal_type(Color.RED) # revealed: Literal[Color.RED]
|
||||||
reveal_type(Color.RED.name) # revealed: Literal["RED"]
|
reveal_type(Color.RED.name) # revealed: Literal["RED"]
|
||||||
reveal_type(Color.RED.value) # revealed: Literal[1]
|
reveal_type(Color.RED.value) # revealed: Literal[1]
|
||||||
|
|
||||||
# TODO: Should be `Color` or `Literal[Color.RED]`
|
|
||||||
reveal_type(Color["RED"]) # revealed: Unknown
|
|
||||||
|
|
||||||
# TODO: Could be `Literal[Color.RED]` to be more precise
|
# TODO: Could be `Literal[Color.RED]` to be more precise
|
||||||
|
reveal_type(Color["RED"]) # revealed: Color
|
||||||
reveal_type(Color(1)) # revealed: Color
|
reveal_type(Color(1)) # revealed: Color
|
||||||
|
|
||||||
reveal_type(Color.RED in Color) # revealed: bool
|
reveal_type(Color.RED in Color) # revealed: bool
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
|
||||||
def takes_in_type(x: type[T]) -> type[T]:
|
def takes_in_type(x: type[T]) -> type[T]:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
|
reveal_type(takes_in_type(int)) # revealed: type[int]
|
||||||
```
|
```
|
||||||
|
|
||||||
This also works when passing in arguments that are subclasses of the parameter type.
|
This also works when passing in arguments that are subclasses of the parameter type.
|
||||||
|
|
|
||||||
|
|
@ -383,8 +383,7 @@ def constrained(f: T):
|
||||||
|
|
||||||
## Meta-type
|
## Meta-type
|
||||||
|
|
||||||
The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the
|
The meta-type of a typevar is `type[T]`.
|
||||||
meta-types of the constraints:
|
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
@ -392,22 +391,22 @@ from typing import TypeVar
|
||||||
T_normal = TypeVar("T_normal")
|
T_normal = TypeVar("T_normal")
|
||||||
|
|
||||||
def normal(x: T_normal):
|
def normal(x: T_normal):
|
||||||
reveal_type(type(x)) # revealed: type
|
reveal_type(type(x)) # revealed: type[T_normal@normal]
|
||||||
|
|
||||||
T_bound_object = TypeVar("T_bound_object", bound=object)
|
T_bound_object = TypeVar("T_bound_object", bound=object)
|
||||||
|
|
||||||
def bound_object(x: T_bound_object):
|
def bound_object(x: T_bound_object):
|
||||||
reveal_type(type(x)) # revealed: type
|
reveal_type(type(x)) # revealed: type[T_bound_object@bound_object]
|
||||||
|
|
||||||
T_bound_int = TypeVar("T_bound_int", bound=int)
|
T_bound_int = TypeVar("T_bound_int", bound=int)
|
||||||
|
|
||||||
def bound_int(x: T_bound_int):
|
def bound_int(x: T_bound_int):
|
||||||
reveal_type(type(x)) # revealed: type[int]
|
reveal_type(type(x)) # revealed: type[T_bound_int@bound_int]
|
||||||
|
|
||||||
T_constrained = TypeVar("T_constrained", int, str)
|
T_constrained = TypeVar("T_constrained", int, str)
|
||||||
|
|
||||||
def constrained(x: T_constrained):
|
def constrained(x: T_constrained):
|
||||||
reveal_type(type(x)) # revealed: type[int] | type[str]
|
reveal_type(type(x)) # revealed: type[T_constrained@constrained]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cycles
|
## Cycles
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
|
||||||
def takes_in_type[T](x: type[T]) -> type[T]:
|
def takes_in_type[T](x: type[T]) -> type[T]:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
|
reveal_type(takes_in_type(int)) # revealed: type[int]
|
||||||
```
|
```
|
||||||
|
|
||||||
This also works when passing in arguments that are subclasses of the parameter type.
|
This also works when passing in arguments that are subclasses of the parameter type.
|
||||||
|
|
|
||||||
|
|
@ -754,21 +754,20 @@ def constrained[T: (Callable[[], int], Callable[[], str])](f: T):
|
||||||
|
|
||||||
## Meta-type
|
## Meta-type
|
||||||
|
|
||||||
The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the
|
The meta-type of a typevar is `type[T]`.
|
||||||
meta-types of the constraints:
|
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def normal[T](x: T):
|
def normal[T](x: T):
|
||||||
reveal_type(type(x)) # revealed: type
|
reveal_type(type(x)) # revealed: type[T@normal]
|
||||||
|
|
||||||
def bound_object[T: object](x: T):
|
def bound_object[T: object](x: T):
|
||||||
reveal_type(type(x)) # revealed: type
|
reveal_type(type(x)) # revealed: type[T@bound_object]
|
||||||
|
|
||||||
def bound_int[T: int](x: T):
|
def bound_int[T: int](x: T):
|
||||||
reveal_type(type(x)) # revealed: type[int]
|
reveal_type(type(x)) # revealed: type[T@bound_int]
|
||||||
|
|
||||||
def constrained[T: (int, str)](x: T):
|
def constrained[T: (int, str)](x: T):
|
||||||
reveal_type(type(x)) # revealed: type[int] | type[str]
|
reveal_type(type(x)) # revealed: type[T@constrained]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cycles
|
## Cycles
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,11 @@ static_assert(not has_member(C(), "non_existent"))
|
||||||
|
|
||||||
### Class objects
|
### Class objects
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
Class-level attributes can also be accessed through the class itself:
|
Class-level attributes can also be accessed through the class itself:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
@ -154,7 +159,13 @@ static_assert(has_member(D, "meta_attr"))
|
||||||
static_assert(has_member(D, "base_attr"))
|
static_assert(has_member(D, "base_attr"))
|
||||||
static_assert(has_member(D, "class_attr"))
|
static_assert(has_member(D, "class_attr"))
|
||||||
|
|
||||||
def f(x: type[D]):
|
def _(x: type[D]):
|
||||||
|
static_assert(has_member(x, "meta_base_attr"))
|
||||||
|
static_assert(has_member(x, "meta_attr"))
|
||||||
|
static_assert(has_member(x, "base_attr"))
|
||||||
|
static_assert(has_member(x, "class_attr"))
|
||||||
|
|
||||||
|
def _[T: D](x: type[T]):
|
||||||
static_assert(has_member(x, "meta_base_attr"))
|
static_assert(has_member(x, "meta_base_attr"))
|
||||||
static_assert(has_member(x, "meta_attr"))
|
static_assert(has_member(x, "meta_attr"))
|
||||||
static_assert(has_member(x, "base_attr"))
|
static_assert(has_member(x, "base_attr"))
|
||||||
|
|
|
||||||
|
|
@ -255,12 +255,10 @@ And it is also an error to use `Protocol` in type expressions:
|
||||||
|
|
||||||
def f(
|
def f(
|
||||||
x: Protocol, # error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions"
|
x: Protocol, # error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions"
|
||||||
y: type[Protocol], # TODO: should emit `[invalid-type-form]` here too
|
y: type[Protocol], # error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions"
|
||||||
):
|
):
|
||||||
reveal_type(x) # revealed: Unknown
|
reveal_type(x) # revealed: Unknown
|
||||||
|
reveal_type(y) # revealed: type[Unknown]
|
||||||
# TODO: should be `type[Unknown]`
|
|
||||||
reveal_type(y) # revealed: @Todo(unsupported type[X] special form)
|
|
||||||
|
|
||||||
# fmt: on
|
# fmt: on
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -58,106 +58,104 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
|
||||||
44 | ) -> BuilderMeta2:
|
44 | ) -> BuilderMeta2:
|
||||||
45 | # revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
45 | # revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
||||||
46 | s = reveal_type(super())
|
46 | s = reveal_type(super())
|
||||||
47 | # TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
|
47 | return reveal_type(s.__new__(cls, name, bases, dct)) # revealed: BuilderMeta2
|
||||||
48 | # revealed: Unknown
|
48 |
|
||||||
49 | return reveal_type(s.__new__(cls, name, bases, dct))
|
49 | class Foo[T]:
|
||||||
50 |
|
50 | x: T
|
||||||
51 | class Foo[T]:
|
51 |
|
||||||
52 | x: T
|
52 | def method(self: Any):
|
||||||
53 |
|
53 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||||
54 | def method(self: Any):
|
54 |
|
||||||
55 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
55 | if isinstance(self, Foo):
|
||||||
56 |
|
56 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||||
57 | if isinstance(self, Foo):
|
57 |
|
||||||
58 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
58 | def method2(self: Foo[T]):
|
||||||
59 |
|
59 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||||
60 | def method2(self: Foo[T]):
|
60 | reveal_type(super())
|
||||||
61 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
61 |
|
||||||
62 | reveal_type(super())
|
62 | def method3(self: Foo):
|
||||||
63 |
|
63 | # revealed: <super: <class 'Foo'>, Foo[Unknown]>
|
||||||
64 | def method3(self: Foo):
|
64 | reveal_type(super())
|
||||||
65 | # revealed: <super: <class 'Foo'>, Foo[Unknown]>
|
65 |
|
||||||
66 | reveal_type(super())
|
66 | def method4(self: Self):
|
||||||
67 |
|
67 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||||
68 | def method4(self: Self):
|
68 | reveal_type(super())
|
||||||
69 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
69 |
|
||||||
70 | reveal_type(super())
|
70 | def method5[S: Foo[int]](self: S, other: S) -> S:
|
||||||
71 |
|
71 | # revealed: <super: <class 'Foo'>, Foo[int]>
|
||||||
72 | def method5[S: Foo[int]](self: S, other: S) -> S:
|
72 | reveal_type(super())
|
||||||
73 | # revealed: <super: <class 'Foo'>, Foo[int]>
|
73 | return self
|
||||||
74 | reveal_type(super())
|
74 |
|
||||||
75 | return self
|
75 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
|
||||||
76 |
|
76 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
|
||||||
77 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
|
77 | reveal_type(super())
|
||||||
78 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
|
78 | return self
|
||||||
79 | reveal_type(super())
|
79 |
|
||||||
80 | return self
|
80 | def method7[S](self: S, other: S) -> S:
|
||||||
81 |
|
81 | # error: [invalid-super-argument]
|
||||||
82 | def method7[S](self: S, other: S) -> S:
|
82 | # revealed: Unknown
|
||||||
83 | # error: [invalid-super-argument]
|
83 | reveal_type(super())
|
||||||
84 | # revealed: Unknown
|
84 | return self
|
||||||
85 | reveal_type(super())
|
85 |
|
||||||
86 | return self
|
86 | def method8[S: int](self: S, other: S) -> S:
|
||||||
87 |
|
87 | # error: [invalid-super-argument]
|
||||||
88 | def method8[S: int](self: S, other: S) -> S:
|
88 | # revealed: Unknown
|
||||||
89 | # error: [invalid-super-argument]
|
89 | reveal_type(super())
|
||||||
90 | # revealed: Unknown
|
90 | return self
|
||||||
91 | reveal_type(super())
|
91 |
|
||||||
92 | return self
|
92 | def method9[S: (int, str)](self: S, other: S) -> S:
|
||||||
93 |
|
93 | # error: [invalid-super-argument]
|
||||||
94 | def method9[S: (int, str)](self: S, other: S) -> S:
|
94 | # revealed: Unknown
|
||||||
95 | # error: [invalid-super-argument]
|
95 | reveal_type(super())
|
||||||
96 | # revealed: Unknown
|
96 | return self
|
||||||
97 | reveal_type(super())
|
97 |
|
||||||
98 | return self
|
98 | def method10[S: Callable[..., str]](self: S, other: S) -> S:
|
||||||
99 |
|
99 | # error: [invalid-super-argument]
|
||||||
100 | def method10[S: Callable[..., str]](self: S, other: S) -> S:
|
100 | # revealed: Unknown
|
||||||
101 | # error: [invalid-super-argument]
|
101 | reveal_type(super())
|
||||||
102 | # revealed: Unknown
|
102 | return self
|
||||||
103 | reveal_type(super())
|
103 |
|
||||||
104 | return self
|
104 | type Alias = Bar
|
||||||
105 |
|
105 |
|
||||||
106 | type Alias = Bar
|
106 | class Bar:
|
||||||
107 |
|
107 | def method(self: Alias):
|
||||||
108 | class Bar:
|
108 | # revealed: <super: <class 'Bar'>, Bar>
|
||||||
109 | def method(self: Alias):
|
109 | reveal_type(super())
|
||||||
110 | # revealed: <super: <class 'Bar'>, Bar>
|
110 |
|
||||||
111 | reveal_type(super())
|
111 | def pls_dont_call_me(self: Never):
|
||||||
112 |
|
112 | # revealed: <super: <class 'Bar'>, Unknown>
|
||||||
113 | def pls_dont_call_me(self: Never):
|
113 | reveal_type(super())
|
||||||
114 | # revealed: <super: <class 'Bar'>, Unknown>
|
114 |
|
||||||
115 | reveal_type(super())
|
115 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
|
||||||
116 |
|
116 | # revealed: <super: <class 'Bar'>, Bar>
|
||||||
117 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
|
117 | reveal_type(super())
|
||||||
118 | # revealed: <super: <class 'Bar'>, Bar>
|
118 |
|
||||||
119 | reveal_type(super())
|
119 | class P(Protocol):
|
||||||
120 |
|
120 | def method(self: P):
|
||||||
121 | class P(Protocol):
|
121 | # revealed: <super: <class 'P'>, P>
|
||||||
122 | def method(self: P):
|
122 | reveal_type(super())
|
||||||
123 | # revealed: <super: <class 'P'>, P>
|
123 |
|
||||||
124 | reveal_type(super())
|
124 | class E(enum.Enum):
|
||||||
125 |
|
125 | X = 1
|
||||||
126 | class E(enum.Enum):
|
126 |
|
||||||
127 | X = 1
|
127 | def method(self: E):
|
||||||
128 |
|
128 | match self:
|
||||||
129 | def method(self: E):
|
129 | case E.X:
|
||||||
130 | match self:
|
130 | # revealed: <super: <class 'E'>, E>
|
||||||
131 | case E.X:
|
131 | reveal_type(super())
|
||||||
132 | # revealed: <super: <class 'E'>, E>
|
|
||||||
133 | reveal_type(super())
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-super-argument]: `S@method7` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method7)` call
|
error[invalid-super-argument]: `S@method7` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method7)` call
|
||||||
--> src/mdtest_snippet.py:85:21
|
--> src/mdtest_snippet.py:83:21
|
||||||
|
|
|
|
||||||
83 | # error: [invalid-super-argument]
|
81 | # error: [invalid-super-argument]
|
||||||
84 | # revealed: Unknown
|
82 | # revealed: Unknown
|
||||||
85 | reveal_type(super())
|
83 | reveal_type(super())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
86 | return self
|
84 | return self
|
||||||
|
|
|
|
||||||
info: Type variable `S` has `object` as its implicit upper bound
|
info: Type variable `S` has `object` as its implicit upper bound
|
||||||
info: `object` is not an instance or subclass of `<class 'Foo'>`
|
info: `object` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
|
@ -168,13 +166,13 @@ info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-super-argument]: `S@method8` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method8)` call
|
error[invalid-super-argument]: `S@method8` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method8)` call
|
||||||
--> src/mdtest_snippet.py:91:21
|
--> src/mdtest_snippet.py:89:21
|
||||||
|
|
|
|
||||||
89 | # error: [invalid-super-argument]
|
87 | # error: [invalid-super-argument]
|
||||||
90 | # revealed: Unknown
|
88 | # revealed: Unknown
|
||||||
91 | reveal_type(super())
|
89 | reveal_type(super())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
92 | return self
|
90 | return self
|
||||||
|
|
|
|
||||||
info: Type variable `S` has upper bound `int`
|
info: Type variable `S` has upper bound `int`
|
||||||
info: `int` is not an instance or subclass of `<class 'Foo'>`
|
info: `int` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
|
@ -184,13 +182,13 @@ info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-super-argument]: `S@method9` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method9)` call
|
error[invalid-super-argument]: `S@method9` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method9)` call
|
||||||
--> src/mdtest_snippet.py:97:21
|
--> src/mdtest_snippet.py:95:21
|
||||||
|
|
|
|
||||||
95 | # error: [invalid-super-argument]
|
93 | # error: [invalid-super-argument]
|
||||||
96 | # revealed: Unknown
|
94 | # revealed: Unknown
|
||||||
97 | reveal_type(super())
|
95 | reveal_type(super())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
98 | return self
|
96 | return self
|
||||||
|
|
|
|
||||||
info: Type variable `S` has constraints `int, str`
|
info: Type variable `S` has constraints `int, str`
|
||||||
info: `int | str` is not an instance or subclass of `<class 'Foo'>`
|
info: `int | str` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
|
@ -200,13 +198,13 @@ info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-super-argument]: `S@method10` is a type variable with an abstract/structural type as its bounds or constraints, in `super(<class 'Foo'>, S@method10)` call
|
error[invalid-super-argument]: `S@method10` is a type variable with an abstract/structural type as its bounds or constraints, in `super(<class 'Foo'>, S@method10)` call
|
||||||
--> src/mdtest_snippet.py:103:21
|
--> src/mdtest_snippet.py:101:21
|
||||||
|
|
|
|
||||||
101 | # error: [invalid-super-argument]
|
99 | # error: [invalid-super-argument]
|
||||||
102 | # revealed: Unknown
|
100 | # revealed: Unknown
|
||||||
103 | reveal_type(super())
|
101 | reveal_type(super())
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
104 | return self
|
102 | return self
|
||||||
|
|
|
|
||||||
info: Type variable `S` has upper bound `(...) -> str`
|
info: Type variable `S` has upper bound `(...) -> str`
|
||||||
info: rule `invalid-super-argument` is enabled by default
|
info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
# `type[T]`
|
||||||
|
|
||||||
|
`type[T]` with a type variable represents the class objects of `T`.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.13"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic
|
||||||
|
|
||||||
|
The meta-type of a typevar is `type[T]`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def _[T](x: T):
|
||||||
|
reveal_type(type(x)) # revealed: type[T@_]
|
||||||
|
```
|
||||||
|
|
||||||
|
`type[T]` with an unbounded type variable represents any subclass of `object`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def unbounded[T](x: type[T]) -> T:
|
||||||
|
reveal_type(x) # revealed: type[T@unbounded]
|
||||||
|
reveal_type(x.__repr__) # revealed: def __repr__(self) -> str
|
||||||
|
reveal_type(x.__init__) # revealed: def __init__(self) -> None
|
||||||
|
reveal_type(x.__qualname__) # revealed: str
|
||||||
|
reveal_type(x()) # revealed: T@unbounded
|
||||||
|
|
||||||
|
return x()
|
||||||
|
```
|
||||||
|
|
||||||
|
`type[T]` with an upper bound of `T: A` represents any subclass of `A`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
class A:
|
||||||
|
x: str
|
||||||
|
|
||||||
|
def __init__(self, value: str): ...
|
||||||
|
|
||||||
|
class B(A): ...
|
||||||
|
class C: ...
|
||||||
|
|
||||||
|
def upper_bound[T: A](x: type[T]) -> T:
|
||||||
|
reveal_type(x) # revealed: type[T@upper_bound]
|
||||||
|
reveal_type(x.__qualname__) # revealed: str
|
||||||
|
reveal_type(x("hello")) # revealed: T@upper_bound
|
||||||
|
|
||||||
|
return x("hello")
|
||||||
|
|
||||||
|
reveal_type(upper_bound(A)) # revealed: A
|
||||||
|
reveal_type(upper_bound(B)) # revealed: B
|
||||||
|
|
||||||
|
# error: [invalid-argument-type] "Argument to function `upper_bound` is incorrect: Argument type `C` does not satisfy upper bound `A` of type variable `T`"
|
||||||
|
upper_bound(C)
|
||||||
|
```
|
||||||
|
|
||||||
|
`type[T]` with a constraints `T: (A, B)` represents exactly the class object `A`, or exactly `B`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def constrained[T: (int, str)](x: type[T]) -> T:
|
||||||
|
reveal_type(x) # revealed: type[T@constrained]
|
||||||
|
reveal_type(x.__qualname__) # revealed: str
|
||||||
|
reveal_type(x("hello")) # revealed: T@constrained
|
||||||
|
|
||||||
|
return x("hello")
|
||||||
|
|
||||||
|
reveal_type(constrained(int)) # revealed: int
|
||||||
|
reveal_type(constrained(str)) # revealed: str
|
||||||
|
|
||||||
|
# error: [invalid-argument-type] "Argument to function `constrained` is incorrect: Argument type `A` does not satisfy constraints (`int`, `str`) of type variable `T`"
|
||||||
|
constrained(A)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Union
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import Intersection, Unknown
|
||||||
|
|
||||||
|
def _[T: int](x: type | type[T]):
|
||||||
|
reveal_type(x()) # revealed: Any
|
||||||
|
|
||||||
|
def _[T: int](x: type[int] | type[T]):
|
||||||
|
reveal_type(x()) # revealed: int
|
||||||
|
|
||||||
|
def _[T](x: type[int] | type[T]):
|
||||||
|
reveal_type(x()) # revealed: int | T@_
|
||||||
|
```
|
||||||
|
|
||||||
|
## Narrowing
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
class A: ...
|
||||||
|
|
||||||
|
def narrow_a[B: A](a: A, b: B):
|
||||||
|
type_of_a = type(a)
|
||||||
|
|
||||||
|
reveal_type(a) # revealed: A
|
||||||
|
reveal_type(type_of_a) # revealed: type[A]
|
||||||
|
|
||||||
|
if isinstance(a, type(b)):
|
||||||
|
reveal_type(a) # revealed: B@narrow_a
|
||||||
|
|
||||||
|
if issubclass(type_of_a, type(b)):
|
||||||
|
reveal_type(type_of_a) # revealed: type[B@narrow_a]
|
||||||
|
```
|
||||||
|
|
||||||
|
## `__class__`
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
class A:
|
||||||
|
def copy(self: Self) -> Self:
|
||||||
|
reveal_type(self.__class__) # revealed: type[Self@copy]
|
||||||
|
reveal_type(self.__class__()) # revealed: Self@copy
|
||||||
|
return self.__class__()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subtyping
|
||||||
|
|
||||||
|
A class `A` is a subtype of `type[T]` if any instance of `A` is a subtype of `T`.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Callable, Protocol
|
||||||
|
from ty_extensions import is_assignable_to, is_subtype_of, is_disjoint_from, static_assert
|
||||||
|
|
||||||
|
class IntCallback(Protocol):
|
||||||
|
def __call__(self, *args, **kwargs) -> int: ...
|
||||||
|
|
||||||
|
def _[T](_: T):
|
||||||
|
static_assert(not is_subtype_of(type[T], T))
|
||||||
|
static_assert(not is_subtype_of(T, type[T]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], T))
|
||||||
|
static_assert(not is_disjoint_from(type[type], type))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[T]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[T]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(type[T], IntCallback))
|
||||||
|
static_assert(not is_disjoint_from(type[T], IntCallback))
|
||||||
|
|
||||||
|
def _[T: int](_: T):
|
||||||
|
static_assert(not is_subtype_of(type[T], T))
|
||||||
|
static_assert(not is_subtype_of(T, type[T]))
|
||||||
|
static_assert(is_disjoint_from(type[T], T))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(type[T], int))
|
||||||
|
static_assert(not is_subtype_of(int, type[T]))
|
||||||
|
static_assert(is_disjoint_from(type[T], int))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(type[int], type[T]))
|
||||||
|
static_assert(is_subtype_of(type[T], type[int]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[int]))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[T]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[T]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(type[T], IntCallback))
|
||||||
|
static_assert(not is_disjoint_from(type[T], IntCallback))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[T] | None))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[T] | None))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[T] | type[float]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[T] | type[float]))
|
||||||
|
|
||||||
|
def _[T: (int, str)](_: T):
|
||||||
|
static_assert(not is_subtype_of(type[T], T))
|
||||||
|
static_assert(not is_subtype_of(T, type[T]))
|
||||||
|
static_assert(is_disjoint_from(type[T], T))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[T]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[T]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(type[T], IntCallback))
|
||||||
|
static_assert(not is_disjoint_from(type[T], IntCallback))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[T] | None))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[T] | None))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[T] | type[float]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[T] | type[float]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(type[T], type[int]))
|
||||||
|
static_assert(not is_subtype_of(type[int], type[T]))
|
||||||
|
static_assert(not is_subtype_of(type[T], type[str]))
|
||||||
|
static_assert(not is_subtype_of(type[str], type[T]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[int]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[str]))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[int] | type[str]))
|
||||||
|
static_assert(is_subtype_of(type[T], type[int | str]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[int | str]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[int] | type[str]))
|
||||||
|
|
||||||
|
def _[T: (int | str, int)](_: T):
|
||||||
|
static_assert(is_subtype_of(type[int], type[T]))
|
||||||
|
static_assert(not is_disjoint_from(type[int], type[T]))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generic Type Inference
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f1[T](x: type[T]) -> type[T]:
|
||||||
|
return x
|
||||||
|
|
||||||
|
reveal_type(f1(int)) # revealed: type[int]
|
||||||
|
reveal_type(f1(object)) # revealed: type
|
||||||
|
|
||||||
|
def f2[T](x: T) -> type[T]:
|
||||||
|
return type(x)
|
||||||
|
|
||||||
|
reveal_type(f2(int(1))) # revealed: type[int]
|
||||||
|
reveal_type(f2(object())) # revealed: type
|
||||||
|
|
||||||
|
# TODO: This should reveal `type[Literal[1]]`.
|
||||||
|
reveal_type(f2(1)) # revealed: type[Unknown]
|
||||||
|
|
||||||
|
def f3[T](x: type[T]) -> T:
|
||||||
|
return x()
|
||||||
|
|
||||||
|
reveal_type(f3(int)) # revealed: int
|
||||||
|
reveal_type(f3(object)) # revealed: object
|
||||||
|
```
|
||||||
|
|
||||||
|
## Default Parameter
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
class Foo[T]: ...
|
||||||
|
|
||||||
|
# TODO: This should not error.
|
||||||
|
# error: [invalid-parameter-default] "Default value of type `<class 'Foo'>` is not assignable to annotated parameter type `type[T@f]`"
|
||||||
|
def f[T: Foo[Any]](x: type[T] = Foo): ...
|
||||||
|
```
|
||||||
|
|
@ -1787,10 +1787,11 @@ impl<'db> Type<'db> {
|
||||||
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
|
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
|
||||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||||
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
|
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
|
||||||
SubclassOfInner::Dynamic(dynamic) => {
|
|
||||||
|
SubclassOfInner::Dynamic(_) | SubclassOfInner::TypeVar(_) => {
|
||||||
Some(CallableTypes::one(CallableType::single(
|
Some(CallableTypes::one(CallableType::single(
|
||||||
db,
|
db,
|
||||||
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
|
Signature::new(Parameters::unknown(), Some(Type::from(subclass_of_ty))),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2075,13 +2076,14 @@ impl<'db> Type<'db> {
|
||||||
//
|
//
|
||||||
// However, there is one exception to this general rule: for any given typevar `T`,
|
// However, there is one exception to this general rule: for any given typevar `T`,
|
||||||
// `T` will always be a subtype of any union containing `T`.
|
// `T` will always be a subtype of any union containing `T`.
|
||||||
// A similar rule applies in reverse to intersection types.
|
|
||||||
(Type::TypeVar(bound_typevar), Type::Union(union))
|
(Type::TypeVar(bound_typevar), Type::Union(union))
|
||||||
if !bound_typevar.is_inferable(db, inferable)
|
if !bound_typevar.is_inferable(db, inferable)
|
||||||
&& union.elements(db).contains(&self) =>
|
&& union.elements(db).contains(&self) =>
|
||||||
{
|
{
|
||||||
ConstraintSet::from(true)
|
ConstraintSet::from(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A similar rule applies in reverse to intersection types.
|
||||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||||
if !bound_typevar.is_inferable(db, inferable)
|
if !bound_typevar.is_inferable(db, inferable)
|
||||||
&& intersection.positive(db).contains(&target) =>
|
&& intersection.positive(db).contains(&target) =>
|
||||||
|
|
@ -2107,6 +2109,45 @@ impl<'db> Type<'db> {
|
||||||
ConstraintSet::from(true)
|
ConstraintSet::from(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `type[T]` is a subtype of the class object `A` if every instance of `T` is a subtype of an instance
|
||||||
|
// of `A`, and vice versa.
|
||||||
|
(Type::SubclassOf(subclass_of), _)
|
||||||
|
if subclass_of.is_type_var()
|
||||||
|
&& !matches!(target, Type::Callable(_) | Type::ProtocolInstance(_)) =>
|
||||||
|
{
|
||||||
|
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||||
|
let other_instance = match target {
|
||||||
|
Type::Union(union) => Some(
|
||||||
|
union.map(db, |element| element.to_instance(db).unwrap_or(Type::Never)),
|
||||||
|
),
|
||||||
|
_ => target.to_instance(db),
|
||||||
|
};
|
||||||
|
|
||||||
|
other_instance.when_some_and(|other_instance| {
|
||||||
|
this_instance.has_relation_to_impl(
|
||||||
|
db,
|
||||||
|
other_instance,
|
||||||
|
inferable,
|
||||||
|
relation,
|
||||||
|
relation_visitor,
|
||||||
|
disjointness_visitor,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(_, Type::SubclassOf(subclass_of)) if subclass_of.is_type_var() => {
|
||||||
|
let other_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||||
|
self.to_instance(db).when_some_and(|this_instance| {
|
||||||
|
this_instance.has_relation_to_impl(
|
||||||
|
db,
|
||||||
|
other_instance,
|
||||||
|
inferable,
|
||||||
|
relation,
|
||||||
|
relation_visitor,
|
||||||
|
disjointness_visitor,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// A fully static typevar is a subtype of its upper bound, and to something similar to
|
// A fully static typevar is a subtype of its upper bound, and to something similar to
|
||||||
// the union of its constraints. An unbound, unconstrained, fully static typevar has an
|
// the union of its constraints. An unbound, unconstrained, fully static typevar has an
|
||||||
// implicit upper bound of `object` (which is handled above).
|
// implicit upper bound of `object` (which is handled above).
|
||||||
|
|
@ -2613,7 +2654,7 @@ impl<'db> Type<'db> {
|
||||||
// since `type[B]` describes all possible runtime subclasses of the class object `B`.
|
// since `type[B]` describes all possible runtime subclasses of the class object `B`.
|
||||||
(Type::ClassLiteral(class), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty
|
(Type::ClassLiteral(class), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty
|
||||||
.subclass_of()
|
.subclass_of()
|
||||||
.into_class()
|
.into_class(db)
|
||||||
.map(|subclass_of_class| {
|
.map(|subclass_of_class| {
|
||||||
class.default_specialization(db).has_relation_to_impl(
|
class.default_specialization(db).has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
|
|
@ -2627,7 +2668,7 @@ impl<'db> Type<'db> {
|
||||||
.unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())),
|
.unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())),
|
||||||
(Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty
|
(Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty
|
||||||
.subclass_of()
|
.subclass_of()
|
||||||
.into_class()
|
.into_class(db)
|
||||||
.map(|subclass_of_class| {
|
.map(|subclass_of_class| {
|
||||||
ClassType::Generic(alias).has_relation_to_impl(
|
ClassType::Generic(alias).has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
|
|
@ -2725,7 +2766,7 @@ impl<'db> Type<'db> {
|
||||||
// however, as they are not fully static types.
|
// however, as they are not fully static types.
|
||||||
(Type::SubclassOf(subclass_of_ty), _) => subclass_of_ty
|
(Type::SubclassOf(subclass_of_ty), _) => subclass_of_ty
|
||||||
.subclass_of()
|
.subclass_of()
|
||||||
.into_class()
|
.into_class(db)
|
||||||
.map(|class| class.metaclass_instance_type(db))
|
.map(|class| class.metaclass_instance_type(db))
|
||||||
.unwrap_or_else(|| KnownClass::Type.to_instance(db))
|
.unwrap_or_else(|| KnownClass::Type.to_instance(db))
|
||||||
.has_relation_to_impl(
|
.has_relation_to_impl(
|
||||||
|
|
@ -3043,6 +3084,57 @@ impl<'db> Type<'db> {
|
||||||
ConstraintSet::from(false)
|
ConstraintSet::from(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `type[T]` is disjoint from a callable or protocol instance if its upper bound or
|
||||||
|
// constraints are.
|
||||||
|
(Type::SubclassOf(subclass_of), Type::Callable(_) | Type::ProtocolInstance(_))
|
||||||
|
| (Type::Callable(_) | Type::ProtocolInstance(_), Type::SubclassOf(subclass_of))
|
||||||
|
if subclass_of.is_type_var() =>
|
||||||
|
{
|
||||||
|
let type_var = subclass_of
|
||||||
|
.subclass_of()
|
||||||
|
.with_transposed_type_var(db)
|
||||||
|
.into_type_var()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Type::TypeVar(type_var).is_disjoint_from_impl(
|
||||||
|
db,
|
||||||
|
other,
|
||||||
|
inferable,
|
||||||
|
disjointness_visitor,
|
||||||
|
relation_visitor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `type[T]` is disjoint from a class object `A` if every instance of `T` is disjoint from an instance of `A`.
|
||||||
|
(Type::SubclassOf(subclass_of), other) | (other, Type::SubclassOf(subclass_of))
|
||||||
|
if subclass_of.is_type_var() =>
|
||||||
|
{
|
||||||
|
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||||
|
let other_instance = match other {
|
||||||
|
Type::Union(union) => Some(
|
||||||
|
union.map(db, |element| element.to_instance(db).unwrap_or(Type::Never)),
|
||||||
|
),
|
||||||
|
// An unbounded typevar `U` may have instances of type `object` if specialized to
|
||||||
|
// an instance of `type`.
|
||||||
|
Type::TypeVar(typevar)
|
||||||
|
if typevar.typevar(db).bound_or_constraints(db).is_none() =>
|
||||||
|
{
|
||||||
|
Some(Type::object())
|
||||||
|
}
|
||||||
|
_ => other.to_instance(db),
|
||||||
|
};
|
||||||
|
|
||||||
|
other_instance.when_none_or(|other_instance| {
|
||||||
|
this_instance.is_disjoint_from_impl(
|
||||||
|
db,
|
||||||
|
other_instance,
|
||||||
|
inferable,
|
||||||
|
disjointness_visitor,
|
||||||
|
relation_visitor,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// A typevar is never disjoint from itself, since all occurrences of the typevar must
|
// A typevar is never disjoint from itself, since all occurrences of the typevar must
|
||||||
// be specialized to the same type. (This is an important difference between typevars
|
// be specialized to the same type. (This is an important difference between typevars
|
||||||
// and `Any`!) Different typevars might be disjoint, depending on their bounds and
|
// and `Any`!) Different typevars might be disjoint, depending on their bounds and
|
||||||
|
|
@ -3382,6 +3474,7 @@ impl<'db> Type<'db> {
|
||||||
SubclassOfInner::Class(class_a) => {
|
SubclassOfInner::Class(class_a) => {
|
||||||
class_b.when_subclass_of(db, None, class_a).negate(db)
|
class_b.when_subclass_of(db, None, class_a).negate(db)
|
||||||
}
|
}
|
||||||
|
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3392,6 +3485,7 @@ impl<'db> Type<'db> {
|
||||||
SubclassOfInner::Class(class_a) => ClassType::from(alias_b)
|
SubclassOfInner::Class(class_a) => ClassType::from(alias_b)
|
||||||
.when_subclass_of(db, class_a, inferable)
|
.when_subclass_of(db, class_a, inferable)
|
||||||
.negate(db),
|
.negate(db),
|
||||||
|
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3402,7 +3496,10 @@ impl<'db> Type<'db> {
|
||||||
// for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`,
|
// for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`,
|
||||||
// so although the type is dynamic we can still determine disjointedness in some situations
|
// so although the type is dynamic we can still determine disjointedness in some situations
|
||||||
(Type::SubclassOf(subclass_of_ty), other)
|
(Type::SubclassOf(subclass_of_ty), other)
|
||||||
| (other, Type::SubclassOf(subclass_of_ty)) => match subclass_of_ty.subclass_of() {
|
| (other, Type::SubclassOf(subclass_of_ty))
|
||||||
|
if !subclass_of_ty.is_type_var() =>
|
||||||
|
{
|
||||||
|
match subclass_of_ty.subclass_of() {
|
||||||
SubclassOfInner::Dynamic(_) => {
|
SubclassOfInner::Dynamic(_) => {
|
||||||
KnownClass::Type.to_instance(db).is_disjoint_from_impl(
|
KnownClass::Type.to_instance(db).is_disjoint_from_impl(
|
||||||
db,
|
db,
|
||||||
|
|
@ -3421,7 +3518,9 @@ impl<'db> Type<'db> {
|
||||||
relation_visitor,
|
relation_visitor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
|
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
|
||||||
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => {
|
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => {
|
||||||
|
|
@ -3683,6 +3782,11 @@ impl<'db> Type<'db> {
|
||||||
relation_visitor,
|
relation_visitor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(Type::SubclassOf(_), _) | (_, Type::SubclassOf(_)) => {
|
||||||
|
// All cases should have been handled above.
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4959,7 +5063,7 @@ impl<'db> Type<'db> {
|
||||||
Type::ClassLiteral(literal) => Some(literal),
|
Type::ClassLiteral(literal) => Some(literal),
|
||||||
Type::SubclassOf(subclass_of) => subclass_of
|
Type::SubclassOf(subclass_of) => subclass_of
|
||||||
.subclass_of()
|
.subclass_of()
|
||||||
.into_class()
|
.into_class(db)
|
||||||
.map(|class| class.class_literal(db).0),
|
.map(|class| class.class_literal(db).0),
|
||||||
_ => None,
|
_ => None,
|
||||||
} {
|
} {
|
||||||
|
|
@ -4975,7 +5079,7 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str,policy).expect(
|
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str, policy).expect(
|
||||||
"Calling `find_name_in_mro` on class literals and subclass-of types should always return `Some`",
|
"Calling `find_name_in_mro` on class literals and subclass-of types should always return `Some`",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -5245,12 +5349,16 @@ impl<'db> Type<'db> {
|
||||||
.metaclass_instance_type(db)
|
.metaclass_instance_type(db)
|
||||||
.try_bool_impl(db, allow_short_circuit, visitor)?,
|
.try_bool_impl(db, allow_short_circuit, visitor)?,
|
||||||
|
|
||||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
Type::SubclassOf(subclass_of_ty) => {
|
||||||
|
match subclass_of_ty.subclass_of().with_transposed_type_var(db) {
|
||||||
SubclassOfInner::Dynamic(_) => Truthiness::Ambiguous,
|
SubclassOfInner::Dynamic(_) => Truthiness::Ambiguous,
|
||||||
SubclassOfInner::Class(class) => {
|
SubclassOfInner::Class(class) => {
|
||||||
Type::from(class).try_bool_impl(db, allow_short_circuit, visitor)?
|
Type::from(class).try_bool_impl(db, allow_short_circuit, visitor)?
|
||||||
}
|
}
|
||||||
},
|
SubclassOfInner::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar)
|
||||||
|
.try_bool_impl(db, allow_short_circuit, visitor)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Type::TypeVar(bound_typevar) => {
|
Type::TypeVar(bound_typevar) => {
|
||||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||||
|
|
@ -5980,6 +6088,14 @@ impl<'db> Type<'db> {
|
||||||
// evaluating callable subtyping). TODO improve this definition (intersection of
|
// evaluating callable subtyping). TODO improve this definition (intersection of
|
||||||
// `__new__` and `__init__` signatures? and respect metaclass `__call__`).
|
// `__new__` and `__init__` signatures? and respect metaclass `__call__`).
|
||||||
SubclassOfInner::Class(class) => Type::from(class).bindings(db),
|
SubclassOfInner::Class(class) => Type::from(class).bindings(db),
|
||||||
|
|
||||||
|
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||||
|
// TODO check call vs signatures of `__new__` and/or `__init__`
|
||||||
|
SubclassOfInner::TypeVar(_) => Binding::single(
|
||||||
|
self,
|
||||||
|
Signature::new(Parameters::gradual_form(), self.to_instance(db)),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
},
|
},
|
||||||
|
|
||||||
Type::NominalInstance(_) | Type::ProtocolInstance(_) | Type::NewTypeInstance(_) => {
|
Type::NominalInstance(_) | Type::ProtocolInstance(_) | Type::NewTypeInstance(_) => {
|
||||||
|
|
@ -6853,6 +6969,17 @@ impl<'db> Type<'db> {
|
||||||
Type::TypeVar(bound_typevar) => Some(Type::TypeVar(bound_typevar.to_instance(db)?)),
|
Type::TypeVar(bound_typevar) => Some(Type::TypeVar(bound_typevar.to_instance(db)?)),
|
||||||
Type::TypeAlias(alias) => alias.value_type(db).to_instance(db),
|
Type::TypeAlias(alias) => alias.value_type(db).to_instance(db),
|
||||||
Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance")),
|
Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance")),
|
||||||
|
// An instance of class `C` may itself have instances if `C` is a subclass of `type`.
|
||||||
|
Type::NominalInstance(instance)
|
||||||
|
if KnownClass::Type
|
||||||
|
.to_class_literal(db)
|
||||||
|
.to_class_type(db)
|
||||||
|
.is_some_and(|type_class| {
|
||||||
|
instance.class(db).is_subclass_of(db, type_class)
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
Some(Type::object())
|
||||||
|
}
|
||||||
Type::BooleanLiteral(_)
|
Type::BooleanLiteral(_)
|
||||||
| Type::BytesLiteral(_)
|
| Type::BytesLiteral(_)
|
||||||
| Type::EnumLiteral(_)
|
| Type::EnumLiteral(_)
|
||||||
|
|
@ -7254,35 +7381,22 @@ impl<'db> Type<'db> {
|
||||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
||||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||||
Type::TypeVar(bound_typevar) => {
|
Type::TypeVar(bound_typevar) => {
|
||||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
SubclassOfType::from(db, SubclassOfInner::TypeVar(bound_typevar))
|
||||||
None => KnownClass::Type.to_instance(db),
|
|
||||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.to_meta_type(db),
|
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
|
||||||
// TODO: If we add a proper `OneOf` connector, we should use that here instead
|
|
||||||
// of union. (Using a union here doesn't break anything, but it is imprecise.)
|
|
||||||
constraints.map(db, |constraint| constraint.to_meta_type(db))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::ClassLiteral(class) => class.metaclass(db),
|
Type::ClassLiteral(class) => class.metaclass(db),
|
||||||
Type::GenericAlias(alias) => ClassType::from(alias).metaclass(db),
|
Type::GenericAlias(alias) => ClassType::from(alias).metaclass(db),
|
||||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of().into_class(db) {
|
||||||
SubclassOfInner::Dynamic(_) => self,
|
None => self,
|
||||||
SubclassOfInner::Class(class) => SubclassOfType::from(
|
Some(class) => SubclassOfType::try_from_type(db, class.metaclass(db))
|
||||||
db,
|
.unwrap_or(SubclassOfType::subclass_of_unknown()),
|
||||||
SubclassOfInner::try_from_type(db, class.metaclass(db))
|
|
||||||
.unwrap_or(SubclassOfInner::unknown()),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db),
|
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db),
|
||||||
Type::Dynamic(dynamic) => SubclassOfType::from(db, SubclassOfInner::Dynamic(dynamic)),
|
Type::Dynamic(dynamic) => SubclassOfType::from(db, SubclassOfInner::Dynamic(dynamic)),
|
||||||
// TODO intersections
|
// TODO intersections
|
||||||
Type::Intersection(_) => SubclassOfType::from(
|
Type::Intersection(_) => {
|
||||||
db,
|
SubclassOfType::try_from_type(db, todo_type!("Intersection meta-type"))
|
||||||
SubclassOfInner::try_from_type(db, todo_type!("Intersection meta-type"))
|
.expect("Type::Todo should be a valid `SubclassOfInner`")
|
||||||
.expect("Type::Todo should be a valid `SubclassOfInner`"),
|
}
|
||||||
),
|
|
||||||
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
||||||
Type::BoundSuper(_) => KnownClass::Super.to_class_literal(db),
|
Type::BoundSuper(_) => KnownClass::Super.to_class_literal(db),
|
||||||
Type::ProtocolInstance(protocol) => protocol.to_meta_type(db),
|
Type::ProtocolInstance(protocol) => protocol.to_meta_type(db),
|
||||||
|
|
@ -7378,41 +7492,7 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Type::TypeVar(bound_typevar) => match type_mapping {
|
Type::TypeVar(bound_typevar) => bound_typevar.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||||
TypeMapping::Specialization(specialization) => {
|
|
||||||
specialization.get(db, bound_typevar).unwrap_or(self)
|
|
||||||
}
|
|
||||||
TypeMapping::PartialSpecialization(partial) => {
|
|
||||||
partial.get(db, bound_typevar).unwrap_or(self)
|
|
||||||
}
|
|
||||||
TypeMapping::BindSelf(self_type) => {
|
|
||||||
if bound_typevar.typevar(db).is_self(db) {
|
|
||||||
*self_type
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TypeMapping::ReplaceSelf { new_upper_bound } => {
|
|
||||||
if bound_typevar.typevar(db).is_self(db) {
|
|
||||||
Type::TypeVar(
|
|
||||||
BoundTypeVarInstance::synthetic_self(
|
|
||||||
db,
|
|
||||||
*new_upper_bound,
|
|
||||||
bound_typevar.binding_context(db)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TypeMapping::PromoteLiterals(_)
|
|
||||||
| TypeMapping::ReplaceParameterDefaults
|
|
||||||
| TypeMapping::BindLegacyTypevars(_)
|
|
||||||
| TypeMapping::EagerExpansion => self,
|
|
||||||
TypeMapping::Materialize(materialization_kind) => {
|
|
||||||
Type::TypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
|
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
|
||||||
TypeMapping::BindLegacyTypevars(binding_context) => {
|
TypeMapping::BindLegacyTypevars(binding_context) => {
|
||||||
|
|
@ -7869,6 +7949,7 @@ impl<'db> Type<'db> {
|
||||||
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||||
SubclassOfInner::Class(class) => Some(TypeDefinition::Class(class.definition(db))),
|
SubclassOfInner::Class(class) => Some(TypeDefinition::Class(class.definition(db))),
|
||||||
SubclassOfInner::Dynamic(_) => None,
|
SubclassOfInner::Dynamic(_) => None,
|
||||||
|
SubclassOfInner::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::TypeAlias(alias) => alias.value_type(db).definition(db),
|
Self::TypeAlias(alias) => alias.value_type(db).definition(db),
|
||||||
|
|
@ -9556,6 +9637,25 @@ impl<'db> BoundTypeVarInstance<'db> {
|
||||||
Self::new(db, typevar, binding_context)
|
Self::new(db, typevar, binding_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an identical type variable with its `TypeVarBoundOrConstraints` mapped by the
|
||||||
|
/// provided closure.
|
||||||
|
pub(crate) fn map_bound_or_constraints(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
f: impl FnOnce(Option<TypeVarBoundOrConstraints<'db>>) -> Option<TypeVarBoundOrConstraints<'db>>,
|
||||||
|
) -> Self {
|
||||||
|
let bound_or_constraints = f(self.typevar(db).bound_or_constraints(db));
|
||||||
|
let typevar = TypeVarInstance::new(
|
||||||
|
db,
|
||||||
|
self.typevar(db).identity(db),
|
||||||
|
bound_or_constraints.map(TypeVarBoundOrConstraintsEvaluation::Eager),
|
||||||
|
self.typevar(db).explicit_variance(db),
|
||||||
|
self.typevar(db)._default(db),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self::new(db, typevar, self.binding_context(db))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn variance_with_polarity(
|
pub(crate) fn variance_with_polarity(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
|
|
@ -9576,6 +9676,47 @@ impl<'db> BoundTypeVarInstance<'db> {
|
||||||
pub fn variance(self, db: &'db dyn Db) -> TypeVarVariance {
|
pub fn variance(self, db: &'db dyn Db) -> TypeVarVariance {
|
||||||
self.variance_with_polarity(db, TypeVarVariance::Covariant)
|
self.variance_with_polarity(db, TypeVarVariance::Covariant)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_type_mapping_impl<'a>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
type_mapping: &TypeMapping<'a, 'db>,
|
||||||
|
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
match type_mapping {
|
||||||
|
TypeMapping::Specialization(specialization) => {
|
||||||
|
specialization.get(db, self).unwrap_or(Type::TypeVar(self))
|
||||||
|
}
|
||||||
|
TypeMapping::PartialSpecialization(partial) => {
|
||||||
|
partial.get(db, self).unwrap_or(Type::TypeVar(self))
|
||||||
|
}
|
||||||
|
TypeMapping::BindSelf(self_type) => {
|
||||||
|
if self.typevar(db).is_self(db) {
|
||||||
|
*self_type
|
||||||
|
} else {
|
||||||
|
Type::TypeVar(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeMapping::ReplaceSelf { new_upper_bound } => {
|
||||||
|
if self.typevar(db).is_self(db) {
|
||||||
|
Type::TypeVar(BoundTypeVarInstance::synthetic_self(
|
||||||
|
db,
|
||||||
|
*new_upper_bound,
|
||||||
|
self.binding_context(db),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Type::TypeVar(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeMapping::PromoteLiterals(_)
|
||||||
|
| TypeMapping::ReplaceParameterDefaults
|
||||||
|
| TypeMapping::BindLegacyTypevars(_)
|
||||||
|
| TypeMapping::EagerExpansion => Type::TypeVar(self),
|
||||||
|
TypeMapping::Materialize(materialization_kind) => {
|
||||||
|
Type::TypeVar(self.materialize_impl(db, *materialization_kind, visitor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_bound_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
fn walk_bound_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||||
|
|
@ -9741,8 +9882,7 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
||||||
.elements(db)
|
.elements(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| ty.materialize(db, materialization_kind, visitor))
|
.map(|ty| ty.materialize(db, materialization_kind, visitor))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Box<_>>(),
|
||||||
.into_boxed_slice(),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -310,10 +310,15 @@ impl<'db> BoundSuperType<'db> {
|
||||||
Type::Never => SuperOwnerKind::Dynamic(DynamicType::Unknown),
|
Type::Never => SuperOwnerKind::Dynamic(DynamicType::Unknown),
|
||||||
Type::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic),
|
Type::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic),
|
||||||
Type::ClassLiteral(class) => SuperOwnerKind::Class(ClassType::NonGeneric(class)),
|
Type::ClassLiteral(class) => SuperOwnerKind::Class(ClassType::NonGeneric(class)),
|
||||||
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
Type::SubclassOf(subclass_of_type) => {
|
||||||
|
match subclass_of_type.subclass_of().with_transposed_type_var(db) {
|
||||||
SubclassOfInner::Class(class) => SuperOwnerKind::Class(class),
|
SubclassOfInner::Class(class) => SuperOwnerKind::Class(class),
|
||||||
SubclassOfInner::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic),
|
SubclassOfInner::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic),
|
||||||
},
|
SubclassOfInner::TypeVar(bound_typevar) => {
|
||||||
|
return delegate_to(Type::TypeVar(bound_typevar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Type::NominalInstance(instance) => SuperOwnerKind::Instance(instance),
|
Type::NominalInstance(instance) => SuperOwnerKind::Instance(instance),
|
||||||
|
|
||||||
Type::ProtocolInstance(protocol) => {
|
Type::ProtocolInstance(protocol) => {
|
||||||
|
|
@ -450,8 +455,15 @@ impl<'db> BoundSuperType<'db> {
|
||||||
let pivot_class = match pivot_class_type {
|
let pivot_class = match pivot_class_type {
|
||||||
Type::ClassLiteral(class) => ClassBase::Class(ClassType::NonGeneric(class)),
|
Type::ClassLiteral(class) => ClassBase::Class(ClassType::NonGeneric(class)),
|
||||||
Type::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
|
Type::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
|
||||||
SubclassOfInner::Class(class) => ClassBase::Class(class),
|
|
||||||
SubclassOfInner::Dynamic(dynamic) => ClassBase::Dynamic(dynamic),
|
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::Protocol) => ClassBase::Protocol,
|
||||||
Type::SpecialForm(SpecialFormType::Generic) => ClassBase::Generic,
|
Type::SpecialForm(SpecialFormType::Generic) => ClassBase::Generic,
|
||||||
|
|
|
||||||
|
|
@ -3122,7 +3122,7 @@ pub(crate) fn report_undeclared_protocol_member(
|
||||||
Type::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
|
Type::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
|
||||||
SubclassOfInner::Class(class) => class,
|
SubclassOfInner::Class(class) => class,
|
||||||
SubclassOfInner::Dynamic(DynamicType::Any) => return true,
|
SubclassOfInner::Dynamic(DynamicType::Any) => return true,
|
||||||
SubclassOfInner::Dynamic(_) => return false,
|
SubclassOfInner::Dynamic(_) | SubclassOfInner::TypeVar(_) => return false,
|
||||||
},
|
},
|
||||||
Type::NominalInstance(instance) => instance.class(db),
|
Type::NominalInstance(instance) => instance.class(db),
|
||||||
Type::Union(union) => {
|
Type::Union(union) => {
|
||||||
|
|
|
||||||
|
|
@ -688,6 +688,11 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
write!(f.with_type(Type::Dynamic(dynamic)), "{dynamic}")?;
|
write!(f.with_type(Type::Dynamic(dynamic)), "{dynamic}")?;
|
||||||
f.write_char(']')
|
f.write_char(']')
|
||||||
}
|
}
|
||||||
|
SubclassOfInner::TypeVar(bound_typevar) => write!(
|
||||||
|
f,
|
||||||
|
"type[{}]",
|
||||||
|
bound_typevar.identity(self.db).display(self.db)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Type::SpecialForm(special_form) => {
|
Type::SpecialForm(special_form) => {
|
||||||
write!(f.with_type(self.ty), "{special_form}")
|
write!(f.with_type(self.ty), "{special_form}")
|
||||||
|
|
|
||||||
|
|
@ -1557,10 +1557,17 @@ impl<'db> SpecializationBuilder<'db> {
|
||||||
argument: ty,
|
argument: ty,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ => {
|
_ => self.add_type_mapping(bound_typevar, ty, polarity, f),
|
||||||
self.add_type_mapping(bound_typevar, ty, polarity, f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(Type::SubclassOf(subclass_of), ty) | (ty, Type::SubclassOf(subclass_of))
|
||||||
|
if subclass_of.is_type_var() =>
|
||||||
|
{
|
||||||
|
let formal_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||||
|
if let Some(actual_instance) = ty.to_instance(self.db) {
|
||||||
|
return self.infer_map_impl(formal_instance, actual_instance, polarity, f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(formal, Type::NominalInstance(actual_nominal)) => {
|
(formal, Type::NominalInstance(actual_nominal)) => {
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,11 @@ impl<'db> AllMembers<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||||
SubclassOfInner::Class(class_type) => {
|
SubclassOfInner::Dynamic(_) => {
|
||||||
|
self.extend_with_type(db, KnownClass::Type.to_instance(db));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(class_type) = subclass_of_type.subclass_of().into_class(db) {
|
||||||
let (class_literal, specialization) = class_type.class_literal(db);
|
let (class_literal, specialization) = class_type.class_literal(db);
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
||||||
|
|
@ -184,8 +188,6 @@ impl<'db> AllMembers<'db> {
|
||||||
self.extend_with_class_members(db, ty, metaclass);
|
self.extend_with_class_members(db, ty, metaclass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SubclassOfInner::Dynamic(_) => {
|
|
||||||
self.extend_with_type(db, KnownClass::Type.to_instance(db));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -241,7 +243,7 @@ impl<'db> AllMembers<'db> {
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
}
|
}
|
||||||
Type::SubclassOf(subclass_of) => {
|
Type::SubclassOf(subclass_of) => {
|
||||||
if let Some(class) = subclass_of.subclass_of().into_class() {
|
if let Some(class) = subclass_of.subclass_of().into_class(db) {
|
||||||
self.extend_with_class_members(db, ty, class.class_literal(db).0);
|
self.extend_with_class_members(db, ty, class.class_literal(db).0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -777,7 +779,7 @@ pub fn definitions_for_attribute<'db>(
|
||||||
};
|
};
|
||||||
let class_literal = match meta_type {
|
let class_literal = match meta_type {
|
||||||
Type::ClassLiteral(class_literal) => class_literal,
|
Type::ClassLiteral(class_literal) => class_literal,
|
||||||
Type::SubclassOf(subclass) => match subclass.subclass_of().into_class() {
|
Type::SubclassOf(subclass) => match subclass.subclass_of().into_class(db) {
|
||||||
Some(cls) => cls.class_literal(db).0,
|
Some(cls) => cls.class_literal(db).0,
|
||||||
None => continue,
|
None => continue,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -8133,7 +8133,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let class = match callable_type {
|
let class = match callable_type {
|
||||||
Type::ClassLiteral(class) => Some(ClassType::NonGeneric(class)),
|
Type::ClassLiteral(class) => Some(ClassType::NonGeneric(class)),
|
||||||
Type::GenericAlias(generic) => Some(ClassType::Generic(generic)),
|
Type::GenericAlias(generic) => Some(ClassType::Generic(generic)),
|
||||||
Type::SubclassOf(subclass) => subclass.subclass_of().into_class(),
|
Type::SubclassOf(subclass) => subclass.subclass_of().into_class(self.db()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -9112,7 +9112,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
});
|
});
|
||||||
|
|
||||||
let attr_name = &attr.id;
|
let attr_name = &attr.id;
|
||||||
|
|
||||||
let resolved_type = fallback_place.unwrap_with_diagnostic(|lookup_err| match lookup_err {
|
let resolved_type = fallback_place.unwrap_with_diagnostic(|lookup_err| match lookup_err {
|
||||||
LookupError::Undefined(_) => {
|
LookupError::Undefined(_) => {
|
||||||
let fallback = || {
|
let fallback = || {
|
||||||
|
|
@ -9140,6 +9139,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
"Attribute lookup on a dynamic `SubclassOf` type \
|
"Attribute lookup on a dynamic `SubclassOf` type \
|
||||||
should always return a bound symbol"
|
should always return a bound symbol"
|
||||||
),
|
),
|
||||||
|
SubclassOfInner::TypeVar(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
|
|
|
||||||
|
|
@ -641,27 +641,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
fn infer_subclass_of_type_expression(&mut self, slice: &ast::Expr) -> Type<'db> {
|
fn infer_subclass_of_type_expression(&mut self, slice: &ast::Expr) -> Type<'db> {
|
||||||
match slice {
|
match slice {
|
||||||
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
|
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
|
||||||
let name_ty = self.infer_expression(slice, TypeContext::default());
|
SubclassOfType::try_from_instance(self.db(), self.infer_type_expression(slice))
|
||||||
match name_ty {
|
.unwrap_or(todo_type!("unsupported type[X] special form"))
|
||||||
Type::ClassLiteral(class_literal) => {
|
|
||||||
if class_literal.is_protocol(self.db()) {
|
|
||||||
SubclassOfType::from(
|
|
||||||
self.db(),
|
|
||||||
todo_type!("type[T] for protocols").expect_dynamic(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
SubclassOfType::from(
|
|
||||||
self.db(),
|
|
||||||
class_literal.default_specialization(self.db()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::SpecialForm(SpecialFormType::Any) => SubclassOfType::subclass_of_any(),
|
|
||||||
Type::SpecialForm(SpecialFormType::Unknown) => {
|
|
||||||
SubclassOfType::subclass_of_unknown()
|
|
||||||
}
|
|
||||||
_ => todo_type!("unsupported type[X] special form"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ast::Expr::BinOp(binary) if binary.op == ast::Operator::BitOr => {
|
ast::Expr::BinOp(binary) if binary.op == ast::Operator::BitOr => {
|
||||||
let union_ty = UnionType::from_elements_leave_aliases(
|
let union_ty = UnionType::from_elements_leave_aliases(
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,10 @@ impl ClassInfoConstraintFunction {
|
||||||
// e.g. `isinstance(x, list[int])` fails at runtime.
|
// e.g. `isinstance(x, list[int])` fails at runtime.
|
||||||
SubclassOfInner::Class(ClassType::Generic(_)) => None,
|
SubclassOfInner::Class(ClassType::Generic(_)) => None,
|
||||||
SubclassOfInner::Dynamic(dynamic) => Some(Type::Dynamic(dynamic)),
|
SubclassOfInner::Dynamic(dynamic) => Some(Type::Dynamic(dynamic)),
|
||||||
|
SubclassOfInner::TypeVar(bound_typevar) => match self {
|
||||||
|
ClassInfoConstraintFunction::IsSubclass => Some(classinfo),
|
||||||
|
ClassInfoConstraintFunction::IsInstance => Some(Type::TypeVar(bound_typevar)),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Type::Dynamic(_) => Some(classinfo),
|
Type::Dynamic(_) => Some(classinfo),
|
||||||
Type::Intersection(intersection) => {
|
Type::Intersection(intersection) => {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType,
|
||||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownClass,
|
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownClass,
|
||||||
MaterializationKind, MemberLookupPolicy, NormalizedVisitor, SpecialFormType, Type, TypeContext,
|
MaterializationKind, MemberLookupPolicy, NormalizedVisitor, SpecialFormType, Type, TypeContext,
|
||||||
TypeMapping, TypeRelation,
|
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, UnionType, todo_type,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ pub(super) fn walk_subclass_of_type<'db, V: super::visitor::TypeVisitor<'db> + ?
|
||||||
subclass_of: SubclassOfType<'db>,
|
subclass_of: SubclassOfType<'db>,
|
||||||
visitor: &V,
|
visitor: &V,
|
||||||
) {
|
) {
|
||||||
visitor.visit_type(db, Type::from(subclass_of.subclass_of));
|
visitor.visit_type(db, Type::from(subclass_of));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> SubclassOfType<'db> {
|
impl<'db> SubclassOfType<'db> {
|
||||||
|
|
@ -44,18 +44,43 @@ impl<'db> SubclassOfType<'db> {
|
||||||
pub(crate) fn from(db: &'db dyn Db, subclass_of: impl Into<SubclassOfInner<'db>>) -> Type<'db> {
|
pub(crate) fn from(db: &'db dyn Db, subclass_of: impl Into<SubclassOfInner<'db>>) -> Type<'db> {
|
||||||
let subclass_of = subclass_of.into();
|
let subclass_of = subclass_of.into();
|
||||||
match subclass_of {
|
match subclass_of {
|
||||||
SubclassOfInner::Dynamic(_) => Type::SubclassOf(Self { subclass_of }),
|
|
||||||
SubclassOfInner::Class(class) => {
|
SubclassOfInner::Class(class) => {
|
||||||
if class.is_final(db) {
|
if class.is_final(db) {
|
||||||
Type::from(class)
|
Type::from(class)
|
||||||
} else if class.is_object(db) {
|
} else if class.is_object(db) {
|
||||||
KnownClass::Type.to_instance(db)
|
Self::subclass_of_object(db)
|
||||||
} else {
|
} else {
|
||||||
Type::SubclassOf(Self { subclass_of })
|
Type::SubclassOf(Self { subclass_of })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SubclassOfInner::Dynamic(_) | SubclassOfInner::TypeVar(_) => {
|
||||||
|
Type::SubclassOf(Self { subclass_of })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given the class object `T`, returns a [`Type`] instance representing `type[T]`.
|
||||||
|
pub(crate) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
|
||||||
|
let subclass_of = match ty {
|
||||||
|
Type::Dynamic(dynamic) => SubclassOfInner::Dynamic(dynamic),
|
||||||
|
Type::ClassLiteral(literal) => {
|
||||||
|
SubclassOfInner::Class(literal.default_specialization(db))
|
||||||
|
}
|
||||||
|
Type::GenericAlias(generic) => SubclassOfInner::Class(ClassType::Generic(generic)),
|
||||||
|
Type::SpecialForm(SpecialFormType::Any) => SubclassOfInner::Dynamic(DynamicType::Any),
|
||||||
|
Type::SpecialForm(SpecialFormType::Unknown) => {
|
||||||
|
SubclassOfInner::Dynamic(DynamicType::Unknown)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Self::from(db, subclass_of))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given an instance of the class or type variable `T`, returns a [`Type`] instance representing `type[T]`.
|
||||||
|
pub(crate) fn try_from_instance(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
|
||||||
|
SubclassOfInner::try_from_instance(db, ty).map(|subclass_of| Self::from(db, subclass_of))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a [`Type`] instance representing the type `type[Unknown]`.
|
/// Return a [`Type`] instance representing the type `type[Unknown]`.
|
||||||
pub(crate) const fn subclass_of_unknown() -> Type<'db> {
|
pub(crate) const fn subclass_of_unknown() -> Type<'db> {
|
||||||
|
|
@ -65,12 +90,19 @@ impl<'db> SubclassOfType<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a [`Type`] instance representing the type `type[Any]`.
|
/// Return a [`Type`] instance representing the type `type[Any]`.
|
||||||
|
#[cfg(test)]
|
||||||
pub(crate) const fn subclass_of_any() -> Type<'db> {
|
pub(crate) const fn subclass_of_any() -> Type<'db> {
|
||||||
Type::SubclassOf(SubclassOfType {
|
Type::SubclassOf(SubclassOfType {
|
||||||
subclass_of: SubclassOfInner::Dynamic(DynamicType::Any),
|
subclass_of: SubclassOfInner::Dynamic(DynamicType::Any),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a [`Type`] instance representing the type `type[object]`.
|
||||||
|
pub(crate) fn subclass_of_object(db: &'db dyn Db) -> Type<'db> {
|
||||||
|
// See the documentation of `SubclassOfType::from` for details.
|
||||||
|
KnownClass::Type.to_instance(db)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the inner [`SubclassOfInner`] value wrapped by this `SubclassOfType`.
|
/// Return the inner [`SubclassOfInner`] value wrapped by this `SubclassOfType`.
|
||||||
pub(crate) const fn subclass_of(self) -> SubclassOfInner<'db> {
|
pub(crate) const fn subclass_of(self) -> SubclassOfInner<'db> {
|
||||||
self.subclass_of
|
self.subclass_of
|
||||||
|
|
@ -82,6 +114,15 @@ impl<'db> SubclassOfType<'db> {
|
||||||
subclass_of.is_dynamic()
|
subclass_of.is_dynamic()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn is_type_var(self) -> bool {
|
||||||
|
let Self { subclass_of } = self;
|
||||||
|
subclass_of.is_type_var()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn into_type_var(self) -> Option<BoundTypeVarInstance<'db>> {
|
||||||
|
self.subclass_of.into_type_var()
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn apply_type_mapping_impl<'a>(
|
pub(super) fn apply_type_mapping_impl<'a>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
|
|
@ -105,6 +146,11 @@ impl<'db> SubclassOfType<'db> {
|
||||||
},
|
},
|
||||||
_ => Type::SubclassOf(self),
|
_ => Type::SubclassOf(self),
|
||||||
},
|
},
|
||||||
|
SubclassOfInner::TypeVar(typevar) => SubclassOfType::try_from_instance(
|
||||||
|
db,
|
||||||
|
typevar.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||||
|
)
|
||||||
|
.unwrap_or(SubclassOfType::subclass_of_unknown()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,10 +162,18 @@ impl<'db> SubclassOfType<'db> {
|
||||||
visitor: &FindLegacyTypeVarsVisitor<'db>,
|
visitor: &FindLegacyTypeVarsVisitor<'db>,
|
||||||
) {
|
) {
|
||||||
match self.subclass_of {
|
match self.subclass_of {
|
||||||
|
SubclassOfInner::Dynamic(_) => {}
|
||||||
SubclassOfInner::Class(class) => {
|
SubclassOfInner::Class(class) => {
|
||||||
class.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
|
class.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
|
||||||
}
|
}
|
||||||
SubclassOfInner::Dynamic(_) => {}
|
SubclassOfInner::TypeVar(typevar) => {
|
||||||
|
Type::TypeVar(typevar).find_legacy_typevars_impl(
|
||||||
|
db,
|
||||||
|
binding_context,
|
||||||
|
typevars,
|
||||||
|
visitor,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +183,19 @@ impl<'db> SubclassOfType<'db> {
|
||||||
name: &str,
|
name: &str,
|
||||||
policy: MemberLookupPolicy,
|
policy: MemberLookupPolicy,
|
||||||
) -> Option<PlaceAndQualifiers<'db>> {
|
) -> Option<PlaceAndQualifiers<'db>> {
|
||||||
Type::from(self.subclass_of).find_name_in_mro_with_policy(db, name, policy)
|
let class_like = match self.subclass_of.with_transposed_type_var(db) {
|
||||||
|
SubclassOfInner::Class(class) => Type::from(class),
|
||||||
|
SubclassOfInner::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
||||||
|
SubclassOfInner::TypeVar(bound_typevar) => {
|
||||||
|
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||||
|
None => unreachable!(),
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound,
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(union)) => Type::Union(union),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class_like.find_name_in_mro_with_policy(db, name, policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if `self` has a certain relation to `other`.
|
/// Return `true` if `self` has a certain relation to `other`.
|
||||||
|
|
@ -165,6 +231,10 @@ impl<'db> SubclassOfType<'db> {
|
||||||
relation_visitor,
|
relation_visitor,
|
||||||
disjointness_visitor,
|
disjointness_visitor,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
(SubclassOfInner::TypeVar(_), _) | (_, SubclassOfInner::TypeVar(_)) => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,6 +255,9 @@ impl<'db> SubclassOfType<'db> {
|
||||||
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
|
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
|
||||||
ConstraintSet::from(!self_class.could_coexist_in_mro_with(db, other_class))
|
ConstraintSet::from(!self_class.could_coexist_in_mro_with(db, other_class))
|
||||||
}
|
}
|
||||||
|
(SubclassOfInner::TypeVar(_), _) | (_, SubclassOfInner::TypeVar(_)) => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,12 +285,13 @@ impl<'db> SubclassOfType<'db> {
|
||||||
match self.subclass_of {
|
match self.subclass_of {
|
||||||
SubclassOfInner::Class(class) => Type::instance(db, class),
|
SubclassOfInner::Class(class) => Type::instance(db, class),
|
||||||
SubclassOfInner::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type),
|
SubclassOfInner::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type),
|
||||||
|
SubclassOfInner::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_typed_dict(self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_typed_dict(self, db: &'db dyn Db) -> bool {
|
||||||
self.subclass_of
|
self.subclass_of
|
||||||
.into_class()
|
.into_class(db)
|
||||||
.is_some_and(|class| class.class_literal(db).0.is_typed_dict(db))
|
.is_some_and(|class| class.class_literal(db).0.is_typed_dict(db))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,8 +299,8 @@ impl<'db> SubclassOfType<'db> {
|
||||||
impl<'db> VarianceInferable<'db> for SubclassOfType<'db> {
|
impl<'db> VarianceInferable<'db> for SubclassOfType<'db> {
|
||||||
fn variance_of(self, db: &dyn Db, typevar: BoundTypeVarInstance<'_>) -> TypeVarVariance {
|
fn variance_of(self, db: &dyn Db, typevar: BoundTypeVarInstance<'_>) -> TypeVarVariance {
|
||||||
match self.subclass_of {
|
match self.subclass_of {
|
||||||
SubclassOfInner::Dynamic(_) => TypeVarVariance::Bivariant,
|
|
||||||
SubclassOfInner::Class(class) => class.variance_of(db, typevar),
|
SubclassOfInner::Class(class) => class.variance_of(db, typevar),
|
||||||
|
SubclassOfInner::Dynamic(_) | SubclassOfInner::TypeVar(_) => TypeVarVariance::Bivariant,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -235,6 +309,7 @@ impl<'db> VarianceInferable<'db> for SubclassOfType<'db> {
|
||||||
///
|
///
|
||||||
/// 1. A "subclass of a class": `type[C]` for any class object `C`
|
/// 1. A "subclass of a class": `type[C]` for any class object `C`
|
||||||
/// 2. A "subclass of a dynamic type": `type[Any]`, `type[Unknown]` and `type[@Todo]`
|
/// 2. A "subclass of a dynamic type": `type[Any]`, `type[Unknown]` and `type[@Todo]`
|
||||||
|
/// 3. A "subclass of a type variable": `type[T]` for any type variable `T`
|
||||||
///
|
///
|
||||||
/// In the long term, we may want to implement <https://github.com/astral-sh/ruff/issues/15381>.
|
/// In the long term, we may want to implement <https://github.com/astral-sh/ruff/issues/15381>.
|
||||||
/// Doing this would allow us to get rid of this enum,
|
/// Doing this would allow us to get rid of this enum,
|
||||||
|
|
@ -249,6 +324,7 @@ impl<'db> VarianceInferable<'db> for SubclassOfType<'db> {
|
||||||
pub(crate) enum SubclassOfInner<'db> {
|
pub(crate) enum SubclassOfInner<'db> {
|
||||||
Class(ClassType<'db>),
|
Class(ClassType<'db>),
|
||||||
Dynamic(DynamicType),
|
Dynamic(DynamicType),
|
||||||
|
TypeVar(BoundTypeVarInstance<'db>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> SubclassOfInner<'db> {
|
impl<'db> SubclassOfInner<'db> {
|
||||||
|
|
@ -260,24 +336,111 @@ impl<'db> SubclassOfInner<'db> {
|
||||||
matches!(self, Self::Dynamic(_))
|
matches!(self, Self::Dynamic(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn into_class(self) -> Option<ClassType<'db>> {
|
pub(crate) const fn is_type_var(self) -> bool {
|
||||||
|
matches!(self, Self::TypeVar(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_class(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Class(class) => Some(class),
|
|
||||||
Self::Dynamic(_) => None,
|
Self::Dynamic(_) => None,
|
||||||
|
Self::Class(class) => Some(class),
|
||||||
|
Self::TypeVar(bound_typevar) => {
|
||||||
|
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||||
|
None => Some(ClassType::object(db)),
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||||
|
Self::try_from_instance(db, bound)
|
||||||
|
.and_then(|subclass_of| subclass_of.into_class(db))
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||||
|
match constraints.elements(db) {
|
||||||
|
[bound] => Self::try_from_instance(db, *bound)
|
||||||
|
.and_then(|subclass_of| subclass_of.into_class(db)),
|
||||||
|
_ => Some(ClassType::object(db)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn into_dynamic(self) -> Option<DynamicType> {
|
pub(crate) const fn into_dynamic(self) -> Option<DynamicType> {
|
||||||
match self {
|
match self {
|
||||||
Self::Class(_) => None,
|
Self::Class(_) | Self::TypeVar(_) => None,
|
||||||
Self::Dynamic(dynamic) => Some(dynamic),
|
Self::Dynamic(dynamic) => Some(dynamic),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn into_type_var(self) -> Option<BoundTypeVarInstance<'db>> {
|
||||||
|
match self {
|
||||||
|
Self::Class(_) | Self::Dynamic(_) => None,
|
||||||
|
Self::TypeVar(bound_typevar) => Some(bound_typevar),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn try_from_instance(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
|
||||||
|
Some(match ty {
|
||||||
|
Type::NominalInstance(instance) => SubclassOfInner::Class(instance.class(db)),
|
||||||
|
Type::TypedDict(typed_dict) => SubclassOfInner::Class(typed_dict.defining_class()),
|
||||||
|
Type::TypeVar(bound_typevar) => SubclassOfInner::TypeVar(bound_typevar),
|
||||||
|
Type::Dynamic(DynamicType::Any) => SubclassOfInner::Dynamic(DynamicType::Any),
|
||||||
|
Type::Dynamic(DynamicType::Unknown) => SubclassOfInner::Dynamic(DynamicType::Unknown),
|
||||||
|
Type::ProtocolInstance(_) => {
|
||||||
|
SubclassOfInner::Dynamic(todo_type!("type[T] for protocols").expect_dynamic())
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transposes `type[T]` with a type variable `T` into `T: type[...]`.
|
||||||
|
///
|
||||||
|
/// In particular:
|
||||||
|
/// - If `T` has an upper bound of `T: Bound`, this returns `T: type[Bound]`.
|
||||||
|
/// - If `T` has constraints `T: (A, B)`, this returns `T: (type[A], type[B])`.
|
||||||
|
/// - Otherwise, for an unbounded type variable, this returns `type[object]`.
|
||||||
|
///
|
||||||
|
/// If this is type of a concrete type `C`, returns the type unchanged.
|
||||||
|
pub(crate) fn with_transposed_type_var(self, db: &'db dyn Db) -> Self {
|
||||||
|
let Some(bound_typevar) = self.into_type_var() else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
let bound_typevar = bound_typevar.map_bound_or_constraints(db, |bound_or_constraints| {
|
||||||
|
Some(match bound_or_constraints {
|
||||||
|
None => TypeVarBoundOrConstraints::UpperBound(
|
||||||
|
SubclassOfType::try_from_instance(db, Type::object())
|
||||||
|
.unwrap_or(SubclassOfType::subclass_of_unknown()),
|
||||||
|
),
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||||
|
TypeVarBoundOrConstraints::UpperBound(
|
||||||
|
SubclassOfType::try_from_instance(db, bound)
|
||||||
|
.unwrap_or(SubclassOfType::subclass_of_unknown()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||||
|
let constraints = constraints
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.map(|constraint| {
|
||||||
|
SubclassOfType::try_from_instance(db, *constraint)
|
||||||
|
.unwrap_or(SubclassOfType::subclass_of_unknown())
|
||||||
|
})
|
||||||
|
.collect::<Box<_>>();
|
||||||
|
|
||||||
|
TypeVarBoundOrConstraints::Constraints(UnionType::new(db, constraints))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
Self::TypeVar(bound_typevar)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)),
|
Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)),
|
||||||
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()),
|
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()),
|
||||||
|
Self::TypeVar(bound_typevar) => {
|
||||||
|
Self::TypeVar(bound_typevar.normalized_impl(db, visitor))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,16 +456,7 @@ impl<'db> SubclassOfInner<'db> {
|
||||||
class.recursive_type_normalized_impl(db, div, nested, visitor)?,
|
class.recursive_type_normalized_impl(db, div, nested, visitor)?,
|
||||||
)),
|
)),
|
||||||
Self::Dynamic(dynamic) => Some(Self::Dynamic(dynamic.recursive_type_normalized())),
|
Self::Dynamic(dynamic) => Some(Self::Dynamic(dynamic.recursive_type_normalized())),
|
||||||
}
|
Self::TypeVar(_) => Some(self),
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
|
|
||||||
match ty {
|
|
||||||
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),
|
|
||||||
Type::ClassLiteral(literal) => Some(Self::Class(literal.default_specialization(db))),
|
|
||||||
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
|
|
||||||
Type::SpecialForm(SpecialFormType::Any) => Some(Self::Dynamic(DynamicType::Any)),
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -325,11 +479,18 @@ impl<'db> From<ProtocolClass<'db>> for SubclassOfInner<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> From<SubclassOfInner<'db>> for Type<'db> {
|
impl<'db> From<BoundTypeVarInstance<'db>> for SubclassOfInner<'db> {
|
||||||
fn from(value: SubclassOfInner<'db>) -> Self {
|
fn from(value: BoundTypeVarInstance<'db>) -> Self {
|
||||||
match value {
|
SubclassOfInner::TypeVar(value)
|
||||||
SubclassOfInner::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> From<SubclassOfType<'db>> for Type<'db> {
|
||||||
|
fn from(value: SubclassOfType<'db>) -> Self {
|
||||||
|
match value.subclass_of {
|
||||||
SubclassOfInner::Class(class) => class.into(),
|
SubclassOfInner::Class(class) => class.into(),
|
||||||
|
SubclassOfInner::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
||||||
|
SubclassOfInner::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||||
(SubclassOfInner::Dynamic(left), SubclassOfInner::Dynamic(right)) => {
|
(SubclassOfInner::Dynamic(left), SubclassOfInner::Dynamic(right)) => {
|
||||||
dynamic_elements_ordering(left, right)
|
dynamic_elements_ordering(left, right)
|
||||||
}
|
}
|
||||||
|
(SubclassOfInner::TypeVar(left), SubclassOfInner::TypeVar(right)) => {
|
||||||
|
left.as_id().cmp(&right.as_id())
|
||||||
|
}
|
||||||
|
(SubclassOfInner::TypeVar(_), _) => Ordering::Less,
|
||||||
|
(_, SubclassOfInner::TypeVar(_)) => Ordering::Greater,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue