[ty] Extend `invalid-explicit-override` to also cover properties decorated with `@override` that do not override anything (#21756)

This commit is contained in:
Alex Waygood 2025-12-03 11:27:47 +00:00 committed by GitHub
parent 92c5f62ec0
commit 5756b3809c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 415 additions and 252 deletions

View File

@ -19,54 +19,74 @@ class A:
class Parent: class Parent:
def foo(self): ... def foo(self): ...
@property @property
def my_property1(self) -> int: ... def my_property1(self) -> int: ...
@property @property
def my_property2(self) -> int: ... def my_property2(self) -> int: ...
baz = None baz = None
@classmethod @classmethod
def class_method1(cls) -> int: ... def class_method1(cls) -> int: ...
@staticmethod @staticmethod
def static_method1() -> int: ... def static_method1() -> int: ...
@classmethod @classmethod
def class_method2(cls) -> int: ... def class_method2(cls) -> int: ...
@staticmethod @staticmethod
def static_method2() -> int: ... def static_method2() -> int: ...
@lossy_decorator @lossy_decorator
def decorated_1(self): ... def decorated_1(self): ...
@lossy_decorator @lossy_decorator
def decorated_2(self): ... def decorated_2(self): ...
@lossy_decorator @lossy_decorator
def decorated_3(self): ... def decorated_3(self): ...
class Child(Parent): class Child(Parent):
@override @override
def foo(self): ... # fine: overrides `Parent.foo` def foo(self): ... # fine: overrides `Parent.foo`
@property @property
@override @override
def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1` def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
@override @override
@property @property
def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2` def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
@override @override
def baz(self): ... # fine: overrides `Parent.baz` def baz(self): ... # fine: overrides `Parent.baz`
@classmethod @classmethod
@override @override
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
@staticmethod @staticmethod
@override @override
def static_method1() -> int: ... # fine: overrides `Parent.static_method1` def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
@override @override
@classmethod @classmethod
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
@override @override
@staticmethod @staticmethod
def static_method2() -> int: ... # fine: overrides `Parent.static_method2` def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
@override @override
def decorated_1(self): ... # fine: overrides `Parent.decorated_1` def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
@override @override
@lossy_decorator @lossy_decorator
def decorated_2(self): ... # fine: overrides `Parent.decorated_2` def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
@lossy_decorator @lossy_decorator
@override @override
def decorated_3(self): ... # fine: overrides `Parent.decorated_3` def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
@ -76,28 +96,37 @@ class OtherChild(Parent): ...
class Grandchild(OtherChild): class Grandchild(OtherChild):
@override @override
def foo(self): ... # fine: overrides `Parent.foo` def foo(self): ... # fine: overrides `Parent.foo`
@override @override
@property @property
def bar(self) -> int: ... # fine: overrides `Parent.bar` def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
@override @override
def baz(self): ... # fine: overrides `Parent.baz` def baz(self): ... # fine: overrides `Parent.baz`
@classmethod @classmethod
@override @override
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
@staticmethod @staticmethod
@override @override
def static_method1() -> int: ... # fine: overrides `Parent.static_method1` def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
@override @override
@classmethod @classmethod
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
@override @override
@staticmethod @staticmethod
def static_method2() -> int: ... # fine: overrides `Parent.static_method2` def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
@override @override
def decorated_1(self): ... # fine: overrides `Parent.decorated_1` def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
@override @override
@lossy_decorator @lossy_decorator
def decorated_2(self): ... # fine: overrides `Parent.decorated_2` def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
@lossy_decorator @lossy_decorator
@override @override
def decorated_3(self): ... # fine: overrides `Parent.decorated_3` def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
@ -105,27 +134,41 @@ class Grandchild(OtherChild):
class Invalid: class Invalid:
@override @override
def ___reprrr__(self): ... # error: [invalid-explicit-override] def ___reprrr__(self): ... # error: [invalid-explicit-override]
@override @override
@classmethod @classmethod
def foo(self): ... # error: [invalid-explicit-override] def foo(self): ... # error: [invalid-explicit-override]
@classmethod @classmethod
@override @override
def bar(self): ... # error: [invalid-explicit-override] def bar(self): ... # error: [invalid-explicit-override]
@staticmethod @staticmethod
@override @override
def baz(): ... # error: [invalid-explicit-override] def baz(): ... # error: [invalid-explicit-override]
@override @override
@staticmethod @staticmethod
def eggs(): ... # error: [invalid-explicit-override] def eggs(): ... # error: [invalid-explicit-override]
@property @property
@override @override
def bad_property1(self) -> int: ... # TODO: should emit `invalid-explicit-override` here def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
@override @override
@property @property
def bad_property2(self) -> int: ... # TODO: should emit `invalid-explicit-override` here def bad_property2(self) -> int: ... # error: [invalid-explicit-override]
@property
@override
def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override]
@bad_settable_property.setter
def bad_settable_property(self, x: int) -> None: ...
@lossy_decorator @lossy_decorator
@override @override
def lossy(self): ... # TODO: should emit `invalid-explicit-override` here def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
@override @override
@lossy_decorator @lossy_decorator
def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
@ -136,11 +179,14 @@ class LiskovViolatingButNotOverrideViolating(Parent):
@override @override
@property @property
def foo(self) -> int: ... def foo(self) -> int: ...
@override @override
def my_property1(self) -> int: ... def my_property1(self) -> int: ...
@staticmethod @staticmethod
@override @override
def class_method1() -> int: ... def class_method1() -> int: ...
@classmethod @classmethod
@override @override
def static_method1(cls) -> int: ... def static_method1(cls) -> int: ...

View File

@ -22,175 +22,221 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/override.md
8 | 8 |
9 | class Parent: 9 | class Parent:
10 | def foo(self): ... 10 | def foo(self): ...
11 | @property 11 |
12 | def my_property1(self) -> int: ... 12 | @property
13 | @property 13 | def my_property1(self) -> int: ...
14 | def my_property2(self) -> int: ... 14 |
15 | baz = None 15 | @property
16 | @classmethod 16 | def my_property2(self) -> int: ...
17 | def class_method1(cls) -> int: ... 17 |
18 | @staticmethod 18 | baz = None
19 | def static_method1() -> int: ... 19 |
20 | @classmethod 20 | @classmethod
21 | def class_method2(cls) -> int: ... 21 | def class_method1(cls) -> int: ...
22 | @staticmethod 22 |
23 | def static_method2() -> int: ... 23 | @staticmethod
24 | @lossy_decorator 24 | def static_method1() -> int: ...
25 | def decorated_1(self): ... 25 |
26 | @lossy_decorator 26 | @classmethod
27 | def decorated_2(self): ... 27 | def class_method2(cls) -> int: ...
28 | @lossy_decorator 28 |
29 | def decorated_3(self): ... 29 | @staticmethod
30 | 30 | def static_method2() -> int: ...
31 | class Child(Parent): 31 |
32 | @override 32 | @lossy_decorator
33 | def foo(self): ... # fine: overrides `Parent.foo` 33 | def decorated_1(self): ...
34 | @property 34 |
35 | @override 35 | @lossy_decorator
36 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1` 36 | def decorated_2(self): ...
37 | @override 37 |
38 | @property 38 | @lossy_decorator
39 | def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2` 39 | def decorated_3(self): ...
40 | @override 40 |
41 | def baz(self): ... # fine: overrides `Parent.baz` 41 | class Child(Parent):
42 | @classmethod 42 | @override
43 | @override 43 | def foo(self): ... # fine: overrides `Parent.foo`
44 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` 44 |
45 | @staticmethod 45 | @property
46 | @override 46 | @override
47 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1` 47 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
48 | @override 48 |
49 | @classmethod 49 | @override
50 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` 50 | @property
51 | @override 51 | def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
52 | @staticmethod 52 |
53 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2` 53 | @override
54 | @override 54 | def baz(self): ... # fine: overrides `Parent.baz`
55 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1` 55 |
56 | @override 56 | @classmethod
57 | @lossy_decorator 57 | @override
58 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2` 58 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
59 | @lossy_decorator 59 |
60 | @override 60 | @staticmethod
61 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3` 61 | @override
62 | 62 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
63 | class OtherChild(Parent): ... 63 |
64 | 64 | @override
65 | class Grandchild(OtherChild): 65 | @classmethod
66 | @override 66 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
67 | def foo(self): ... # fine: overrides `Parent.foo` 67 |
68 | @override 68 | @override
69 | @property 69 | @staticmethod
70 | def bar(self) -> int: ... # fine: overrides `Parent.bar` 70 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
71 | @override 71 |
72 | def baz(self): ... # fine: overrides `Parent.baz` 72 | @override
73 | @classmethod 73 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
74 | @override 74 |
75 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` 75 | @override
76 | @staticmethod 76 | @lossy_decorator
77 | @override 77 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
78 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1` 78 |
79 | @override 79 | @lossy_decorator
80 | @classmethod 80 | @override
81 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` 81 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
82 | @override 82 |
83 | @staticmethod 83 | class OtherChild(Parent): ...
84 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2` 84 |
85 | @override 85 | class Grandchild(OtherChild):
86 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1` 86 | @override
87 | @override 87 | def foo(self): ... # fine: overrides `Parent.foo`
88 | @lossy_decorator 88 |
89 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2` 89 | @override
90 | @lossy_decorator 90 | @property
91 | @override 91 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
92 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3` 92 |
93 | 93 | @override
94 | class Invalid: 94 | def baz(self): ... # fine: overrides `Parent.baz`
95 | @override 95 |
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override] 96 | @classmethod
97 | @override 97 | @override
98 | @classmethod 98 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
99 | def foo(self): ... # error: [invalid-explicit-override] 99 |
100 | @classmethod 100 | @staticmethod
101 | @override 101 | @override
102 | def bar(self): ... # error: [invalid-explicit-override] 102 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
103 | @staticmethod 103 |
104 | @override 104 | @override
105 | def baz(): ... # error: [invalid-explicit-override] 105 | @classmethod
106 | @override 106 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
107 | @staticmethod 107 |
108 | def eggs(): ... # error: [invalid-explicit-override] 108 | @override
109 | @property 109 | @staticmethod
110 | @override 110 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
111 | def bad_property1(self) -> int: ... # TODO: should emit `invalid-explicit-override` here 111 |
112 | @override 112 | @override
113 | @property 113 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
114 | def bad_property2(self) -> int: ... # TODO: should emit `invalid-explicit-override` here 114 |
115 | @lossy_decorator 115 | @override
116 | @override 116 | @lossy_decorator
117 | def lossy(self): ... # TODO: should emit `invalid-explicit-override` here 117 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
118 | @override 118 |
119 | @lossy_decorator 119 | @lossy_decorator
120 | def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here 120 | @override
121 | 121 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
122 | # TODO: all overrides in this class should cause us to emit *Liskov* violations, 122 |
123 | # but not `@override` violations 123 | class Invalid:
124 | class LiskovViolatingButNotOverrideViolating(Parent): 124 | @override
125 | @override 125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
126 | @property 126 |
127 | def foo(self) -> int: ... 127 | @override
128 | @override 128 | @classmethod
129 | def my_property1(self) -> int: ... 129 | def foo(self): ... # error: [invalid-explicit-override]
130 | @staticmethod 130 |
131 | @override 131 | @classmethod
132 | def class_method1() -> int: ... 132 | @override
133 | @classmethod 133 | def bar(self): ... # error: [invalid-explicit-override]
134 | @override 134 |
135 | def static_method1(cls) -> int: ... 135 | @staticmethod
136 | 136 | @override
137 | # Diagnostic edge case: `override` is very far away from the method definition in the source code: 137 | def baz(): ... # error: [invalid-explicit-override]
138 | 138 |
139 | T = TypeVar("T") 139 | @override
140 | 140 | @staticmethod
141 | def identity(x: T) -> T: ... 141 | def eggs(): ... # error: [invalid-explicit-override]
142 | 142 |
143 | class Foo: 143 | @property
144 | @override 144 | @override
145 | @identity 145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
146 | @identity 146 |
147 | @identity 147 | @override
148 | @identity 148 | @property
149 | @identity 149 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override]
150 | @identity 150 |
151 | @identity 151 | @property
152 | @identity 152 | @override
153 | @identity 153 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override]
154 | @identity 154 | @bad_settable_property.setter
155 | @identity 155 | def bad_settable_property(self, x: int) -> None: ...
156 | @identity 156 |
157 | @identity 157 | @lossy_decorator
158 | @identity 158 | @override
159 | @identity 159 | def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
160 | @identity 160 |
161 | @identity 161 | @override
162 | @identity 162 | @lossy_decorator
163 | def bar(self): ... # error: [invalid-explicit-override] 163 | def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
164 |
165 | # TODO: all overrides in this class should cause us to emit *Liskov* violations,
166 | # but not `@override` violations
167 | class LiskovViolatingButNotOverrideViolating(Parent):
168 | @override
169 | @property
170 | def foo(self) -> int: ...
171 |
172 | @override
173 | def my_property1(self) -> int: ...
174 |
175 | @staticmethod
176 | @override
177 | def class_method1() -> int: ...
178 |
179 | @classmethod
180 | @override
181 | def static_method1(cls) -> int: ...
182 |
183 | # Diagnostic edge case: `override` is very far away from the method definition in the source code:
184 |
185 | T = TypeVar("T")
186 |
187 | def identity(x: T) -> T: ...
188 |
189 | class Foo:
190 | @override
191 | @identity
192 | @identity
193 | @identity
194 | @identity
195 | @identity
196 | @identity
197 | @identity
198 | @identity
199 | @identity
200 | @identity
201 | @identity
202 | @identity
203 | @identity
204 | @identity
205 | @identity
206 | @identity
207 | @identity
208 | @identity
209 | def bar(self): ... # error: [invalid-explicit-override]
``` ```
# Diagnostics # Diagnostics
``` ```
error[invalid-explicit-override]: Method `___reprrr__` is decorated with `@override` but does not override anything error[invalid-explicit-override]: Method `___reprrr__` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:95:5 --> src/mdtest_snippet.pyi:124:5
| |
94 | class Invalid: 123 | class Invalid:
95 | @override 124 | @override
| --------- | ---------
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override] 125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
| ^^^^^^^^^^^ | ^^^^^^^^^^^
97 | @override 126 |
98 | @classmethod 127 | @override
| |
info: No `___reprrr__` definitions were found on any superclasses of `Invalid` info: No `___reprrr__` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default info: rule `invalid-explicit-override` is enabled by default
@ -198,17 +244,17 @@ info: rule `invalid-explicit-override` is enabled by default
``` ```
error[invalid-explicit-override]: Method `foo` is decorated with `@override` but does not override anything error[invalid-explicit-override]: Method `foo` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:97:5 --> src/mdtest_snippet.pyi:127:5
| |
95 | @override 125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override] 126 |
97 | @override 127 | @override
| --------- | ---------
98 | @classmethod 128 | @classmethod
99 | def foo(self): ... # error: [invalid-explicit-override] 129 | def foo(self): ... # error: [invalid-explicit-override]
| ^^^ | ^^^
100 | @classmethod 130 |
101 | @override 131 | @classmethod
| |
info: No `foo` definitions were found on any superclasses of `Invalid` info: No `foo` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default info: rule `invalid-explicit-override` is enabled by default
@ -217,16 +263,15 @@ info: rule `invalid-explicit-override` is enabled by default
``` ```
error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:101:5 --> src/mdtest_snippet.pyi:132:5
| |
99 | def foo(self): ... # error: [invalid-explicit-override] 131 | @classmethod
100 | @classmethod 132 | @override
101 | @override
| --------- | ---------
102 | def bar(self): ... # error: [invalid-explicit-override] 133 | def bar(self): ... # error: [invalid-explicit-override]
| ^^^ | ^^^
103 | @staticmethod 134 |
104 | @override 135 | @staticmethod
| |
info: No `bar` definitions were found on any superclasses of `Invalid` info: No `bar` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default info: rule `invalid-explicit-override` is enabled by default
@ -235,16 +280,15 @@ info: rule `invalid-explicit-override` is enabled by default
``` ```
error[invalid-explicit-override]: Method `baz` is decorated with `@override` but does not override anything error[invalid-explicit-override]: Method `baz` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:104:5 --> src/mdtest_snippet.pyi:136:5
| |
102 | def bar(self): ... # error: [invalid-explicit-override] 135 | @staticmethod
103 | @staticmethod 136 | @override
104 | @override
| --------- | ---------
105 | def baz(): ... # error: [invalid-explicit-override] 137 | def baz(): ... # error: [invalid-explicit-override]
| ^^^ | ^^^
106 | @override 138 |
107 | @staticmethod 139 | @override
| |
info: No `baz` definitions were found on any superclasses of `Invalid` info: No `baz` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default info: rule `invalid-explicit-override` is enabled by default
@ -253,17 +297,17 @@ info: rule `invalid-explicit-override` is enabled by default
``` ```
error[invalid-explicit-override]: Method `eggs` is decorated with `@override` but does not override anything error[invalid-explicit-override]: Method `eggs` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:106:5 --> src/mdtest_snippet.pyi:139:5
| |
104 | @override 137 | def baz(): ... # error: [invalid-explicit-override]
105 | def baz(): ... # error: [invalid-explicit-override] 138 |
106 | @override 139 | @override
| --------- | ---------
107 | @staticmethod 140 | @staticmethod
108 | def eggs(): ... # error: [invalid-explicit-override] 141 | def eggs(): ... # error: [invalid-explicit-override]
| ^^^^ | ^^^^
109 | @property 142 |
110 | @override 143 | @property
| |
info: No `eggs` definitions were found on any superclasses of `Invalid` info: No `eggs` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default info: rule `invalid-explicit-override` is enabled by default
@ -271,21 +315,74 @@ info: rule `invalid-explicit-override` is enabled by default
``` ```
``` ```
error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything error[invalid-explicit-override]: Method `bad_property1` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:163:9 --> src/mdtest_snippet.pyi:144:5
| |
161 | @identity 143 | @property
162 | @identity
163 | def bar(self): ... # error: [invalid-explicit-override]
| ^^^
|
::: src/mdtest_snippet.pyi:144:5
|
143 | class Foo:
144 | @override 144 | @override
| --------- | ---------
145 | @identity 145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
146 | @identity | ^^^^^^^^^^^^^
146 |
147 | @override
|
info: No `bad_property1` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
```
```
error[invalid-explicit-override]: Method `bad_property2` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:147:5
|
145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
146 |
147 | @override
| ---------
148 | @property
149 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override]
| ^^^^^^^^^^^^^
150 |
151 | @property
|
info: No `bad_property2` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
```
```
error[invalid-explicit-override]: Method `bad_settable_property` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:152:5
|
151 | @property
152 | @override
| ---------
153 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override]
| ^^^^^^^^^^^^^^^^^^^^^
154 | @bad_settable_property.setter
155 | def bad_settable_property(self, x: int) -> None: ...
|
info: No `bad_settable_property` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
```
```
error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:209:9
|
207 | @identity
208 | @identity
209 | def bar(self): ... # error: [invalid-explicit-override]
| ^^^
|
::: src/mdtest_snippet.pyi:190:5
|
189 | class Foo:
190 | @override
| ---------
191 | @identity
192 | @identity
| |
info: No `bar` definitions were found on any superclasses of `Foo` info: No `bar` definitions were found on any superclasses of `Foo`
info: rule `invalid-explicit-override` is enabled by default info: rule `invalid-explicit-override` is enabled by default

View File

@ -25,7 +25,7 @@ use crate::{
report_overridden_final_method, report_overridden_final_method,
}, },
function::{FunctionDecorators, FunctionType, KnownFunction}, function::{FunctionDecorators, FunctionType, KnownFunction},
list_members::{MemberWithDefinition, all_members_of_scope}, list_members::{Member, MemberWithDefinition, all_members_of_scope},
}, },
}; };
@ -101,33 +101,6 @@ fn check_class_declaration<'db>(
.any(|definition| definition.kind(db).is_function_def()) .any(|definition| definition.kind(db).is_function_def())
} }
fn extract_underlying_functions<'db>(
db: &'db dyn Db,
ty: Type<'db>,
) -> Option<smallvec::SmallVec<[FunctionType<'db>; 1]>> {
match ty {
Type::FunctionLiteral(function) => Some(smallvec::smallvec_inline![function]),
Type::BoundMethod(method) => Some(smallvec::smallvec_inline![method.function(db)]),
Type::PropertyInstance(property) => {
extract_underlying_functions(db, property.getter(db)?)
}
Type::Union(union) => {
let mut functions = smallvec::smallvec![];
for member in union.elements(db) {
if let Some(mut member_functions) = extract_underlying_functions(db, *member) {
functions.append(&mut member_functions);
}
}
if functions.is_empty() {
None
} else {
Some(functions)
}
}
_ => None,
}
}
let db = context.db(); let db = context.db();
let MemberWithDefinition { member, definition } = member; let MemberWithDefinition { member, definition } = member;
@ -153,6 +126,8 @@ fn check_class_declaration<'db>(
if class_kind == Some(CodeGeneratorKind::NamedTuple) if class_kind == Some(CodeGeneratorKind::NamedTuple)
&& configuration.check_prohibited_named_tuple_attrs() && configuration.check_prohibited_named_tuple_attrs()
&& PROHIBITED_NAMEDTUPLE_ATTRS.contains(&member.name.as_str()) && PROHIBITED_NAMEDTUPLE_ATTRS.contains(&member.name.as_str())
// accessing `.kind()` here is fine as `definition`
// will always be a definition in the file currently being checked
&& !matches!(definition.kind(db), DefinitionKind::AnnotatedAssignment(_)) && !matches!(definition.kind(db), DefinitionKind::AnnotatedAssignment(_))
&& let Some(builder) = context.report_lint( && let Some(builder) = context.report_lint(
&INVALID_NAMED_TUPLE, &INVALID_NAMED_TUPLE,
@ -331,35 +306,11 @@ fn check_class_declaration<'db>(
if !subclass_overrides_superclass_declaration if !subclass_overrides_superclass_declaration
&& !has_dynamic_superclass && !has_dynamic_superclass
// accessing `.kind()` here is fine as `definition`
// will always be a definition in the file currently being checked
&& definition.kind(db).is_function_def() && definition.kind(db).is_function_def()
&& let Type::FunctionLiteral(function) = member.ty
&& function.has_known_decorator(db, FunctionDecorators::OVERRIDE)
{ {
let function_literal = if context.in_stub() { check_explicit_overrides(context, member, class);
function.first_overload_or_implementation(db)
} else {
function.literal(db).last_definition(db)
};
if let Some(builder) = context.report_lint(
&INVALID_EXPLICIT_OVERRIDE,
function_literal.focus_range(db, context.module()),
) {
let mut diagnostic = builder.into_diagnostic(format_args!(
"Method `{}` is decorated with `@override` but does not override anything",
member.name
));
if let Some(decorator_span) =
function_literal.find_known_decorator_span(db, KnownFunction::Override)
{
diagnostic.annotate(Annotation::secondary(decorator_span));
}
diagnostic.info(format_args!(
"No `{member}` definitions were found on any superclasses of `{class}`",
member = &member.name,
class = class.name(db)
));
}
} }
if let Some((superclass, superclass_method)) = overridden_final_method { if let Some((superclass, superclass_method)) = overridden_final_method {
@ -434,3 +385,72 @@ impl OverrideRulesConfig {
self.contains(OverrideRulesConfig::PROHIBITED_NAMED_TUPLE_ATTR) self.contains(OverrideRulesConfig::PROHIBITED_NAMED_TUPLE_ATTR)
} }
} }
fn check_explicit_overrides<'db>(
context: &InferContext<'db, '_>,
member: &Member<'db>,
class: ClassType<'db>,
) {
let db = context.db();
let underlying_functions = extract_underlying_functions(db, member.ty);
let Some(functions) = underlying_functions else {
return;
};
if !functions
.iter()
.any(|function| function.has_known_decorator(db, FunctionDecorators::OVERRIDE))
{
return;
}
let function_literal = if context.in_stub() {
functions[0].first_overload_or_implementation(db)
} else {
functions[0].literal(db).last_definition(db)
};
let Some(builder) = context.report_lint(
&INVALID_EXPLICIT_OVERRIDE,
function_literal.focus_range(db, context.module()),
) else {
return;
};
let mut diagnostic = builder.into_diagnostic(format_args!(
"Method `{}` is decorated with `@override` but does not override anything",
member.name
));
if let Some(decorator_span) =
function_literal.find_known_decorator_span(db, KnownFunction::Override)
{
diagnostic.annotate(Annotation::secondary(decorator_span));
}
diagnostic.info(format_args!(
"No `{member}` definitions were found on any superclasses of `{class}`",
member = &member.name,
class = class.name(db)
));
}
fn extract_underlying_functions<'db>(
db: &'db dyn Db,
ty: Type<'db>,
) -> Option<smallvec::SmallVec<[FunctionType<'db>; 1]>> {
match ty {
Type::FunctionLiteral(function) => Some(smallvec::smallvec_inline![function]),
Type::BoundMethod(method) => Some(smallvec::smallvec_inline![method.function(db)]),
Type::PropertyInstance(property) => extract_underlying_functions(db, property.getter(db)?),
Type::Union(union) => {
let mut functions = smallvec::smallvec![];
for member in union.elements(db) {
if let Some(mut member_functions) = extract_underlying_functions(db, *member) {
functions.append(&mut member_functions);
}
}
if functions.is_empty() {
None
} else {
Some(functions)
}
}
_ => None,
}
}