Skip to content

Commit 8bcfc19

Browse files
[ty] Implement typing.final for methods (#21646)
Co-authored-by: Micha Reiser <[email protected]>
1 parent c534bfa commit 8bcfc19

14 files changed

+2607
-159
lines changed

crates/ty/docs/rules.md

Lines changed: 105 additions & 72 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ty_python_semantic/resources/mdtest/final.md

Lines changed: 454 additions & 1 deletion
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
---
2+
source: crates/ty_test/src/lib.rs
3+
expression: snapshot
4+
---
5+
---
6+
mdtest name: final.md - Tests for the `@typing(_extensions).final` decorator - A possibly-undefined `@final` method is overridden
7+
mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
8+
---
9+
10+
# Python source files
11+
12+
## mdtest_snippet.py
13+
14+
```
15+
1 | from typing import final
16+
2 |
17+
3 | def coinflip() -> bool:
18+
4 | return False
19+
5 |
20+
6 | class A:
21+
7 | if coinflip():
22+
8 | @final
23+
9 | def method1(self) -> None: ...
24+
10 | else:
25+
11 | def method1(self) -> None: ...
26+
12 |
27+
13 | if coinflip():
28+
14 | def method2(self) -> None: ...
29+
15 | else:
30+
16 | @final
31+
17 | def method2(self) -> None: ...
32+
18 |
33+
19 | if coinflip():
34+
20 | @final
35+
21 | def method3(self) -> None: ...
36+
22 | else:
37+
23 | @final
38+
24 | def method3(self) -> None: ...
39+
25 |
40+
26 | if coinflip():
41+
27 | def method4(self) -> None: ...
42+
28 | elif coinflip():
43+
29 | @final
44+
30 | def method4(self) -> None: ...
45+
31 | else:
46+
32 | def method4(self) -> None: ...
47+
33 |
48+
34 | class B(A):
49+
35 | def method1(self) -> None: ... # error: [override-of-final-method]
50+
36 | def method2(self) -> None: ... # error: [override-of-final-method]
51+
37 | def method3(self) -> None: ... # error: [override-of-final-method]
52+
38 | def method4(self) -> None: ... # error: [override-of-final-method]
53+
39 |
54+
40 | # Possible overrides of possibly `@final` methods...
55+
41 | class C(A):
56+
42 | if coinflip():
57+
43 | # TODO: the autofix here introduces invalid syntax because there are now no
58+
44 | # statements inside the `if:` branch
59+
45 | # (but it might still be a useful autofix in an IDE context?)
60+
46 | def method1(self) -> None: ... # error: [override-of-final-method]
61+
47 | else:
62+
48 | pass
63+
49 |
64+
50 | if coinflip():
65+
51 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
66+
52 | else:
67+
53 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
68+
54 |
69+
55 | if coinflip():
70+
56 | def method3(self) -> None: ... # error: [override-of-final-method]
71+
57 | def method4(self) -> None: ... # error: [override-of-final-method]
72+
```
73+
74+
# Diagnostics
75+
76+
```
77+
error[override-of-final-method]: Cannot override `A.method1`
78+
--> src/mdtest_snippet.py:35:9
79+
|
80+
34 | class B(A):
81+
35 | def method1(self) -> None: ... # error: [override-of-final-method]
82+
| ^^^^^^^ Overrides a definition from superclass `A`
83+
36 | def method2(self) -> None: ... # error: [override-of-final-method]
84+
37 | def method3(self) -> None: ... # error: [override-of-final-method]
85+
|
86+
info: `A.method1` is decorated with `@final`, forbidding overrides
87+
--> src/mdtest_snippet.py:8:9
88+
|
89+
6 | class A:
90+
7 | if coinflip():
91+
8 | @final
92+
| ------
93+
9 | def method1(self) -> None: ...
94+
| ------- `A.method1` defined here
95+
10 | else:
96+
11 | def method1(self) -> None: ...
97+
|
98+
help: Remove the override of `method1`
99+
info: rule `override-of-final-method` is enabled by default
100+
32 | def method4(self) -> None: ...
101+
33 |
102+
34 | class B(A):
103+
- def method1(self) -> None: ... # error: [override-of-final-method]
104+
35 + # error: [override-of-final-method]
105+
36 | def method2(self) -> None: ... # error: [override-of-final-method]
106+
37 | def method3(self) -> None: ... # error: [override-of-final-method]
107+
38 | def method4(self) -> None: ... # error: [override-of-final-method]
108+
note: This is an unsafe fix and may change runtime behavior
109+
110+
```
111+
112+
```
113+
error[override-of-final-method]: Cannot override `A.method2`
114+
--> src/mdtest_snippet.py:36:9
115+
|
116+
34 | class B(A):
117+
35 | def method1(self) -> None: ... # error: [override-of-final-method]
118+
36 | def method2(self) -> None: ... # error: [override-of-final-method]
119+
| ^^^^^^^ Overrides a definition from superclass `A`
120+
37 | def method3(self) -> None: ... # error: [override-of-final-method]
121+
38 | def method4(self) -> None: ... # error: [override-of-final-method]
122+
|
123+
info: `A.method2` is decorated with `@final`, forbidding overrides
124+
--> src/mdtest_snippet.py:16:9
125+
|
126+
14 | def method2(self) -> None: ...
127+
15 | else:
128+
16 | @final
129+
| ------
130+
17 | def method2(self) -> None: ...
131+
| ------- `A.method2` defined here
132+
18 |
133+
19 | if coinflip():
134+
|
135+
help: Remove the override of `method2`
136+
info: rule `override-of-final-method` is enabled by default
137+
33 |
138+
34 | class B(A):
139+
35 | def method1(self) -> None: ... # error: [override-of-final-method]
140+
- def method2(self) -> None: ... # error: [override-of-final-method]
141+
36 + # error: [override-of-final-method]
142+
37 | def method3(self) -> None: ... # error: [override-of-final-method]
143+
38 | def method4(self) -> None: ... # error: [override-of-final-method]
144+
39 |
145+
note: This is an unsafe fix and may change runtime behavior
146+
147+
```
148+
149+
```
150+
error[override-of-final-method]: Cannot override `A.method3`
151+
--> src/mdtest_snippet.py:37:9
152+
|
153+
35 | def method1(self) -> None: ... # error: [override-of-final-method]
154+
36 | def method2(self) -> None: ... # error: [override-of-final-method]
155+
37 | def method3(self) -> None: ... # error: [override-of-final-method]
156+
| ^^^^^^^ Overrides a definition from superclass `A`
157+
38 | def method4(self) -> None: ... # error: [override-of-final-method]
158+
|
159+
info: `A.method3` is decorated with `@final`, forbidding overrides
160+
--> src/mdtest_snippet.py:20:9
161+
|
162+
19 | if coinflip():
163+
20 | @final
164+
| ------
165+
21 | def method3(self) -> None: ...
166+
| ------- `A.method3` defined here
167+
22 | else:
168+
23 | @final
169+
|
170+
help: Remove the override of `method3`
171+
info: rule `override-of-final-method` is enabled by default
172+
34 | class B(A):
173+
35 | def method1(self) -> None: ... # error: [override-of-final-method]
174+
36 | def method2(self) -> None: ... # error: [override-of-final-method]
175+
- def method3(self) -> None: ... # error: [override-of-final-method]
176+
37 + # error: [override-of-final-method]
177+
38 | def method4(self) -> None: ... # error: [override-of-final-method]
178+
39 |
179+
40 | # Possible overrides of possibly `@final` methods...
180+
note: This is an unsafe fix and may change runtime behavior
181+
182+
```
183+
184+
```
185+
error[override-of-final-method]: Cannot override `A.method4`
186+
--> src/mdtest_snippet.py:38:9
187+
|
188+
36 | def method2(self) -> None: ... # error: [override-of-final-method]
189+
37 | def method3(self) -> None: ... # error: [override-of-final-method]
190+
38 | def method4(self) -> None: ... # error: [override-of-final-method]
191+
| ^^^^^^^ Overrides a definition from superclass `A`
192+
39 |
193+
40 | # Possible overrides of possibly `@final` methods...
194+
|
195+
info: `A.method4` is decorated with `@final`, forbidding overrides
196+
--> src/mdtest_snippet.py:29:9
197+
|
198+
27 | def method4(self) -> None: ...
199+
28 | elif coinflip():
200+
29 | @final
201+
| ------
202+
30 | def method4(self) -> None: ...
203+
| ------- `A.method4` defined here
204+
31 | else:
205+
32 | def method4(self) -> None: ...
206+
|
207+
help: Remove the override of `method4`
208+
info: rule `override-of-final-method` is enabled by default
209+
35 | def method1(self) -> None: ... # error: [override-of-final-method]
210+
36 | def method2(self) -> None: ... # error: [override-of-final-method]
211+
37 | def method3(self) -> None: ... # error: [override-of-final-method]
212+
- def method4(self) -> None: ... # error: [override-of-final-method]
213+
38 + # error: [override-of-final-method]
214+
39 |
215+
40 | # Possible overrides of possibly `@final` methods...
216+
41 | class C(A):
217+
note: This is an unsafe fix and may change runtime behavior
218+
219+
```
220+
221+
```
222+
error[override-of-final-method]: Cannot override `A.method1`
223+
--> src/mdtest_snippet.py:46:13
224+
|
225+
44 | # statements inside the `if:` branch
226+
45 | # (but it might still be a useful autofix in an IDE context?)
227+
46 | def method1(self) -> None: ... # error: [override-of-final-method]
228+
| ^^^^^^^ Overrides a definition from superclass `A`
229+
47 | else:
230+
48 | pass
231+
|
232+
info: `A.method1` is decorated with `@final`, forbidding overrides
233+
--> src/mdtest_snippet.py:8:9
234+
|
235+
6 | class A:
236+
7 | if coinflip():
237+
8 | @final
238+
| ------
239+
9 | def method1(self) -> None: ...
240+
| ------- `A.method1` defined here
241+
10 | else:
242+
11 | def method1(self) -> None: ...
243+
|
244+
help: Remove the override of `method1`
245+
info: rule `override-of-final-method` is enabled by default
246+
43 | # TODO: the autofix here introduces invalid syntax because there are now no
247+
44 | # statements inside the `if:` branch
248+
45 | # (but it might still be a useful autofix in an IDE context?)
249+
- def method1(self) -> None: ... # error: [override-of-final-method]
250+
46 + # error: [override-of-final-method]
251+
47 | else:
252+
48 | pass
253+
49 |
254+
note: This is an unsafe fix and may change runtime behavior
255+
256+
```
257+
258+
```
259+
error[override-of-final-method]: Cannot override `A.method3`
260+
--> src/mdtest_snippet.py:56:13
261+
|
262+
55 | if coinflip():
263+
56 | def method3(self) -> None: ... # error: [override-of-final-method]
264+
| ^^^^^^^ Overrides a definition from superclass `A`
265+
57 | def method4(self) -> None: ... # error: [override-of-final-method]
266+
|
267+
info: `A.method3` is decorated with `@final`, forbidding overrides
268+
--> src/mdtest_snippet.py:20:9
269+
|
270+
19 | if coinflip():
271+
20 | @final
272+
| ------
273+
21 | def method3(self) -> None: ...
274+
| ------- `A.method3` defined here
275+
22 | else:
276+
23 | @final
277+
|
278+
help: Remove the override of `method3`
279+
info: rule `override-of-final-method` is enabled by default
280+
53 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
281+
54 |
282+
55 | if coinflip():
283+
- def method3(self) -> None: ... # error: [override-of-final-method]
284+
56 + # error: [override-of-final-method]
285+
57 | def method4(self) -> None: ... # error: [override-of-final-method]
286+
note: This is an unsafe fix and may change runtime behavior
287+
288+
```
289+
290+
```
291+
error[override-of-final-method]: Cannot override `A.method4`
292+
--> src/mdtest_snippet.py:57:13
293+
|
294+
55 | if coinflip():
295+
56 | def method3(self) -> None: ... # error: [override-of-final-method]
296+
57 | def method4(self) -> None: ... # error: [override-of-final-method]
297+
| ^^^^^^^ Overrides a definition from superclass `A`
298+
|
299+
info: `A.method4` is decorated with `@final`, forbidding overrides
300+
--> src/mdtest_snippet.py:29:9
301+
|
302+
27 | def method4(self) -> None: ...
303+
28 | elif coinflip():
304+
29 | @final
305+
| ------
306+
30 | def method4(self) -> None: ...
307+
| ------- `A.method4` defined here
308+
31 | else:
309+
32 | def method4(self) -> None: ...
310+
|
311+
help: Remove the override of `method4`
312+
info: rule `override-of-final-method` is enabled by default
313+
54 |
314+
55 | if coinflip():
315+
56 | def method3(self) -> None: ... # error: [override-of-final-method]
316+
- def method4(self) -> None: ... # error: [override-of-final-method]
317+
57 + # error: [override-of-final-method]
318+
note: This is an unsafe fix and may change runtime behavior
319+
320+
```

0 commit comments

Comments
 (0)