mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-19 12:16:43 +00:00
[ty] Fix false-positive diagnostics on super() calls (#20814)
This commit is contained in:
parent
565dbf3c9d
commit
d83d7a0dcd
6 changed files with 975 additions and 123 deletions
|
|
@ -14,9 +14,16 @@ common usage.
|
||||||
|
|
||||||
### Explicit Super Object
|
### Explicit Super Object
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
`super(pivot_class, owner)` performs attribute lookup along the MRO, starting immediately after the
|
`super(pivot_class, owner)` performs attribute lookup along the MRO, starting immediately after the
|
||||||
specified pivot class.
|
specified pivot class.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
class A:
|
class A:
|
||||||
def a(self): ...
|
def a(self): ...
|
||||||
|
|
@ -34,21 +41,15 @@ reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>,
|
||||||
|
|
||||||
super(C, C()).a
|
super(C, C()).a
|
||||||
super(C, C()).b
|
super(C, C()).b
|
||||||
# error: [unresolved-attribute] "Type `<super: <class 'C'>, C>` has no attribute `c`"
|
super(C, C()).c # error: [unresolved-attribute]
|
||||||
super(C, C()).c
|
|
||||||
|
|
||||||
super(B, C()).a
|
super(B, C()).a
|
||||||
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `b`"
|
super(B, C()).b # error: [unresolved-attribute]
|
||||||
super(B, C()).b
|
super(B, C()).c # error: [unresolved-attribute]
|
||||||
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `c`"
|
|
||||||
super(B, C()).c
|
|
||||||
|
|
||||||
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `a`"
|
super(A, C()).a # error: [unresolved-attribute]
|
||||||
super(A, C()).a
|
super(A, C()).b # error: [unresolved-attribute]
|
||||||
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `b`"
|
super(A, C()).c # error: [unresolved-attribute]
|
||||||
super(A, C()).b
|
|
||||||
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `c`"
|
|
||||||
super(A, C()).c
|
|
||||||
|
|
||||||
reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||||
reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
|
reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
|
||||||
|
|
@ -56,12 +57,80 @@ reveal_type(super(C, C()).aa) # revealed: int
|
||||||
reveal_type(super(C, C()).bb) # revealed: int
|
reveal_type(super(C, C()).bb) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Examples of explicit `super()` with unusual types. We allow almost any type to be passed as the
|
||||||
|
second argument to `super()` -- the only exceptions are "pure abstract" types such as `Callable` and
|
||||||
|
synthesized `Protocol`s that cannot be upcast to, or interpreted as, a non-`object` nominal type.
|
||||||
|
|
||||||
|
```py
|
||||||
|
import types
|
||||||
|
from typing_extensions import Callable, TypeIs, Literal, TypedDict
|
||||||
|
|
||||||
|
def f(): ...
|
||||||
|
|
||||||
|
class Foo[T]:
|
||||||
|
def method(self): ...
|
||||||
|
@property
|
||||||
|
def some_property(self): ...
|
||||||
|
|
||||||
|
type Alias = int
|
||||||
|
|
||||||
|
class SomeTypedDict(TypedDict):
|
||||||
|
x: int
|
||||||
|
y: bytes
|
||||||
|
|
||||||
|
# revealed: <super: <class 'object'>, FunctionType>
|
||||||
|
reveal_type(super(object, f))
|
||||||
|
# revealed: <super: <class 'object'>, WrapperDescriptorType>
|
||||||
|
reveal_type(super(object, types.FunctionType.__get__))
|
||||||
|
# revealed: <super: <class 'object'>, GenericAlias>
|
||||||
|
reveal_type(super(object, Foo[int]))
|
||||||
|
# revealed: <super: <class 'object'>, _SpecialForm>
|
||||||
|
reveal_type(super(object, Literal))
|
||||||
|
# revealed: <super: <class 'object'>, TypeAliasType>
|
||||||
|
reveal_type(super(object, Alias))
|
||||||
|
# revealed: <super: <class 'object'>, MethodType>
|
||||||
|
reveal_type(super(object, Foo().method))
|
||||||
|
# revealed: <super: <class 'object'>, property>
|
||||||
|
reveal_type(super(object, Foo.some_property))
|
||||||
|
|
||||||
|
def g(x: object) -> TypeIs[list[object]]:
|
||||||
|
return isinstance(x, list)
|
||||||
|
|
||||||
|
def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
|
||||||
|
if hasattr(x, "bar"):
|
||||||
|
# revealed: <Protocol with members 'bar'>
|
||||||
|
reveal_type(x)
|
||||||
|
# error: [invalid-super-argument]
|
||||||
|
# revealed: Unknown
|
||||||
|
reveal_type(super(object, x))
|
||||||
|
|
||||||
|
# error: [invalid-super-argument]
|
||||||
|
# revealed: Unknown
|
||||||
|
reveal_type(super(object, z))
|
||||||
|
|
||||||
|
is_list = g(x)
|
||||||
|
# revealed: TypeIs[list[object] @ x]
|
||||||
|
reveal_type(is_list)
|
||||||
|
# revealed: <super: <class 'object'>, bool>
|
||||||
|
reveal_type(super(object, is_list))
|
||||||
|
|
||||||
|
# revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
|
||||||
|
reveal_type(super(object, y))
|
||||||
|
```
|
||||||
|
|
||||||
### Implicit Super Object
|
### Implicit Super Object
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
The implicit form `super()` is same as `super(__class__, <first argument>)`. The `__class__` refers
|
The implicit form `super()` is same as `super(__class__, <first argument>)`. The `__class__` refers
|
||||||
to the class that contains the function where `super()` is used. The first argument refers to the
|
to the class that contains the function where `super()` is used. The first argument refers to the
|
||||||
current method’s first parameter (typically `self` or `cls`).
|
current method’s first parameter (typically `self` or `cls`).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -74,6 +143,7 @@ class B(A):
|
||||||
def __init__(self, a: int):
|
def __init__(self, a: int):
|
||||||
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
|
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
|
||||||
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
||||||
|
reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
|
||||||
super().__init__(a)
|
super().__init__(a)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -86,6 +156,123 @@ super(B, B(42)).__init__(42)
|
||||||
super(B, B).f()
|
super(B, B).f()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Some examples with unusual annotations for `self` or `cls`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
import enum
|
||||||
|
from typing import Any, Self, Never, Protocol, Callable
|
||||||
|
from ty_extensions import Intersection
|
||||||
|
|
||||||
|
class BuilderMeta(type):
|
||||||
|
def __new__(
|
||||||
|
cls: type[Any],
|
||||||
|
name: str,
|
||||||
|
bases: tuple[type, ...],
|
||||||
|
dct: dict[str, Any],
|
||||||
|
) -> BuilderMeta:
|
||||||
|
# revealed: <super: <class 'BuilderMeta'>, Any>
|
||||||
|
s = reveal_type(super())
|
||||||
|
# revealed: Any
|
||||||
|
return reveal_type(s.__new__(cls, name, bases, dct))
|
||||||
|
|
||||||
|
class BuilderMeta2(type):
|
||||||
|
def __new__(
|
||||||
|
cls: type[BuilderMeta2],
|
||||||
|
name: str,
|
||||||
|
bases: tuple[type, ...],
|
||||||
|
dct: dict[str, Any],
|
||||||
|
) -> BuilderMeta2:
|
||||||
|
# revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
||||||
|
s = reveal_type(super())
|
||||||
|
# TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
|
||||||
|
# revealed: Unknown
|
||||||
|
return reveal_type(s.__new__(cls, name, bases, dct))
|
||||||
|
|
||||||
|
class Foo[T]:
|
||||||
|
x: T
|
||||||
|
|
||||||
|
def method(self: Any):
|
||||||
|
reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||||
|
|
||||||
|
if isinstance(self, Foo):
|
||||||
|
reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||||
|
|
||||||
|
def method2(self: Foo[T]):
|
||||||
|
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||||
|
reveal_type(super())
|
||||||
|
|
||||||
|
def method3(self: Foo):
|
||||||
|
# revealed: <super: <class 'Foo'>, Foo[Unknown]>
|
||||||
|
reveal_type(super())
|
||||||
|
|
||||||
|
def method4(self: Self):
|
||||||
|
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||||
|
reveal_type(super())
|
||||||
|
|
||||||
|
def method5[S: Foo[int]](self: S, other: S) -> S:
|
||||||
|
# revealed: <super: <class 'Foo'>, Foo[int]>
|
||||||
|
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]>
|
||||||
|
reveal_type(super())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def method7[S](self: S, other: S) -> S:
|
||||||
|
# error: [invalid-super-argument]
|
||||||
|
# revealed: Unknown
|
||||||
|
reveal_type(super())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def method8[S: int](self: S, other: S) -> S:
|
||||||
|
# error: [invalid-super-argument]
|
||||||
|
# revealed: Unknown
|
||||||
|
reveal_type(super())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def method9[S: (int, str)](self: S, other: S) -> S:
|
||||||
|
# error: [invalid-super-argument]
|
||||||
|
# revealed: Unknown
|
||||||
|
reveal_type(super())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def method10[S: Callable[..., str]](self: S, other: S) -> S:
|
||||||
|
# error: [invalid-super-argument]
|
||||||
|
# revealed: Unknown
|
||||||
|
reveal_type(super())
|
||||||
|
return self
|
||||||
|
|
||||||
|
type Alias = Bar
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
def method(self: Alias):
|
||||||
|
# revealed: <super: <class 'Bar'>, Bar>
|
||||||
|
reveal_type(super())
|
||||||
|
|
||||||
|
def pls_dont_call_me(self: Never):
|
||||||
|
# revealed: <super: <class 'Bar'>, Unknown>
|
||||||
|
reveal_type(super())
|
||||||
|
|
||||||
|
def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
|
||||||
|
# revealed: <super: <class 'Bar'>, Bar>
|
||||||
|
reveal_type(super())
|
||||||
|
|
||||||
|
class P(Protocol):
|
||||||
|
def method(self: P):
|
||||||
|
# revealed: <super: <class 'P'>, P>
|
||||||
|
reveal_type(super())
|
||||||
|
|
||||||
|
class E(enum.Enum):
|
||||||
|
X = 1
|
||||||
|
|
||||||
|
def method(self: E):
|
||||||
|
match self:
|
||||||
|
case E.X:
|
||||||
|
# revealed: <super: <class 'E'>, E>
|
||||||
|
reveal_type(super())
|
||||||
|
```
|
||||||
|
|
||||||
### Unbound Super Object
|
### Unbound Super Object
|
||||||
|
|
||||||
Calling `super(cls)` without a second argument returns an _unbound super object_. This is treated as
|
Calling `super(cls)` without a second argument returns an _unbound super object_. This is treated as
|
||||||
|
|
@ -167,11 +354,19 @@ class A:
|
||||||
## Built-ins and Literals
|
## Built-ins and Literals
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
reveal_type(super(bool, True)) # revealed: <super: <class 'bool'>, bool>
|
reveal_type(super(bool, True)) # revealed: <super: <class 'bool'>, bool>
|
||||||
reveal_type(super(bool, bool())) # revealed: <super: <class 'bool'>, bool>
|
reveal_type(super(bool, bool())) # revealed: <super: <class 'bool'>, bool>
|
||||||
reveal_type(super(int, bool())) # revealed: <super: <class 'int'>, bool>
|
reveal_type(super(int, bool())) # revealed: <super: <class 'int'>, bool>
|
||||||
reveal_type(super(int, 3)) # revealed: <super: <class 'int'>, int>
|
reveal_type(super(int, 3)) # revealed: <super: <class 'int'>, int>
|
||||||
reveal_type(super(str, "")) # revealed: <super: <class 'str'>, str>
|
reveal_type(super(str, "")) # revealed: <super: <class 'str'>, str>
|
||||||
|
reveal_type(super(bytes, b"")) # revealed: <super: <class 'bytes'>, bytes>
|
||||||
|
|
||||||
|
class E(Enum):
|
||||||
|
X = 42
|
||||||
|
|
||||||
|
reveal_type(super(E, E.X)) # revealed: <super: <class 'E'>, E>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Descriptor Behavior with Super
|
## Descriptor Behavior with Super
|
||||||
|
|
@ -342,7 +537,7 @@ def f(x: int):
|
||||||
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
|
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
|
||||||
super(IntAlias, 0)
|
super(IntAlias, 0)
|
||||||
|
|
||||||
# error: [invalid-super-argument] "`Literal[""]` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, Literal[""])` call"
|
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
|
||||||
# revealed: Unknown
|
# revealed: Unknown
|
||||||
reveal_type(super(int, str()))
|
reveal_type(super(int, str()))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: super.md - Super - Basic Usage - Explicit Super Object
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class A:
|
||||||
|
2 | def a(self): ...
|
||||||
|
3 | aa: int = 1
|
||||||
|
4 |
|
||||||
|
5 | class B(A):
|
||||||
|
6 | def b(self): ...
|
||||||
|
7 | bb: int = 2
|
||||||
|
8 |
|
||||||
|
9 | class C(B):
|
||||||
|
10 | def c(self): ...
|
||||||
|
11 | cc: int = 3
|
||||||
|
12 |
|
||||||
|
13 | reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
|
||||||
|
14 |
|
||||||
|
15 | super(C, C()).a
|
||||||
|
16 | super(C, C()).b
|
||||||
|
17 | super(C, C()).c # error: [unresolved-attribute]
|
||||||
|
18 |
|
||||||
|
19 | super(B, C()).a
|
||||||
|
20 | super(B, C()).b # error: [unresolved-attribute]
|
||||||
|
21 | super(B, C()).c # error: [unresolved-attribute]
|
||||||
|
22 |
|
||||||
|
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||||
|
24 | super(A, C()).b # error: [unresolved-attribute]
|
||||||
|
25 | super(A, C()).c # error: [unresolved-attribute]
|
||||||
|
26 |
|
||||||
|
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||||
|
28 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
|
||||||
|
29 | reveal_type(super(C, C()).aa) # revealed: int
|
||||||
|
30 | reveal_type(super(C, C()).bb) # revealed: int
|
||||||
|
31 | import types
|
||||||
|
32 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
|
||||||
|
33 |
|
||||||
|
34 | def f(): ...
|
||||||
|
35 |
|
||||||
|
36 | class Foo[T]:
|
||||||
|
37 | def method(self): ...
|
||||||
|
38 | @property
|
||||||
|
39 | def some_property(self): ...
|
||||||
|
40 |
|
||||||
|
41 | type Alias = int
|
||||||
|
42 |
|
||||||
|
43 | class SomeTypedDict(TypedDict):
|
||||||
|
44 | x: int
|
||||||
|
45 | y: bytes
|
||||||
|
46 |
|
||||||
|
47 | # revealed: <super: <class 'object'>, FunctionType>
|
||||||
|
48 | reveal_type(super(object, f))
|
||||||
|
49 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
|
||||||
|
50 | reveal_type(super(object, types.FunctionType.__get__))
|
||||||
|
51 | # revealed: <super: <class 'object'>, GenericAlias>
|
||||||
|
52 | reveal_type(super(object, Foo[int]))
|
||||||
|
53 | # revealed: <super: <class 'object'>, _SpecialForm>
|
||||||
|
54 | reveal_type(super(object, Literal))
|
||||||
|
55 | # revealed: <super: <class 'object'>, TypeAliasType>
|
||||||
|
56 | reveal_type(super(object, Alias))
|
||||||
|
57 | # revealed: <super: <class 'object'>, MethodType>
|
||||||
|
58 | reveal_type(super(object, Foo().method))
|
||||||
|
59 | # revealed: <super: <class 'object'>, property>
|
||||||
|
60 | reveal_type(super(object, Foo.some_property))
|
||||||
|
61 |
|
||||||
|
62 | def g(x: object) -> TypeIs[list[object]]:
|
||||||
|
63 | return isinstance(x, list)
|
||||||
|
64 |
|
||||||
|
65 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
|
||||||
|
66 | if hasattr(x, "bar"):
|
||||||
|
67 | # revealed: <Protocol with members 'bar'>
|
||||||
|
68 | reveal_type(x)
|
||||||
|
69 | # error: [invalid-super-argument]
|
||||||
|
70 | # revealed: Unknown
|
||||||
|
71 | reveal_type(super(object, x))
|
||||||
|
72 |
|
||||||
|
73 | # error: [invalid-super-argument]
|
||||||
|
74 | # revealed: Unknown
|
||||||
|
75 | reveal_type(super(object, z))
|
||||||
|
76 |
|
||||||
|
77 | is_list = g(x)
|
||||||
|
78 | # revealed: TypeIs[list[object] @ x]
|
||||||
|
79 | reveal_type(is_list)
|
||||||
|
80 | # revealed: <super: <class 'object'>, bool>
|
||||||
|
81 | reveal_type(super(object, is_list))
|
||||||
|
82 |
|
||||||
|
83 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
|
||||||
|
84 | reveal_type(super(object, y))
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-attribute]: Type `<super: <class 'C'>, C>` has no attribute `c`
|
||||||
|
--> src/mdtest_snippet.py:17:1
|
||||||
|
|
|
||||||
|
15 | super(C, C()).a
|
||||||
|
16 | super(C, C()).b
|
||||||
|
17 | super(C, C()).c # error: [unresolved-attribute]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
18 |
|
||||||
|
19 | super(B, C()).a
|
||||||
|
|
|
||||||
|
info: rule `unresolved-attribute` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `b`
|
||||||
|
--> src/mdtest_snippet.py:20:1
|
||||||
|
|
|
||||||
|
19 | super(B, C()).a
|
||||||
|
20 | super(B, C()).b # error: [unresolved-attribute]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
21 | super(B, C()).c # error: [unresolved-attribute]
|
||||||
|
|
|
||||||
|
info: rule `unresolved-attribute` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `c`
|
||||||
|
--> src/mdtest_snippet.py:21:1
|
||||||
|
|
|
||||||
|
19 | super(B, C()).a
|
||||||
|
20 | super(B, C()).b # error: [unresolved-attribute]
|
||||||
|
21 | super(B, C()).c # error: [unresolved-attribute]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
22 |
|
||||||
|
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||||
|
|
|
||||||
|
info: rule `unresolved-attribute` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `a`
|
||||||
|
--> src/mdtest_snippet.py:23:1
|
||||||
|
|
|
||||||
|
21 | super(B, C()).c # error: [unresolved-attribute]
|
||||||
|
22 |
|
||||||
|
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
24 | super(A, C()).b # error: [unresolved-attribute]
|
||||||
|
25 | super(A, C()).c # error: [unresolved-attribute]
|
||||||
|
|
|
||||||
|
info: rule `unresolved-attribute` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `b`
|
||||||
|
--> src/mdtest_snippet.py:24:1
|
||||||
|
|
|
||||||
|
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||||
|
24 | super(A, C()).b # error: [unresolved-attribute]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
25 | super(A, C()).c # error: [unresolved-attribute]
|
||||||
|
|
|
||||||
|
info: rule `unresolved-attribute` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `c`
|
||||||
|
--> src/mdtest_snippet.py:25:1
|
||||||
|
|
|
||||||
|
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||||
|
24 | super(A, C()).b # error: [unresolved-attribute]
|
||||||
|
25 | super(A, C()).c # error: [unresolved-attribute]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
26 |
|
||||||
|
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||||
|
|
|
||||||
|
info: rule `unresolved-attribute` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-super-argument]: `<Protocol with members 'bar'>` is an abstract/structural type in `super(<class 'object'>, <Protocol with members 'bar'>)` call
|
||||||
|
--> src/mdtest_snippet.py:71:21
|
||||||
|
|
|
||||||
|
69 | # error: [invalid-super-argument]
|
||||||
|
70 | # revealed: Unknown
|
||||||
|
71 | reveal_type(super(object, x))
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
72 |
|
||||||
|
73 | # error: [invalid-super-argument]
|
||||||
|
|
|
||||||
|
info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-super-argument]: `(int, str, /) -> bool` is an abstract/structural type in `super(<class 'object'>, (int, str, /) -> bool)` call
|
||||||
|
--> src/mdtest_snippet.py:75:17
|
||||||
|
|
|
||||||
|
73 | # error: [invalid-super-argument]
|
||||||
|
74 | # revealed: Unknown
|
||||||
|
75 | reveal_type(super(object, z))
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
76 |
|
||||||
|
77 | is_list = g(x)
|
||||||
|
|
|
||||||
|
info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: super.md - Super - Basic Usage - Implicit Super Object
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from __future__ import annotations
|
||||||
|
2 |
|
||||||
|
3 | class A:
|
||||||
|
4 | def __init__(self, a: int): ...
|
||||||
|
5 | @classmethod
|
||||||
|
6 | def f(cls): ...
|
||||||
|
7 |
|
||||||
|
8 | class B(A):
|
||||||
|
9 | def __init__(self, a: int):
|
||||||
|
10 | # TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
|
||||||
|
11 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
||||||
|
12 | reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
|
||||||
|
13 | super().__init__(a)
|
||||||
|
14 |
|
||||||
|
15 | @classmethod
|
||||||
|
16 | def f(cls):
|
||||||
|
17 | # TODO: Once `Self` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
|
||||||
|
18 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
||||||
|
19 | super().f()
|
||||||
|
20 |
|
||||||
|
21 | super(B, B(42)).__init__(42)
|
||||||
|
22 | super(B, B).f()
|
||||||
|
23 | import enum
|
||||||
|
24 | from typing import Any, Self, Never, Protocol, Callable
|
||||||
|
25 | from ty_extensions import Intersection
|
||||||
|
26 |
|
||||||
|
27 | class BuilderMeta(type):
|
||||||
|
28 | def __new__(
|
||||||
|
29 | cls: type[Any],
|
||||||
|
30 | name: str,
|
||||||
|
31 | bases: tuple[type, ...],
|
||||||
|
32 | dct: dict[str, Any],
|
||||||
|
33 | ) -> BuilderMeta:
|
||||||
|
34 | # revealed: <super: <class 'BuilderMeta'>, Any>
|
||||||
|
35 | s = reveal_type(super())
|
||||||
|
36 | # revealed: Any
|
||||||
|
37 | return reveal_type(s.__new__(cls, name, bases, dct))
|
||||||
|
38 |
|
||||||
|
39 | class BuilderMeta2(type):
|
||||||
|
40 | def __new__(
|
||||||
|
41 | cls: type[BuilderMeta2],
|
||||||
|
42 | name: str,
|
||||||
|
43 | bases: tuple[type, ...],
|
||||||
|
44 | dct: dict[str, Any],
|
||||||
|
45 | ) -> BuilderMeta2:
|
||||||
|
46 | # revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
||||||
|
47 | s = reveal_type(super())
|
||||||
|
48 | # TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
|
||||||
|
49 | # revealed: Unknown
|
||||||
|
50 | return reveal_type(s.__new__(cls, name, bases, dct))
|
||||||
|
51 |
|
||||||
|
52 | class Foo[T]:
|
||||||
|
53 | x: T
|
||||||
|
54 |
|
||||||
|
55 | def method(self: Any):
|
||||||
|
56 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||||
|
57 |
|
||||||
|
58 | if isinstance(self, Foo):
|
||||||
|
59 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||||
|
60 |
|
||||||
|
61 | def method2(self: Foo[T]):
|
||||||
|
62 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||||
|
63 | reveal_type(super())
|
||||||
|
64 |
|
||||||
|
65 | def method3(self: Foo):
|
||||||
|
66 | # revealed: <super: <class 'Foo'>, Foo[Unknown]>
|
||||||
|
67 | reveal_type(super())
|
||||||
|
68 |
|
||||||
|
69 | def method4(self: Self):
|
||||||
|
70 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||||
|
71 | reveal_type(super())
|
||||||
|
72 |
|
||||||
|
73 | def method5[S: Foo[int]](self: S, other: S) -> S:
|
||||||
|
74 | # revealed: <super: <class 'Foo'>, Foo[int]>
|
||||||
|
75 | reveal_type(super())
|
||||||
|
76 | return self
|
||||||
|
77 |
|
||||||
|
78 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
|
||||||
|
79 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
|
||||||
|
80 | reveal_type(super())
|
||||||
|
81 | return self
|
||||||
|
82 |
|
||||||
|
83 | def method7[S](self: S, other: S) -> S:
|
||||||
|
84 | # error: [invalid-super-argument]
|
||||||
|
85 | # revealed: Unknown
|
||||||
|
86 | reveal_type(super())
|
||||||
|
87 | return self
|
||||||
|
88 |
|
||||||
|
89 | def method8[S: int](self: S, other: S) -> S:
|
||||||
|
90 | # error: [invalid-super-argument]
|
||||||
|
91 | # revealed: Unknown
|
||||||
|
92 | reveal_type(super())
|
||||||
|
93 | return self
|
||||||
|
94 |
|
||||||
|
95 | def method9[S: (int, str)](self: S, other: S) -> S:
|
||||||
|
96 | # error: [invalid-super-argument]
|
||||||
|
97 | # revealed: Unknown
|
||||||
|
98 | reveal_type(super())
|
||||||
|
99 | return self
|
||||||
|
100 |
|
||||||
|
101 | def method10[S: Callable[..., str]](self: S, other: S) -> S:
|
||||||
|
102 | # error: [invalid-super-argument]
|
||||||
|
103 | # revealed: Unknown
|
||||||
|
104 | reveal_type(super())
|
||||||
|
105 | return self
|
||||||
|
106 |
|
||||||
|
107 | type Alias = Bar
|
||||||
|
108 |
|
||||||
|
109 | class Bar:
|
||||||
|
110 | def method(self: Alias):
|
||||||
|
111 | # revealed: <super: <class 'Bar'>, Bar>
|
||||||
|
112 | reveal_type(super())
|
||||||
|
113 |
|
||||||
|
114 | def pls_dont_call_me(self: Never):
|
||||||
|
115 | # revealed: <super: <class 'Bar'>, Unknown>
|
||||||
|
116 | reveal_type(super())
|
||||||
|
117 |
|
||||||
|
118 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
|
||||||
|
119 | # revealed: <super: <class 'Bar'>, Bar>
|
||||||
|
120 | reveal_type(super())
|
||||||
|
121 |
|
||||||
|
122 | class P(Protocol):
|
||||||
|
123 | def method(self: P):
|
||||||
|
124 | # revealed: <super: <class 'P'>, P>
|
||||||
|
125 | reveal_type(super())
|
||||||
|
126 |
|
||||||
|
127 | class E(enum.Enum):
|
||||||
|
128 | X = 1
|
||||||
|
129 |
|
||||||
|
130 | def method(self: E):
|
||||||
|
131 | match self:
|
||||||
|
132 | case E.X:
|
||||||
|
133 | # revealed: <super: <class 'E'>, E>
|
||||||
|
134 | reveal_type(super())
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
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:86:21
|
||||||
|
|
|
||||||
|
84 | # error: [invalid-super-argument]
|
||||||
|
85 | # revealed: Unknown
|
||||||
|
86 | reveal_type(super())
|
||||||
|
| ^^^^^^^
|
||||||
|
87 | return self
|
||||||
|
|
|
||||||
|
info: Type variable `S` has `object` as its implicit upper bound
|
||||||
|
info: `object` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
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
|
||||||
|
--> src/mdtest_snippet.py:92:21
|
||||||
|
|
|
||||||
|
90 | # error: [invalid-super-argument]
|
||||||
|
91 | # revealed: Unknown
|
||||||
|
92 | reveal_type(super())
|
||||||
|
| ^^^^^^^
|
||||||
|
93 | return self
|
||||||
|
|
|
||||||
|
info: Type variable `S` has upper bound `int`
|
||||||
|
info: `int` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
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
|
||||||
|
--> src/mdtest_snippet.py:98:21
|
||||||
|
|
|
||||||
|
96 | # error: [invalid-super-argument]
|
||||||
|
97 | # revealed: Unknown
|
||||||
|
98 | reveal_type(super())
|
||||||
|
| ^^^^^^^
|
||||||
|
99 | return self
|
||||||
|
|
|
||||||
|
info: Type variable `S` has constraints `int, str`
|
||||||
|
info: `int | str` is not an instance or subclass of `<class 'Foo'>`
|
||||||
|
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
|
||||||
|
--> src/mdtest_snippet.py:104:21
|
||||||
|
|
|
||||||
|
102 | # error: [invalid-super-argument]
|
||||||
|
103 | # revealed: Unknown
|
||||||
|
104 | reveal_type(super())
|
||||||
|
| ^^^^^^^
|
||||||
|
105 | return self
|
||||||
|
|
|
||||||
|
info: Type variable `S` has upper bound `(...) -> str`
|
||||||
|
info: rule `invalid-super-argument` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -1020,6 +1020,13 @@ impl<'db> Type<'db> {
|
||||||
.expect("Expected a Type::Dynamic variant")
|
.expect("Expected a Type::Dynamic variant")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn into_protocol_instance(self) -> Option<ProtocolInstanceType<'db>> {
|
||||||
|
match self {
|
||||||
|
Type::ProtocolInstance(instance) => Some(instance),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(crate) fn expect_class_literal(self) -> ClassLiteral<'db> {
|
pub(crate) fn expect_class_literal(self) -> ClassLiteral<'db> {
|
||||||
self.into_class_literal()
|
self.into_class_literal()
|
||||||
|
|
@ -11248,21 +11255,62 @@ impl<'db> EnumLiteralType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enumeration of ways in which a `super()` call can cause us to emit a diagnostic.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(crate) enum BoundSuperError<'db> {
|
pub(crate) enum BoundSuperError<'db> {
|
||||||
InvalidPivotClassType {
|
/// The second argument to `super()` (which may have been implicitly provided by
|
||||||
|
/// the Python interpreter) has an abstract or structural type.
|
||||||
|
/// It's impossible to determine whether a `Callable` type or a synthesized protocol
|
||||||
|
/// type is an instance or subclass of the pivot class, so these are rejected.
|
||||||
|
AbstractOwnerType {
|
||||||
|
owner_type: Type<'db>,
|
||||||
pivot_class: Type<'db>,
|
pivot_class: Type<'db>,
|
||||||
|
/// If `owner_type` is a type variable, this contains the type variable instance
|
||||||
|
typevar_context: Option<TypeVarInstance<'db>>,
|
||||||
},
|
},
|
||||||
|
/// The first argument to `super()` (which may have been implicitly provided by
|
||||||
|
/// the Python interpreter) is not a valid class type.
|
||||||
|
InvalidPivotClassType { pivot_class: Type<'db> },
|
||||||
|
/// The second argument to `super()` was not a subclass or instance of the first argument.
|
||||||
|
/// (Note that both arguments may have been implicitly provided by the Python interpreter.)
|
||||||
FailingConditionCheck {
|
FailingConditionCheck {
|
||||||
pivot_class: Type<'db>,
|
pivot_class: Type<'db>,
|
||||||
owner: Type<'db>,
|
owner: Type<'db>,
|
||||||
|
/// If `owner_type` is a type variable, this contains the type variable instance
|
||||||
|
typevar_context: Option<TypeVarInstance<'db>>,
|
||||||
},
|
},
|
||||||
|
/// It was a single-argument `super()` call, but we were unable to determine
|
||||||
|
/// the implicit arguments provided by the Python interpreter.
|
||||||
UnavailableImplicitArguments,
|
UnavailableImplicitArguments,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoundSuperError<'_> {
|
impl<'db> BoundSuperError<'db> {
|
||||||
pub(super) fn report_diagnostic(&self, context: &InferContext, node: AnyNodeRef) {
|
pub(super) fn report_diagnostic(&self, context: &'db InferContext<'db, '_>, node: AnyNodeRef) {
|
||||||
match self {
|
match self {
|
||||||
|
BoundSuperError::AbstractOwnerType {
|
||||||
|
owner_type,
|
||||||
|
pivot_class,
|
||||||
|
typevar_context,
|
||||||
|
} => {
|
||||||
|
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
|
||||||
|
if let Some(typevar_context) = typevar_context {
|
||||||
|
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||||
|
"`{owner}` is a type variable with an abstract/structural type as \
|
||||||
|
its bounds or constraints, in `super({pivot_class}, {owner})` call",
|
||||||
|
pivot_class = pivot_class.display(context.db()),
|
||||||
|
owner = owner_type.display(context.db()),
|
||||||
|
));
|
||||||
|
Self::describe_typevar(context.db(), &mut diagnostic, *typevar_context);
|
||||||
|
} else {
|
||||||
|
builder.into_diagnostic(format_args!(
|
||||||
|
"`{owner}` is an abstract/structural type in \
|
||||||
|
`super({pivot_class}, {owner})` call",
|
||||||
|
pivot_class = pivot_class.display(context.db()),
|
||||||
|
owner = owner_type.display(context.db()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BoundSuperError::InvalidPivotClassType { pivot_class } => {
|
BoundSuperError::InvalidPivotClassType { pivot_class } => {
|
||||||
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
|
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
|
||||||
builder.into_diagnostic(format_args!(
|
builder.into_diagnostic(format_args!(
|
||||||
|
|
@ -11271,14 +11319,28 @@ impl BoundSuperError<'_> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BoundSuperError::FailingConditionCheck { pivot_class, owner } => {
|
BoundSuperError::FailingConditionCheck {
|
||||||
|
pivot_class,
|
||||||
|
owner,
|
||||||
|
typevar_context,
|
||||||
|
} => {
|
||||||
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
|
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
|
||||||
builder.into_diagnostic(format_args!(
|
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||||
"`{owner}` is not an instance or subclass of \
|
"`{owner}` is not an instance or subclass of \
|
||||||
`{pivot_class}` in `super({pivot_class}, {owner})` call",
|
`{pivot_class}` in `super({pivot_class}, {owner})` call",
|
||||||
pivot_class = pivot_class.display(context.db()),
|
pivot_class = pivot_class.display(context.db()),
|
||||||
owner = owner.display(context.db()),
|
owner = owner.display(context.db()),
|
||||||
));
|
));
|
||||||
|
if let Some(typevar_context) = typevar_context {
|
||||||
|
let bound_or_constraints_union =
|
||||||
|
Self::describe_typevar(context.db(), &mut diagnostic, *typevar_context);
|
||||||
|
diagnostic.info(format_args!(
|
||||||
|
"`{bounds_or_constraints}` is not an instance or subclass of `{pivot_class}`",
|
||||||
|
bounds_or_constraints =
|
||||||
|
bound_or_constraints_union.display(context.db()),
|
||||||
|
pivot_class = pivot_class.display(context.db()),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BoundSuperError::UnavailableImplicitArguments => {
|
BoundSuperError::UnavailableImplicitArguments => {
|
||||||
|
|
@ -11292,6 +11354,44 @@ impl BoundSuperError<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add an `info`-level diagnostic describing the bounds or constraints,
|
||||||
|
/// and return the type variable's upper bound or the union of its constraints.
|
||||||
|
fn describe_typevar(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
diagnostic: &mut Diagnostic,
|
||||||
|
type_var: TypeVarInstance<'db>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
match type_var.bound_or_constraints(db) {
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||||
|
diagnostic.info(format_args!(
|
||||||
|
"Type variable `{}` has upper bound `{}`",
|
||||||
|
type_var.name(db),
|
||||||
|
bound.display(db)
|
||||||
|
));
|
||||||
|
bound
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||||
|
diagnostic.info(format_args!(
|
||||||
|
"Type variable `{}` has constraints `{}`",
|
||||||
|
type_var.name(db),
|
||||||
|
constraints
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.display(db))
|
||||||
|
.join(", ")
|
||||||
|
));
|
||||||
|
Type::Union(constraints)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
diagnostic.info(format_args!(
|
||||||
|
"Type variable `{}` has `object` as its implicit upper bound",
|
||||||
|
type_var.name(db)
|
||||||
|
));
|
||||||
|
Type::object()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, get_size2::GetSize)]
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, get_size2::GetSize)]
|
||||||
|
|
@ -11326,14 +11426,6 @@ impl<'db> SuperOwnerKind<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_type(self) -> Type<'db> {
|
|
||||||
match self {
|
|
||||||
SuperOwnerKind::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
|
||||||
SuperOwnerKind::Class(class) => class.into(),
|
|
||||||
SuperOwnerKind::Instance(instance) => instance.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_class(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
fn into_class(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
||||||
match self {
|
match self {
|
||||||
SuperOwnerKind::Dynamic(_) => None,
|
SuperOwnerKind::Dynamic(_) => None,
|
||||||
|
|
@ -11341,35 +11433,6 @@ impl<'db> SuperOwnerKind<'db> {
|
||||||
SuperOwnerKind::Instance(instance) => Some(instance.class(db)),
|
SuperOwnerKind::Instance(instance) => Some(instance.class(db)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
|
|
||||||
match ty {
|
|
||||||
Type::Dynamic(dynamic) => Some(SuperOwnerKind::Dynamic(dynamic)),
|
|
||||||
Type::ClassLiteral(class_literal) => Some(SuperOwnerKind::Class(
|
|
||||||
class_literal.apply_optional_specialization(db, None),
|
|
||||||
)),
|
|
||||||
Type::NominalInstance(instance) => Some(SuperOwnerKind::Instance(instance)),
|
|
||||||
Type::BooleanLiteral(_) => {
|
|
||||||
SuperOwnerKind::try_from_type(db, KnownClass::Bool.to_instance(db))
|
|
||||||
}
|
|
||||||
Type::IntLiteral(_) => {
|
|
||||||
SuperOwnerKind::try_from_type(db, KnownClass::Int.to_instance(db))
|
|
||||||
}
|
|
||||||
Type::StringLiteral(_) => {
|
|
||||||
SuperOwnerKind::try_from_type(db, KnownClass::Str.to_instance(db))
|
|
||||||
}
|
|
||||||
Type::LiteralString => {
|
|
||||||
SuperOwnerKind::try_from_type(db, KnownClass::Str.to_instance(db))
|
|
||||||
}
|
|
||||||
Type::BytesLiteral(_) => {
|
|
||||||
SuperOwnerKind::try_from_type(db, KnownClass::Bytes.to_instance(db))
|
|
||||||
}
|
|
||||||
Type::SpecialForm(special_form) => {
|
|
||||||
SuperOwnerKind::try_from_type(db, special_form.instance_fallback(db))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> From<SuperOwnerKind<'db>> for Type<'db> {
|
impl<'db> From<SuperOwnerKind<'db>> for Type<'db> {
|
||||||
|
|
@ -11397,8 +11460,8 @@ fn walk_bound_super_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||||
bound_super: BoundSuperType<'db>,
|
bound_super: BoundSuperType<'db>,
|
||||||
visitor: &V,
|
visitor: &V,
|
||||||
) {
|
) {
|
||||||
visitor.visit_type(db, bound_super.pivot_class(db).into());
|
visitor.visit_type(db, Type::from(bound_super.pivot_class(db)));
|
||||||
visitor.visit_type(db, bound_super.owner(db).into_type());
|
visitor.visit_type(db, Type::from(bound_super.owner(db)));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> BoundSuperType<'db> {
|
impl<'db> BoundSuperType<'db> {
|
||||||
|
|
@ -11414,16 +11477,171 @@ impl<'db> BoundSuperType<'db> {
|
||||||
pivot_class_type: Type<'db>,
|
pivot_class_type: Type<'db>,
|
||||||
owner_type: Type<'db>,
|
owner_type: Type<'db>,
|
||||||
) -> Result<Type<'db>, BoundSuperError<'db>> {
|
) -> Result<Type<'db>, BoundSuperError<'db>> {
|
||||||
if let Type::Union(union) = owner_type {
|
let delegate_to =
|
||||||
return Ok(UnionType::from_elements(
|
|type_to_delegate_to| BoundSuperType::build(db, pivot_class_type, type_to_delegate_to);
|
||||||
db,
|
|
||||||
union
|
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 {
|
||||||
|
BoundSuperError::AbstractOwnerType {
|
||||||
|
owner_type: _,
|
||||||
|
pivot_class: _,
|
||||||
|
typevar_context: _,
|
||||||
|
} => BoundSuperError::AbstractOwnerType {
|
||||||
|
owner_type,
|
||||||
|
pivot_class: pivot_class_type,
|
||||||
|
typevar_context: error_context,
|
||||||
|
},
|
||||||
|
BoundSuperError::FailingConditionCheck {
|
||||||
|
pivot_class,
|
||||||
|
owner: _,
|
||||||
|
typevar_context: _,
|
||||||
|
} => BoundSuperError::FailingConditionCheck {
|
||||||
|
pivot_class,
|
||||||
|
owner: owner_type,
|
||||||
|
typevar_context: error_context,
|
||||||
|
},
|
||||||
|
BoundSuperError::InvalidPivotClassType { pivot_class } => {
|
||||||
|
BoundSuperError::InvalidPivotClassType { pivot_class }
|
||||||
|
}
|
||||||
|
BoundSuperError::UnavailableImplicitArguments => {
|
||||||
|
BoundSuperError::UnavailableImplicitArguments
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
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() {
|
||||||
|
SubclassOfInner::Class(class) => SuperOwnerKind::Class(class),
|
||||||
|
SubclassOfInner::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic),
|
||||||
|
},
|
||||||
|
Type::NominalInstance(instance) => SuperOwnerKind::Instance(instance),
|
||||||
|
|
||||||
|
Type::ProtocolInstance(protocol) => {
|
||||||
|
if let Some(nominal_instance) = protocol.as_nominal_type() {
|
||||||
|
SuperOwnerKind::Instance(nominal_instance)
|
||||||
|
} else {
|
||||||
|
return Err(BoundSuperError::AbstractOwnerType {
|
||||||
|
owner_type,
|
||||||
|
pivot_class: pivot_class_type,
|
||||||
|
typevar_context: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::Union(union) => {
|
||||||
|
return Ok(union
|
||||||
.elements(db)
|
.elements(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| BoundSuperType::build(db, pivot_class_type, *ty))
|
.try_fold(UnionBuilder::new(db), |builder, element| {
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
delegate_to(*element).map(|ty| builder.add(ty))
|
||||||
));
|
})?
|
||||||
}
|
.build());
|
||||||
|
}
|
||||||
|
Type::Intersection(intersection) => {
|
||||||
|
let mut builder = IntersectionBuilder::new(db);
|
||||||
|
let mut one_good_element_found = false;
|
||||||
|
for positive in intersection.positive(db) {
|
||||||
|
if let Ok(good_element) = delegate_to(*positive) {
|
||||||
|
one_good_element_found = true;
|
||||||
|
builder = builder.add_positive(good_element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !one_good_element_found {
|
||||||
|
return Err(BoundSuperError::AbstractOwnerType {
|
||||||
|
owner_type,
|
||||||
|
pivot_class: pivot_class_type,
|
||||||
|
typevar_context: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for negative in intersection.negative(db) {
|
||||||
|
if let Ok(good_element) = delegate_to(*negative) {
|
||||||
|
builder = builder.add_negative(good_element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(builder.build());
|
||||||
|
}
|
||||||
|
Type::TypeAlias(alias) => {
|
||||||
|
return delegate_with_error_mapped(alias.value_type(db), None);
|
||||||
|
}
|
||||||
|
Type::TypeVar(type_var) | Type::NonInferableTypeVar(type_var) => {
|
||||||
|
let type_var = type_var.typevar(db);
|
||||||
|
return match type_var.bound_or_constraints(db) {
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||||
|
delegate_with_error_mapped(bound, Some(type_var))
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||||
|
delegate_with_error_mapped(Type::Union(constraints), Some(type_var))
|
||||||
|
}
|
||||||
|
None => delegate_with_error_mapped(Type::object(), Some(type_var)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Type::BooleanLiteral(_) | Type::TypeIs(_) => {
|
||||||
|
return delegate_to(KnownClass::Bool.to_instance(db));
|
||||||
|
}
|
||||||
|
Type::IntLiteral(_) => return delegate_to(KnownClass::Int.to_instance(db)),
|
||||||
|
Type::StringLiteral(_) | Type::LiteralString => {
|
||||||
|
return delegate_to(KnownClass::Str.to_instance(db));
|
||||||
|
}
|
||||||
|
Type::BytesLiteral(_) => {
|
||||||
|
return delegate_to(KnownClass::Bytes.to_instance(db));
|
||||||
|
}
|
||||||
|
Type::SpecialForm(special_form) => {
|
||||||
|
return delegate_to(special_form.instance_fallback(db));
|
||||||
|
}
|
||||||
|
Type::KnownInstance(instance) => {
|
||||||
|
return delegate_to(instance.instance_fallback(db));
|
||||||
|
}
|
||||||
|
Type::FunctionLiteral(_) | Type::DataclassDecorator(_) => {
|
||||||
|
return delegate_to(KnownClass::FunctionType.to_instance(db));
|
||||||
|
}
|
||||||
|
Type::WrapperDescriptor(_) => {
|
||||||
|
return delegate_to(KnownClass::WrapperDescriptorType.to_instance(db));
|
||||||
|
}
|
||||||
|
Type::KnownBoundMethod(method) => {
|
||||||
|
return delegate_to(method.class().to_instance(db));
|
||||||
|
}
|
||||||
|
Type::BoundMethod(_) => return delegate_to(KnownClass::MethodType.to_instance(db)),
|
||||||
|
Type::ModuleLiteral(_) => {
|
||||||
|
return delegate_to(KnownClass::ModuleType.to_instance(db));
|
||||||
|
}
|
||||||
|
Type::GenericAlias(_) => return delegate_to(KnownClass::GenericAlias.to_instance(db)),
|
||||||
|
Type::PropertyInstance(_) => return delegate_to(KnownClass::Property.to_instance(db)),
|
||||||
|
Type::EnumLiteral(enum_literal_type) => {
|
||||||
|
return delegate_to(enum_literal_type.enum_class_instance(db));
|
||||||
|
}
|
||||||
|
Type::BoundSuper(_) => return delegate_to(KnownClass::Super.to_instance(db)),
|
||||||
|
Type::TypedDict(td) => {
|
||||||
|
// In general it isn't sound to upcast a `TypedDict` to a `dict`,
|
||||||
|
// but here it seems like it's probably sound?
|
||||||
|
let mut key_builder = UnionBuilder::new(db);
|
||||||
|
let mut value_builder = UnionBuilder::new(db);
|
||||||
|
for (name, field) in td.items(db) {
|
||||||
|
key_builder = key_builder.add(Type::string_literal(db, &name));
|
||||||
|
value_builder = value_builder.add(field.declared_ty);
|
||||||
|
}
|
||||||
|
return delegate_to(
|
||||||
|
KnownClass::Dict
|
||||||
|
.to_specialized_instance(db, [key_builder.build(), value_builder.build()]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Type::Callable(callable) if callable.is_function_like(db) => {
|
||||||
|
return delegate_to(KnownClass::FunctionType.to_instance(db));
|
||||||
|
}
|
||||||
|
Type::AlwaysFalsy
|
||||||
|
| Type::AlwaysTruthy
|
||||||
|
| Type::Callable(_)
|
||||||
|
| Type::DataclassTransformer(_) => {
|
||||||
|
return Err(BoundSuperError::AbstractOwnerType {
|
||||||
|
owner_type,
|
||||||
|
pivot_class: pivot_class_type,
|
||||||
|
typevar_context: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// We don't use `Classbase::try_from_type` here because:
|
// We don't use `Classbase::try_from_type` here because:
|
||||||
// - There are objects that may validly be present in a class's bases list
|
// - There are objects that may validly be present in a class's bases list
|
||||||
|
|
@ -11452,24 +11670,22 @@ impl<'db> BoundSuperType<'db> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let owner = SuperOwnerKind::try_from_type(db, owner_type)
|
if let Some(pivot_class) = pivot_class.into_class()
|
||||||
.and_then(|owner| {
|
&& let Some(owner_class) = owner.into_class(db)
|
||||||
let Some(pivot_class) = pivot_class.into_class() else {
|
{
|
||||||
return Some(owner);
|
let pivot_class = pivot_class.class_literal(db).0;
|
||||||
};
|
if !owner_class.iter_mro(db).any(|superclass| match superclass {
|
||||||
let Some(owner_class) = owner.into_class(db) else {
|
ClassBase::Dynamic(_) => true,
|
||||||
return Some(owner);
|
ClassBase::Generic | ClassBase::Protocol | ClassBase::TypedDict => false,
|
||||||
};
|
ClassBase::Class(superclass) => superclass.class_literal(db).0 == pivot_class,
|
||||||
if owner_class.is_subclass_of(db, pivot_class) {
|
}) {
|
||||||
Some(owner)
|
return Err(BoundSuperError::FailingConditionCheck {
|
||||||
} else {
|
pivot_class: pivot_class_type,
|
||||||
None
|
owner: owner_type,
|
||||||
}
|
typevar_context: None,
|
||||||
})
|
});
|
||||||
.ok_or(BoundSuperError::FailingConditionCheck {
|
}
|
||||||
pivot_class: pivot_class_type,
|
}
|
||||||
owner: owner_type,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Type::BoundSuper(BoundSuperType::new(
|
Ok(Type::BoundSuper(BoundSuperType::new(
|
||||||
db,
|
db,
|
||||||
|
|
@ -11525,19 +11741,22 @@ impl<'db> BoundSuperType<'db> {
|
||||||
db,
|
db,
|
||||||
attribute,
|
attribute,
|
||||||
Type::none(db),
|
Type::none(db),
|
||||||
owner.into_type(),
|
Type::from(owner),
|
||||||
)
|
)
|
||||||
.0,
|
.0,
|
||||||
),
|
),
|
||||||
SuperOwnerKind::Instance(_) => Some(
|
SuperOwnerKind::Instance(_) => {
|
||||||
Type::try_call_dunder_get_on_attribute(
|
let owner = Type::from(owner);
|
||||||
db,
|
Some(
|
||||||
attribute,
|
Type::try_call_dunder_get_on_attribute(
|
||||||
owner.into_type(),
|
db,
|
||||||
owner.into_type().to_meta_type(db),
|
attribute,
|
||||||
|
owner,
|
||||||
|
owner.to_meta_type(db),
|
||||||
|
)
|
||||||
|
.0,
|
||||||
)
|
)
|
||||||
.0,
|
}
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -11551,9 +11770,8 @@ impl<'db> BoundSuperType<'db> {
|
||||||
) -> PlaceAndQualifiers<'db> {
|
) -> PlaceAndQualifiers<'db> {
|
||||||
let owner = self.owner(db);
|
let owner = self.owner(db);
|
||||||
let class = match owner {
|
let class = match owner {
|
||||||
SuperOwnerKind::Dynamic(_) => {
|
SuperOwnerKind::Dynamic(dynamic) => {
|
||||||
return owner
|
return Type::Dynamic(dynamic)
|
||||||
.into_type()
|
|
||||||
.find_name_in_mro_with_policy(db, name, policy)
|
.find_name_in_mro_with_policy(db, name, policy)
|
||||||
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`");
|
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -571,9 +571,7 @@ impl Display for DisplayRepresentation<'_> {
|
||||||
"<super: {pivot}, {owner}>",
|
"<super: {pivot}, {owner}>",
|
||||||
pivot = Type::from(bound_super.pivot_class(self.db))
|
pivot = Type::from(bound_super.pivot_class(self.db))
|
||||||
.display_with(self.db, self.settings.singleline()),
|
.display_with(self.db, self.settings.singleline()),
|
||||||
owner = bound_super
|
owner = Type::from(bound_super.owner(self.db))
|
||||||
.owner(self.db)
|
|
||||||
.into_type()
|
|
||||||
.display_with(self.db, self.settings.singleline())
|
.display_with(self.db, self.settings.singleline())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,31 +156,27 @@ impl<'db> Type<'db> {
|
||||||
// This matches the behaviour of other type checkers, and is required for us to
|
// This matches the behaviour of other type checkers, and is required for us to
|
||||||
// recognise `str` as a subtype of `Container[str]`.
|
// recognise `str` as a subtype of `Container[str]`.
|
||||||
structurally_satisfied.or(db, || {
|
structurally_satisfied.or(db, || {
|
||||||
if let Protocol::FromClass(class) = protocol.inner {
|
let Some(nominal_instance) = protocol.as_nominal_type() else {
|
||||||
// if `self` and `other` are *both* protocols, we also need to treat `self` as if it
|
return ConstraintSet::from(false);
|
||||||
// were a nominal type, or we won't consider a protocol `P` that explicitly inherits
|
};
|
||||||
// from a protocol `Q` to be a subtype of `Q` to be a subtype of `Q` if it overrides
|
|
||||||
// `Q`'s members in a Liskov-incompatible way.
|
|
||||||
let type_to_test = if let Type::ProtocolInstance(ProtocolInstanceType {
|
|
||||||
inner: Protocol::FromClass(class),
|
|
||||||
..
|
|
||||||
}) = self
|
|
||||||
{
|
|
||||||
Type::non_tuple_instance(db, class)
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
};
|
|
||||||
|
|
||||||
type_to_test.has_relation_to_impl(
|
// if `self` and `other` are *both* protocols, we also need to treat `self` as if it
|
||||||
db,
|
// were a nominal type, or we won't consider a protocol `P` that explicitly inherits
|
||||||
Type::non_tuple_instance(db, class),
|
// from a protocol `Q` to be a subtype of `Q` to be a subtype of `Q` if it overrides
|
||||||
relation,
|
// `Q`'s members in a Liskov-incompatible way.
|
||||||
relation_visitor,
|
let type_to_test = self
|
||||||
disjointness_visitor,
|
.into_protocol_instance()
|
||||||
)
|
.and_then(ProtocolInstanceType::as_nominal_type)
|
||||||
} else {
|
.map(Type::NominalInstance)
|
||||||
ConstraintSet::from(false)
|
.unwrap_or(self);
|
||||||
}
|
|
||||||
|
type_to_test.has_relation_to_impl(
|
||||||
|
db,
|
||||||
|
Type::NominalInstance(nominal_instance),
|
||||||
|
relation,
|
||||||
|
relation_visitor,
|
||||||
|
disjointness_visitor,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -605,6 +601,20 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this is a class-based protocol, convert the protocol-instance into a nominal instance.
|
||||||
|
///
|
||||||
|
/// If this is a synthesized protocol that does not correspond to a class definition
|
||||||
|
/// in source code, return `None`. These are "pure" abstract types, that cannot be
|
||||||
|
/// treated in a nominal way.
|
||||||
|
pub(super) fn as_nominal_type(self) -> Option<NominalInstanceType<'db>> {
|
||||||
|
match self.inner {
|
||||||
|
Protocol::FromClass(class) => {
|
||||||
|
Some(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
|
||||||
|
}
|
||||||
|
Protocol::Synthesized(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the meta-type of this protocol-instance type.
|
/// Return the meta-type of this protocol-instance type.
|
||||||
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
match self.inner {
|
match self.inner {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue