Skip to content

Commit 38d8846

Browse files
committed
[ty] Extend Final test suite
1 parent 897889d commit 38d8846

File tree

1 file changed

+106
-12
lines changed
  • crates/ty_python_semantic/resources/mdtest/type_qualifiers

1 file changed

+106
-12
lines changed

crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ FINAL_A: Final[int] = 1
1919
FINAL_B: Annotated[Final[int], "the annotation for FINAL_B"] = 1
2020
FINAL_C: Final[Annotated[int, "the annotation for FINAL_C"]] = 1
2121
FINAL_D: "Final[int]" = 1
22-
# Note: Some type checkers do not support a separate declaration and
23-
# assignment for `Final` symbols, but it's possible to support this in
24-
# ty, and is useful for code that declares symbols `Final` inside
25-
# `if TYPE_CHECKING` blocks.
2622
FINAL_F: Final[int]
2723
FINAL_F = 1
2824

@@ -52,7 +48,7 @@ reveal_type(FINAL_D) # revealed: int
5248
reveal_type(FINAL_F) # revealed: int
5349
```
5450

55-
### `Final` without a type
51+
### Bare `Final` without a type
5652

5753
When a symbol is qualified with `Final` but no type is specified, the type is inferred from the
5854
right-hand side of the assignment. We do not union the inferred type with `Unknown`, because the
@@ -231,7 +227,86 @@ FINAL_LIST: Final[list[int]] = [1, 2, 3]
231227
FINAL_LIST[0] = 4
232228
```
233229

234-
## Too many arguments
230+
## Overriding in subclasses
231+
232+
When a symbol is qualified with `Final` in a class, it cannot be overridden in subclasses.
233+
234+
```py
235+
from typing import Final
236+
237+
class Base:
238+
FINAL_A: Final[int] = 1
239+
FINAL_B: Final[int] = 1
240+
FINAL_C: Final = 1
241+
242+
class Derived(Base):
243+
# TODO: This should be an error
244+
FINAL_A = 2
245+
# TODO: This should be an error
246+
FINAL_B: Final[int] = 2
247+
# TODO: This should be an error
248+
FINAL_C = 2
249+
```
250+
251+
## Syntax and usage
252+
253+
### Legal syntactical positions
254+
255+
Final may only be used in assignments or variable annotations. Using it in any other position is an
256+
error.
257+
258+
```py
259+
from typing import Final, ClassVar, Annotated
260+
261+
LEGAL_A: Final[int] = 1
262+
LEGAL_B: Final = 1
263+
LEGAL_C: Final[int]
264+
LEGAL_C = 1
265+
LEGAL_D: Final
266+
LEGAL_D = 1
267+
268+
class C:
269+
LEGAL_E: ClassVar[Final[int]] = 1
270+
LEGAL_F: Final[ClassVar[int]] = 1
271+
LEGAL_G: Annotated[Final[ClassVar[int]], "metadata"] = 1
272+
273+
def __init__(self):
274+
self.LEGAL_H: Final[int] = 1
275+
self.LEGAL_I: Final[int]
276+
self.LEGAL_I = 1
277+
278+
# TODO: This should be an error
279+
def f(ILLEGAL: Final[int]) -> None:
280+
pass
281+
```
282+
283+
### Attribute assignment outside `__init__`
284+
285+
Qualifying an instance attribute with `Final` outside of `__init__` is not allowed. The instance
286+
attribute must be assigned only once, when the instance is created.
287+
288+
```py
289+
from typing import Final
290+
291+
class C:
292+
def some_method(self):
293+
# TODO: This should be an error
294+
self.x: Final[int] = 1
295+
```
296+
297+
### `Final` in loops
298+
299+
Using `Final` in a loop is not allowed.
300+
301+
```py
302+
from typing import Final
303+
304+
for i in range(10):
305+
# TODO: This should be an error
306+
i: Final[int] = 1
307+
```
308+
309+
### Too many arguments
235310

236311
```py
237312
from typing import Final
@@ -241,39 +316,58 @@ class C:
241316
x: Final[int, str] = 1
242317
```
243318

244-
## Illegal `Final` in type expression
319+
### Illegal `Final` in type expression
245320

246321
```py
247322
from typing import Final
248323

324+
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
325+
x: list[Final[int]] = [] # Error!
326+
249327
class C:
250-
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
328+
# error: [invalid-type-form]
251329
x: Final | int
252330

253-
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
331+
# error: [invalid-type-form]
254332
y: int | Final[str]
255333
```
256334

257335
## No assignment
258336

337+
Some type checkers do not support a separate declaration and assignment for `Final` symbols, but
338+
it's possible to support this in ty, and is useful for code that declares symbols `Final` inside
339+
`if TYPE_CHECKING` blocks.
340+
341+
### Basic
342+
259343
```py
260344
from typing import Final
261345

262346
DECLARED_THEN_BOUND: Final[int]
263347
DECLARED_THEN_BOUND = 1
264348
```
265349

266-
## No assignment for bare `Final`
350+
### No assignment
267351

268352
```py
269353
from typing import Final
270354

271355
# TODO: This should be an error
272-
NO_RHS: Final
356+
NO_ASSIGNMENT_A: Final
357+
# TODO: This should be an error
358+
NO_ASSIGNMENT_B: Final[int]
273359

274360
class C:
275361
# TODO: This should be an error
276-
NO_RHS: Final
362+
NO_ASSIGNMENT_A: Final
363+
# TODO: This should be an error
364+
NO_ASSIGNMENT_B: Final[int]
365+
366+
# This is okay. `DEFINED_IN_INIT` is defined in `__init__`.
367+
DEFINED_IN_INIT: Final[int]
368+
369+
def __init__(self):
370+
self.DEFINED_IN_INIT = 1
277371
```
278372

279373
## Full diagnostics

0 commit comments

Comments
 (0)