mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty] Extend Final
test suite (#19476)
## Summary Restructures and cleans up the `typing.Final` test suite. Also adds a few more tests with TODOs based on the [typing spec for `typing.Final`](https://typing.python.org/en/latest/spec/qualifiers.html#uppercase-final).
This commit is contained in:
parent
cb60ecef6b
commit
9d98a66f65
1 changed files with 116 additions and 12 deletions
|
@ -19,10 +19,6 @@ FINAL_A: Final[int] = 1
|
||||||
FINAL_B: Annotated[Final[int], "the annotation for FINAL_B"] = 1
|
FINAL_B: Annotated[Final[int], "the annotation for FINAL_B"] = 1
|
||||||
FINAL_C: Final[Annotated[int, "the annotation for FINAL_C"]] = 1
|
FINAL_C: Final[Annotated[int, "the annotation for FINAL_C"]] = 1
|
||||||
FINAL_D: "Final[int]" = 1
|
FINAL_D: "Final[int]" = 1
|
||||||
# Note: Some type checkers do not support a separate declaration and
|
|
||||||
# assignment for `Final` symbols, but it's possible to support this in
|
|
||||||
# ty, and is useful for code that declares symbols `Final` inside
|
|
||||||
# `if TYPE_CHECKING` blocks.
|
|
||||||
FINAL_F: Final[int]
|
FINAL_F: Final[int]
|
||||||
FINAL_F = 1
|
FINAL_F = 1
|
||||||
|
|
||||||
|
@ -52,7 +48,7 @@ reveal_type(FINAL_D) # revealed: int
|
||||||
reveal_type(FINAL_F) # revealed: int
|
reveal_type(FINAL_F) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
### `Final` without a type
|
### Bare `Final` without a type
|
||||||
|
|
||||||
When a symbol is qualified with `Final` but no type is specified, the type is inferred from the
|
When a symbol is qualified with `Final` but no type is specified, the type is inferred from the
|
||||||
right-hand side of the assignment. We do not union the inferred type with `Unknown`, because the
|
right-hand side of the assignment. We do not union the inferred type with `Unknown`, because the
|
||||||
|
@ -231,7 +227,96 @@ FINAL_LIST: Final[list[int]] = [1, 2, 3]
|
||||||
FINAL_LIST[0] = 4
|
FINAL_LIST[0] = 4
|
||||||
```
|
```
|
||||||
|
|
||||||
## Too many arguments
|
## Overriding in subclasses
|
||||||
|
|
||||||
|
When a symbol is qualified with `Final` in a class, it cannot be overridden in subclasses.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
class Base:
|
||||||
|
FINAL_A: Final[int] = 1
|
||||||
|
FINAL_B: Final[int] = 1
|
||||||
|
FINAL_C: Final = 1
|
||||||
|
|
||||||
|
class Derived(Base):
|
||||||
|
# TODO: This should be an error
|
||||||
|
FINAL_A = 2
|
||||||
|
# TODO: This should be an error
|
||||||
|
FINAL_B: Final[int] = 2
|
||||||
|
# TODO: This should be an error
|
||||||
|
FINAL_C = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Syntax and usage
|
||||||
|
|
||||||
|
### Legal syntactical positions
|
||||||
|
|
||||||
|
Final may only be used in assignments or variable annotations. Using it in any other position is an
|
||||||
|
error.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Final, ClassVar, Annotated
|
||||||
|
|
||||||
|
LEGAL_A: Final[int] = 1
|
||||||
|
LEGAL_B: Final = 1
|
||||||
|
LEGAL_C: Final[int]
|
||||||
|
LEGAL_C = 1
|
||||||
|
LEGAL_D: Final
|
||||||
|
LEGAL_D = 1
|
||||||
|
|
||||||
|
class C:
|
||||||
|
LEGAL_E: ClassVar[Final[int]] = 1
|
||||||
|
LEGAL_F: Final[ClassVar[int]] = 1
|
||||||
|
LEGAL_G: Annotated[Final[ClassVar[int]], "metadata"] = 1
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.LEGAL_H: Final[int] = 1
|
||||||
|
self.LEGAL_I: Final[int]
|
||||||
|
self.LEGAL_I = 1
|
||||||
|
|
||||||
|
# TODO: This should be an error
|
||||||
|
def f(ILLEGAL: Final[int]) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# TODO: This should be an error
|
||||||
|
def f() -> Final[None]: ...
|
||||||
|
|
||||||
|
# TODO: This should be an error
|
||||||
|
class Foo(Final[tuple[int]]): ...
|
||||||
|
|
||||||
|
# TODO: Show `Unknown` instead of `@Todo` type in the MRO; or ignore `Final` and show the MRO as if `Final` was not there
|
||||||
|
# revealed: tuple[<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>]
|
||||||
|
reveal_type(Foo.__mro__)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Attribute assignment outside `__init__`
|
||||||
|
|
||||||
|
Qualifying an instance attribute with `Final` outside of `__init__` is not allowed. The instance
|
||||||
|
attribute must be assigned only once, when the instance is created.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
class C:
|
||||||
|
def some_method(self):
|
||||||
|
# TODO: This should be an error
|
||||||
|
self.x: Final[int] = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Final` in loops
|
||||||
|
|
||||||
|
Using `Final` in a loop is not allowed.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
for _ in range(10):
|
||||||
|
# TODO: This should be an error
|
||||||
|
i: Final[int] = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Too many arguments
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
@ -241,21 +326,30 @@ class C:
|
||||||
x: Final[int, str] = 1
|
x: Final[int, str] = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Illegal `Final` in type expression
|
### Illegal `Final` in type expression
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
class C:
|
|
||||||
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
|
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
|
||||||
|
x: list[Final[int]] = [] # Error!
|
||||||
|
|
||||||
|
class C:
|
||||||
|
# error: [invalid-type-form]
|
||||||
x: Final | int
|
x: Final | int
|
||||||
|
|
||||||
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
|
# error: [invalid-type-form]
|
||||||
y: int | Final[str]
|
y: int | Final[str]
|
||||||
```
|
```
|
||||||
|
|
||||||
## No assignment
|
## No assignment
|
||||||
|
|
||||||
|
Some type checkers do not support a separate declaration and assignment for `Final` symbols, but
|
||||||
|
it's possible to support this in ty, and is useful for code that declares symbols `Final` inside
|
||||||
|
`if TYPE_CHECKING` blocks.
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
|
@ -263,17 +357,27 @@ DECLARED_THEN_BOUND: Final[int]
|
||||||
DECLARED_THEN_BOUND = 1
|
DECLARED_THEN_BOUND = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
## No assignment for bare `Final`
|
### No assignment
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
# TODO: This should be an error
|
# TODO: This should be an error
|
||||||
NO_RHS: Final
|
NO_ASSIGNMENT_A: Final
|
||||||
|
# TODO: This should be an error
|
||||||
|
NO_ASSIGNMENT_B: Final[int]
|
||||||
|
|
||||||
class C:
|
class C:
|
||||||
# TODO: This should be an error
|
# TODO: This should be an error
|
||||||
NO_RHS: Final
|
NO_ASSIGNMENT_A: Final
|
||||||
|
# TODO: This should be an error
|
||||||
|
NO_ASSIGNMENT_B: Final[int]
|
||||||
|
|
||||||
|
# This is okay. `DEFINED_IN_INIT` is defined in `__init__`.
|
||||||
|
DEFINED_IN_INIT: Final[int]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.DEFINED_IN_INIT = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Full diagnostics
|
## Full diagnostics
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue