[ty] Infer type of self as typing.Self in method body (#18473)

Part of https://github.com/astral-sh/ty/issues/159

Add support for adding a synthetic `typing.Self` type for `self`
arguments in methods.
`typing.Self` is assigned as the type if there are no annotations.

- Updated md tests.

---------

Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
Shaygan Hooshyari 2025-10-16 16:39:17 +02:00 committed by David Peter
parent 596ee17ec3
commit 195669f33d
15 changed files with 288 additions and 203 deletions

View File

@ -667,7 +667,7 @@ fn attrs(criterion: &mut Criterion) {
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313, python_version: PythonVersion::PY313,
}, },
100, 110,
); );
bench_project(&benchmark, criterion); bench_project(&benchmark, criterion);
@ -684,7 +684,7 @@ fn anyio(criterion: &mut Criterion) {
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313, python_version: PythonVersion::PY313,
}, },
100, 150,
); );
bench_project(&benchmark, criterion); bench_project(&benchmark, criterion);

View File

@ -210,7 +210,7 @@ static TANJUN: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312, python_version: PythonVersion::PY312,
}, },
100, 320,
); );
static STATIC_FRAME: Benchmark = Benchmark::new( static STATIC_FRAME: Benchmark = Benchmark::new(

View File

@ -1957,14 +1957,31 @@ class Quux:
", ",
); );
// FIXME: This should list completions on `self`, which should assert_snapshot!(test.completions_without_builtins(), @r"
// include, at least, `foo` and `bar`. At time of writing __annotations__
// (2025-06-04), the type of `self` is inferred as `Unknown` in __class__
// this context. This in turn prevents us from getting a list __delattr__
// of available attributes. __dict__
// __dir__
// See: https://github.com/astral-sh/ty/issues/159 __doc__
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>"); __eq__
__format__
__getattribute__
__getstate__
__hash__
__init__
__init_subclass__
__module__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
");
} }
#[test] #[test]

View File

@ -1809,12 +1809,12 @@ class BoundedContainer[T: int, U = str]:
"get_first" @ 642..651: Method [definition] "get_first" @ 642..651: Method [definition]
"self" @ 652..656: SelfParameter "self" @ 652..656: SelfParameter
"T" @ 661..662: TypeParameter "T" @ 661..662: TypeParameter
"self" @ 679..683: Variable "self" @ 679..683: TypeParameter
"value1" @ 684..690: Variable "value1" @ 684..690: Variable
"get_second" @ 700..710: Method [definition] "get_second" @ 700..710: Method [definition]
"self" @ 711..715: SelfParameter "self" @ 711..715: SelfParameter
"U" @ 720..721: TypeParameter "U" @ 720..721: TypeParameter
"self" @ 738..742: Variable "self" @ 738..742: TypeParameter
"value2" @ 743..749: Variable "value2" @ 743..749: Variable
"BoundedContainer" @ 798..814: Class [definition] "BoundedContainer" @ 798..814: Class [definition]
"T" @ 815..816: TypeParameter [definition] "T" @ 815..816: TypeParameter [definition]

View File

@ -64,8 +64,7 @@ from typing import Self
class A: class A:
def implicit_self(self) -> Self: def implicit_self(self) -> Self:
# TODO: This should be Self@implicit_self reveal_type(self) # revealed: Self@implicit_self
reveal_type(self) # revealed: Unknown
return self return self
@ -127,19 +126,16 @@ The name `self` is not special in any way.
```py ```py
class B: class B:
def name_does_not_matter(this) -> Self: def name_does_not_matter(this) -> Self:
# TODO: Should reveal Self@name_does_not_matter reveal_type(this) # revealed: Self@name_does_not_matter
reveal_type(this) # revealed: Unknown
return this return this
def positional_only(self, /, x: int) -> Self: def positional_only(self, /, x: int) -> Self:
# TODO: Should reveal Self@positional_only reveal_type(self) # revealed: Self@positional_only
reveal_type(self) # revealed: Unknown
return self return self
def keyword_only(self, *, x: int) -> Self: def keyword_only(self, *, x: int) -> Self:
# TODO: Should reveal Self@keyword_only reveal_type(self) # revealed: Self@keyword_only
reveal_type(self) # revealed: Unknown
return self return self
@property @property
@ -165,8 +161,7 @@ T = TypeVar("T")
class G(Generic[T]): class G(Generic[T]):
def id(self) -> Self: def id(self) -> Self:
# TODO: Should reveal Self@id reveal_type(self) # revealed: Self@id
reveal_type(self) # revealed: Unknown
return self return self
@ -252,6 +247,20 @@ class LinkedList:
reveal_type(LinkedList().next()) # revealed: LinkedList reveal_type(LinkedList().next()) # revealed: LinkedList
``` ```
Attributes can also refer to a generic parameter:
```py
from typing import Generic, TypeVar
T = TypeVar("T")
class C(Generic[T]):
foo: T
def method(self) -> None:
reveal_type(self) # revealed: Self@method
reveal_type(self.foo) # revealed: T@C
```
## Generic Classes ## Generic Classes
```py ```py
@ -342,31 +351,28 @@ b: Self
# TODO: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self" # TODO: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self"
class Foo: class Foo:
# TODO: rejected Self because self has a different type # TODO: This `self: T` annotation should be rejected because `T` is not `Self`
def has_existing_self_annotation(self: T) -> Self: def has_existing_self_annotation(self: T) -> Self:
return self # error: [invalid-return-type] return self # error: [invalid-return-type]
def return_concrete_type(self) -> Self: def return_concrete_type(self) -> Self:
# TODO: tell user to use "Foo" instead of "Self" # TODO: We could emit a hint that suggests annotating with `Foo` instead of `Self`
# error: [invalid-return-type] # error: [invalid-return-type]
return Foo() return Foo()
@staticmethod @staticmethod
# TODO: reject because of staticmethod # TODO: The usage of `Self` here should be rejected because this is a static method
def make() -> Self: def make() -> Self:
# error: [invalid-return-type] # error: [invalid-return-type]
return Foo() return Foo()
class Bar(Generic[T]): class Bar(Generic[T]): ...
foo: T
def bar(self) -> T:
return self.foo
# error: [invalid-type-form] # error: [invalid-type-form]
class Baz(Bar[Self]): ... class Baz(Bar[Self]): ...
class MyMetaclass(type): class MyMetaclass(type):
# TODO: rejected # TODO: reject the Self usage. because self cannot be used within a metaclass.
def __new__(cls) -> Self: def __new__(cls) -> Self:
return super().__new__(cls) return super().__new__(cls)
``` ```

View File

@ -26,9 +26,7 @@ class C:
c_instance = C(1) c_instance = C(1)
reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"] reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"]
reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown | Literal[1, "a"]
# TODO: Same here. This should be `Unknown | Literal[1, "a"]`
reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
# There is no special handling of attributes that are (directly) assigned to a declared parameter, # There is no special handling of attributes that are (directly) assigned to a declared parameter,
# which means we union with `Unknown` here, since the attribute itself is not declared. This is # which means we union with `Unknown` here, since the attribute itself is not declared. This is
@ -177,8 +175,7 @@ c_instance = C(1)
reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"] reveal_type(c_instance.inferred_from_value) # revealed: Unknown | Literal[1, "a"]
# TODO: Should be `Unknown | Literal[1, "a"]` reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown | Literal[1, "a"]
reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None
@ -399,9 +396,19 @@ class TupleIterable:
class C: class C:
def __init__(self) -> None: def __init__(self) -> None:
# TODO: Should not emit this diagnostic
# error: [unresolved-attribute]
[... for self.a in IntIterable()] [... for self.a in IntIterable()]
# TODO: Should not emit this diagnostic
# error: [unresolved-attribute]
# error: [unresolved-attribute]
[... for (self.b, self.c) in TupleIterable()] [... for (self.b, self.c) in TupleIterable()]
# TODO: Should not emit this diagnostic
# error: [unresolved-attribute]
# error: [unresolved-attribute]
[... for self.d in IntIterable() for self.e in IntIterable()] [... for self.d in IntIterable() for self.e in IntIterable()]
# TODO: Should not emit this diagnostic
# error: [unresolved-attribute]
[[... for self.f in IntIterable()] for _ in IntIterable()] [[... for self.f in IntIterable()] for _ in IntIterable()]
[[... for self.g in IntIterable()] for self in [D()]] [[... for self.g in IntIterable()] for self in [D()]]
@ -598,6 +605,8 @@ class C:
self.c = c self.c = c
if False: if False:
def set_e(self, e: str) -> None: def set_e(self, e: str) -> None:
# TODO: Should not emit this diagnostic
# error: [unresolved-attribute]
self.e = e self.e = e
# TODO: this would ideally be `Unknown | Literal[1]` # TODO: this would ideally be `Unknown | Literal[1]`
@ -685,7 +694,7 @@ class C:
pure_class_variable2: ClassVar = 1 pure_class_variable2: ClassVar = 1
def method(self): def method(self):
# TODO: this should be an error # error: [invalid-attribute-access] "Cannot assign to ClassVar `pure_class_variable1` from an instance of type `Self@method`"
self.pure_class_variable1 = "value set through instance" self.pure_class_variable1 = "value set through instance"
reveal_type(C.pure_class_variable1) # revealed: str reveal_type(C.pure_class_variable1) # revealed: str
@ -885,11 +894,9 @@ class Intermediate(Base):
# TODO: This should be an error (violates Liskov) # TODO: This should be an error (violates Liskov)
self.redeclared_in_method_with_wider_type: object = object() self.redeclared_in_method_with_wider_type: object = object()
# TODO: This should be an `invalid-assignment` error self.overwritten_in_subclass_method = None # error: [invalid-assignment]
self.overwritten_in_subclass_method = None
# TODO: This should be an `invalid-assignment` error self.pure_overwritten_in_subclass_method = None # error: [invalid-assignment]
self.pure_overwritten_in_subclass_method = None
self.pure_undeclared = "intermediate" self.pure_undeclared = "intermediate"
@ -1839,6 +1846,7 @@ def external_getattribute(name) -> int:
class ThisFails: class ThisFails:
def __init__(self): def __init__(self):
# error: [invalid-assignment] "Implicit shadowing of function `__getattribute__`"
self.__getattribute__ = external_getattribute self.__getattribute__ = external_getattribute
# error: [unresolved-attribute] # error: [unresolved-attribute]

View File

@ -205,7 +205,7 @@ class C:
return str(key) return str(key)
def f(self): def f(self):
# TODO: This should emit an `invalid-assignment` diagnostic once we understand the type of `self` # error: [invalid-assignment] "Implicit shadowing of function `__getitem__`"
self.__getitem__ = None self.__getitem__ = None
# This is still fine, and simply calls the `__getitem__` method on the class # This is still fine, and simply calls the `__getitem__` method on the class

View File

@ -163,14 +163,13 @@ class A:
class B(A): 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>` reveal_type(super()) # revealed: <super: <class 'B'>, B>
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super> reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
super().__init__(a) super().__init__(a)
@classmethod @classmethod
def f(cls): def f(cls):
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, <class 'B'>>` # TODO: Once `cls` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown> reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
super().f() super().f()
@ -358,15 +357,15 @@ from __future__ import annotations
class A: class A:
def test(self): def test(self):
reveal_type(super()) # revealed: <super: <class 'A'>, Unknown> reveal_type(super()) # revealed: <super: <class 'A'>, A>
class B: class B:
def test(self): def test(self):
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown> reveal_type(super()) # revealed: <super: <class 'B'>, B>
class C(A.B): class C(A.B):
def test(self): def test(self):
reveal_type(super()) # revealed: <super: <class 'C'>, Unknown> reveal_type(super()) # revealed: <super: <class 'C'>, C>
def inner(t: C): def inner(t: C):
reveal_type(super()) # revealed: <super: <class 'B'>, C> reveal_type(super()) # revealed: <super: <class 'B'>, C>
@ -616,7 +615,7 @@ class A:
class B(A): class B(A):
def __init__(self, a: int): def __init__(self, a: int):
super().__init__(a) super().__init__(a)
# TODO: Once `Self` is supported, this should raise `unresolved-attribute` error # error: [unresolved-attribute] "Type `<super: <class 'B'>, B>` has no attribute `a`"
super().a super().a
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`" # error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"

View File

@ -170,6 +170,7 @@ def f1(flag: bool):
attr = DataDescriptor() attr = DataDescriptor()
def f(self): def f(self):
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `attr` on type `Self@f` with custom `__set__` method"
self.attr = "normal" self.attr = "normal"
reveal_type(C1().attr) # revealed: Unknown | Literal["data", "normal"] reveal_type(C1().attr) # revealed: Unknown | Literal["data", "normal"]

View File

@ -208,8 +208,7 @@ class SuperUser(User):
def now_called_robert(self): def now_called_robert(self):
self.name = "Robert" # fine because overridden with a mutable attribute self.name = "Robert" # fine because overridden with a mutable attribute
# TODO: this should cause us to emit an error as we're assigning to a read-only property # error: 9 [invalid-assignment] "Cannot assign to read-only property `nickname` on object of type `Self@now_called_robert`"
# inherited from the `NamedTuple` superclass (requires https://github.com/astral-sh/ty/issues/159)
self.nickname = "Bob" self.nickname = "Bob"
james = SuperUser(0, "James", 42, "Jimmy") james = SuperUser(0, "James", 42, "Jimmy")

View File

@ -21,144 +21,143 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
7 | 7 |
8 | class B(A): 8 | class B(A):
9 | def __init__(self, a: int): 9 | def __init__(self, a: int):
10 | # TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>` 10 | reveal_type(super()) # revealed: <super: <class 'B'>, B>
11 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown> 11 | reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
12 | reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super> 12 | super().__init__(a)
13 | super().__init__(a) 13 |
14 | 14 | @classmethod
15 | @classmethod 15 | def f(cls):
16 | def f(cls): 16 | # TODO: Once `cls` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
17 | # TODO: Once `Self` is supported, this should be `<super: <class 'B'>, <class 'B'>>` 17 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
18 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown> 18 | super().f()
19 | super().f() 19 |
20 | 20 | super(B, B(42)).__init__(42)
21 | super(B, B(42)).__init__(42) 21 | super(B, B).f()
22 | super(B, B).f() 22 | import enum
23 | import enum 23 | from typing import Any, Self, Never, Protocol, Callable
24 | from typing import Any, Self, Never, Protocol, Callable 24 | from ty_extensions import Intersection
25 | from ty_extensions import Intersection 25 |
26 | 26 | class BuilderMeta(type):
27 | class BuilderMeta(type): 27 | def __new__(
28 | def __new__( 28 | cls: type[Any],
29 | cls: type[Any], 29 | name: str,
30 | name: str, 30 | bases: tuple[type, ...],
31 | bases: tuple[type, ...], 31 | dct: dict[str, Any],
32 | dct: dict[str, Any], 32 | ) -> BuilderMeta:
33 | ) -> BuilderMeta: 33 | # revealed: <super: <class 'BuilderMeta'>, Any>
34 | # revealed: <super: <class 'BuilderMeta'>, Any> 34 | s = reveal_type(super())
35 | s = reveal_type(super()) 35 | # revealed: Any
36 | # revealed: Any 36 | return reveal_type(s.__new__(cls, name, bases, dct))
37 | return reveal_type(s.__new__(cls, name, bases, dct)) 37 |
38 | 38 | class BuilderMeta2(type):
39 | class BuilderMeta2(type): 39 | def __new__(
40 | def __new__( 40 | cls: type[BuilderMeta2],
41 | cls: type[BuilderMeta2], 41 | name: str,
42 | name: str, 42 | bases: tuple[type, ...],
43 | bases: tuple[type, ...], 43 | dct: dict[str, Any],
44 | dct: dict[str, Any], 44 | ) -> BuilderMeta2:
45 | ) -> BuilderMeta2: 45 | # revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
46 | # revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>> 46 | s = reveal_type(super())
47 | s = reveal_type(super()) 47 | # TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
48 | # TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501) 48 | # revealed: Unknown
49 | # revealed: Unknown 49 | return reveal_type(s.__new__(cls, name, bases, dct))
50 | return reveal_type(s.__new__(cls, name, bases, dct)) 50 |
51 | 51 | class Foo[T]:
52 | class Foo[T]: 52 | x: T
53 | x: T 53 |
54 | 54 | def method(self: Any):
55 | def method(self: Any): 55 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
56 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any> 56 |
57 | 57 | if isinstance(self, Foo):
58 | if isinstance(self, Foo): 58 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
59 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any> 59 |
60 | 60 | def method2(self: Foo[T]):
61 | def method2(self: Foo[T]): 61 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
62 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]> 62 | reveal_type(super())
63 | reveal_type(super()) 63 |
64 | 64 | def method3(self: Foo):
65 | def method3(self: Foo): 65 | # revealed: <super: <class 'Foo'>, Foo[Unknown]>
66 | # revealed: <super: <class 'Foo'>, Foo[Unknown]> 66 | reveal_type(super())
67 | reveal_type(super()) 67 |
68 | 68 | def method4(self: Self):
69 | def method4(self: Self): 69 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
70 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]> 70 | reveal_type(super())
71 | reveal_type(super()) 71 |
72 | 72 | def method5[S: Foo[int]](self: S, other: S) -> S:
73 | def method5[S: Foo[int]](self: S, other: S) -> S: 73 | # revealed: <super: <class 'Foo'>, Foo[int]>
74 | # revealed: <super: <class 'Foo'>, Foo[int]> 74 | reveal_type(super())
75 | reveal_type(super()) 75 | return self
76 | return self 76 |
77 | 77 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
78 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S: 78 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
79 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]> 79 | reveal_type(super())
80 | reveal_type(super()) 80 | return self
81 | return self 81 |
82 | 82 | def method7[S](self: S, other: S) -> S:
83 | def method7[S](self: S, other: S) -> S: 83 | # error: [invalid-super-argument]
84 | # error: [invalid-super-argument] 84 | # revealed: Unknown
85 | # revealed: Unknown 85 | reveal_type(super())
86 | reveal_type(super()) 86 | return self
87 | return self 87 |
88 | 88 | def method8[S: int](self: S, other: S) -> S:
89 | def method8[S: int](self: S, other: S) -> S: 89 | # error: [invalid-super-argument]
90 | # error: [invalid-super-argument] 90 | # revealed: Unknown
91 | # revealed: Unknown 91 | reveal_type(super())
92 | reveal_type(super()) 92 | return self
93 | return self 93 |
94 | 94 | def method9[S: (int, str)](self: S, other: S) -> S:
95 | def method9[S: (int, str)](self: S, other: S) -> S: 95 | # error: [invalid-super-argument]
96 | # error: [invalid-super-argument] 96 | # revealed: Unknown
97 | # revealed: Unknown 97 | reveal_type(super())
98 | reveal_type(super()) 98 | return self
99 | return self 99 |
100 | 100 | def method10[S: Callable[..., str]](self: S, other: S) -> S:
101 | def method10[S: Callable[..., str]](self: S, other: S) -> S: 101 | # error: [invalid-super-argument]
102 | # error: [invalid-super-argument] 102 | # revealed: Unknown
103 | # revealed: Unknown 103 | reveal_type(super())
104 | reveal_type(super()) 104 | return self
105 | return self 105 |
106 | 106 | type Alias = Bar
107 | type Alias = Bar 107 |
108 | 108 | class Bar:
109 | class Bar: 109 | def method(self: Alias):
110 | def method(self: Alias): 110 | # revealed: <super: <class 'Bar'>, Bar>
111 | # revealed: <super: <class 'Bar'>, Bar> 111 | reveal_type(super())
112 | reveal_type(super()) 112 |
113 | 113 | def pls_dont_call_me(self: Never):
114 | def pls_dont_call_me(self: Never): 114 | # revealed: <super: <class 'Bar'>, Unknown>
115 | # revealed: <super: <class 'Bar'>, Unknown> 115 | reveal_type(super())
116 | reveal_type(super()) 116 |
117 | 117 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
118 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]): 118 | # revealed: <super: <class 'Bar'>, Bar>
119 | # revealed: <super: <class 'Bar'>, Bar> 119 | reveal_type(super())
120 | reveal_type(super()) 120 |
121 | 121 | class P(Protocol):
122 | class P(Protocol): 122 | def method(self: P):
123 | def method(self: P): 123 | # revealed: <super: <class 'P'>, P>
124 | # revealed: <super: <class 'P'>, P> 124 | reveal_type(super())
125 | reveal_type(super()) 125 |
126 | 126 | class E(enum.Enum):
127 | class E(enum.Enum): 127 | X = 1
128 | X = 1 128 |
129 | 129 | def method(self: E):
130 | def method(self: E): 130 | match self:
131 | match self: 131 | case E.X:
132 | case E.X: 132 | # revealed: <super: <class 'E'>, E>
133 | # revealed: <super: <class 'E'>, E> 133 | reveal_type(super())
134 | 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:86:21 --> src/mdtest_snippet.py:85:21
| |
84 | # error: [invalid-super-argument] 83 | # error: [invalid-super-argument]
85 | # revealed: Unknown 84 | # revealed: Unknown
86 | reveal_type(super()) 85 | reveal_type(super())
| ^^^^^^^ | ^^^^^^^
87 | return self 86 | 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'>`
@ -169,13 +168,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:92:21 --> src/mdtest_snippet.py:91:21
| |
90 | # error: [invalid-super-argument] 89 | # error: [invalid-super-argument]
91 | # revealed: Unknown 90 | # revealed: Unknown
92 | reveal_type(super()) 91 | reveal_type(super())
| ^^^^^^^ | ^^^^^^^
93 | return self 92 | 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'>`
@ -185,13 +184,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:98:21 --> src/mdtest_snippet.py:97:21
| |
96 | # error: [invalid-super-argument] 95 | # error: [invalid-super-argument]
97 | # revealed: Unknown 96 | # revealed: Unknown
98 | reveal_type(super()) 97 | reveal_type(super())
| ^^^^^^^ | ^^^^^^^
99 | return self 98 | 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'>`
@ -201,13 +200,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:104:21 --> src/mdtest_snippet.py:103:21
| |
102 | # error: [invalid-super-argument] 101 | # error: [invalid-super-argument]
103 | # revealed: Unknown 102 | # revealed: Unknown
104 | reveal_type(super()) 103 | reveal_type(super())
| ^^^^^^^ | ^^^^^^^
105 | return self 104 | 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

View File

@ -88,6 +88,8 @@ class C:
self.FINAL_C: Final[int] = 1 self.FINAL_C: Final[int] = 1
self.FINAL_D: Final = 1 self.FINAL_D: Final = 1
self.FINAL_E: Final self.FINAL_E: Final
# TODO: Should not be an error
# error: [invalid-assignment] "Cannot assign to final attribute `FINAL_E` on type `Self@__init__`"
self.FINAL_E = 1 self.FINAL_E = 1
reveal_type(C.FINAL_A) # revealed: int reveal_type(C.FINAL_A) # revealed: int
@ -184,6 +186,7 @@ class C(metaclass=Meta):
self.INSTANCE_FINAL_A: Final[int] = 1 self.INSTANCE_FINAL_A: Final[int] = 1
self.INSTANCE_FINAL_B: Final = 1 self.INSTANCE_FINAL_B: Final = 1
self.INSTANCE_FINAL_C: Final[int] self.INSTANCE_FINAL_C: Final[int]
# error: [invalid-assignment] "Cannot assign to final attribute `INSTANCE_FINAL_C` on type `Self@__init__`"
self.INSTANCE_FINAL_C = 1 self.INSTANCE_FINAL_C = 1
# error: [invalid-assignment] "Cannot assign to final attribute `META_FINAL_A` on type `<class 'C'>`" # error: [invalid-assignment] "Cannot assign to final attribute `META_FINAL_A` on type `<class 'C'>`"
@ -278,6 +281,8 @@ class C:
def __init__(self): def __init__(self):
self.LEGAL_H: Final[int] = 1 self.LEGAL_H: Final[int] = 1
self.LEGAL_I: Final[int] self.LEGAL_I: Final[int]
# TODO: Should not be an error
# error: [invalid-assignment]
self.LEGAL_I = 1 self.LEGAL_I = 1
# error: [invalid-type-form] "`Final` is not allowed in function parameter annotations" # error: [invalid-type-form] "`Final` is not allowed in function parameter annotations"
@ -390,6 +395,8 @@ class C:
DEFINED_IN_INIT: Final[int] DEFINED_IN_INIT: Final[int]
def __init__(self): def __init__(self):
# TODO: should not be an error
# error: [invalid-assignment]
self.DEFINED_IN_INIT = 1 self.DEFINED_IN_INIT = 1
``` ```

View File

@ -206,7 +206,7 @@ enum ReduceResult<'db> {
// //
// For now (until we solve https://github.com/astral-sh/ty/issues/957), keep this number // For now (until we solve https://github.com/astral-sh/ty/issues/957), keep this number
// below 200, which is the salsa fixpoint iteration limit. // below 200, which is the salsa fixpoint iteration limit.
const MAX_UNION_LITERALS: usize = 199; const MAX_UNION_LITERALS: usize = 190;
pub(crate) struct UnionBuilder<'db> { pub(crate) struct UnionBuilder<'db> {
elements: Vec<UnionElement<'db>>, elements: Vec<UnionElement<'db>>,

View File

@ -80,11 +80,11 @@ pub(crate) fn bind_typevar<'db>(
/// Create a `typing.Self` type variable for a given class. /// Create a `typing.Self` type variable for a given class.
pub(crate) fn typing_self<'db>( pub(crate) fn typing_self<'db>(
db: &'db dyn Db, db: &'db dyn Db,
scope_id: ScopeId, function_scope_id: ScopeId,
typevar_binding_context: Option<Definition<'db>>, typevar_binding_context: Option<Definition<'db>>,
class: ClassLiteral<'db>, class: ClassLiteral<'db>,
) -> Option<Type<'db>> { ) -> Option<Type<'db>> {
let index = semantic_index(db, scope_id.file(db)); let index = semantic_index(db, function_scope_id.file(db));
let identity = TypeVarIdentity::new( let identity = TypeVarIdentity::new(
db, db,
@ -110,7 +110,7 @@ pub(crate) fn typing_self<'db>(
bind_typevar( bind_typevar(
db, db,
index, index,
scope_id.file_scope_id(db), function_scope_id.file_scope_id(db),
typevar_binding_context, typevar_binding_context,
typevar, typevar,
) )

View File

@ -3,7 +3,7 @@ use std::{iter, mem};
use itertools::{Either, Itertools}; use itertools::{Either, Itertools};
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity}; use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity};
use ruff_db::files::File; use ruff_db::files::File;
use ruff_db::parsed::ParsedModuleRef; use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast::visitor::{Visitor, walk_expr}; use ruff_python_ast::visitor::{Visitor, walk_expr};
use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext, PythonVersion}; use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext, PythonVersion};
use ruff_python_stdlib::builtins::version_builtin_was_added; use ruff_python_stdlib::builtins::version_builtin_was_added;
@ -81,9 +81,9 @@ use crate::types::function::{
}; };
use crate::types::generics::{ use crate::types::generics::{
GenericContext, InferableTypeVars, LegacyGenericBase, SpecializationBuilder, bind_typevar, GenericContext, InferableTypeVars, LegacyGenericBase, SpecializationBuilder, bind_typevar,
enclosing_generic_contexts, enclosing_generic_contexts, typing_self,
}; };
use crate::types::infer::nearest_enclosing_function; use crate::types::infer::{nearest_enclosing_class, nearest_enclosing_function};
use crate::types::instance::SliceLiteral; use crate::types::instance::SliceLiteral;
use crate::types::mro::MroErrorKind; use crate::types::mro::MroErrorKind;
use crate::types::signatures::Signature; use crate::types::signatures::Signature;
@ -2495,6 +2495,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} else { } else {
let ty = if let Some(default_ty) = default_ty { let ty = if let Some(default_ty) = default_ty {
UnionType::from_elements(self.db(), [Type::unknown(), default_ty]) UnionType::from_elements(self.db(), [Type::unknown(), default_ty])
} else if let Some(ty) = self.special_first_method_parameter_type(parameter) {
ty
} else { } else {
Type::unknown() Type::unknown()
}; };
@ -2535,6 +2537,53 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
} }
/// Special case for unannotated `cls` and `self` arguments to class methods and instance methods.
fn special_first_method_parameter_type(
&mut self,
parameter: &ast::Parameter,
) -> Option<Type<'db>> {
let db = self.db();
let scope_id = self.scope();
let file = scope_id.file(db);
let function_scope = scope_id.scope(db);
let method = function_scope.node().as_function()?;
let parent_scope_id = function_scope.parent()?;
let parent_scope = self.index.scope(parent_scope_id);
parent_scope.node().as_class()?;
let method_definition = self.index.expect_single_definition(method);
let DefinitionKind::Function(function_definition) = method_definition.kind(db) else {
return None;
};
let func_type = infer_definition_types(db, method_definition)
.declaration_type(method_definition)
.inner_type()
.as_function_literal()?;
let module = parsed_module(db, file).load(db);
if function_definition
.node(&module)
.parameters
.index(parameter.name())
.is_some_and(|index| index != 0)
{
return None;
}
if func_type.is_classmethod(db) {
// TODO: set the type for `cls` argument
return None;
} else if func_type.is_staticmethod(db) {
return None;
}
let class = nearest_enclosing_class(db, self.index, scope_id).unwrap();
typing_self(db, self.scope(), Some(method_definition), class)
}
/// Set initial declared/inferred types for a `*args` variadic positional parameter. /// Set initial declared/inferred types for a `*args` variadic positional parameter.
/// ///
/// The annotated type is implicitly wrapped in a string-keyed dictionary. /// The annotated type is implicitly wrapped in a string-keyed dictionary.