mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-02 04:48:07 +00:00
[ty] Enforce typing.Final (#19178)
## Summary Emit a diagnostic when a `Final`-qualified symbol is modified. This first iteration only works for name targets. Tests with TODO comments were added for attribute assignments as well. related ticket: https://github.com/astral-sh/ty/issues/158 ## Ecosystem impact Correctly identified [modification of a `Final` symbol](7b4164a5f2/sphinx/__init__.py (L44)) (behind a `# type: ignore`): ```diff - warning[unused-ignore-comment] sphinx/__init__.py:44:56: Unused blanket `type: ignore` directive ``` And the same [here](5471a37e82/src/trio/_core/_run.py (L128)): ```diff - warning[unused-ignore-comment] src/trio/_core/_run.py:128:45: Unused blanket `type: ignore` directive ``` ## Test Plan New Markdown tests
This commit is contained in:
parent
6a42d28867
commit
149350bf39
6 changed files with 252 additions and 40 deletions
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: final.md - `typing.Final` - Full diagnostics
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import Final
|
||||
2 |
|
||||
3 | MY_CONSTANT: Final[int] = 1
|
||||
4 |
|
||||
5 | # more code
|
||||
6 |
|
||||
7 | MY_CONSTANT = 2 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Reassignment of `Final` symbol `MY_CONSTANT` is not allowed
|
||||
--> src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import Final
|
||||
2 |
|
||||
3 | MY_CONSTANT: Final[int] = 1
|
||||
| ----------- Original definition
|
||||
4 |
|
||||
5 | # more code
|
||||
6 |
|
||||
7 | MY_CONSTANT = 2 # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^ Reassignment of `Final` symbol
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -100,9 +100,13 @@ reveal_type(C().FINAL_D) # revealed: Unknown
|
|||
|
||||
## Not modifiable
|
||||
|
||||
### Names
|
||||
|
||||
Symbols qualified with `Final` cannot be reassigned, and attempting to do so will result in an
|
||||
error:
|
||||
|
||||
`mod.py`:
|
||||
|
||||
```py
|
||||
from typing import Final, Annotated
|
||||
|
||||
|
|
@ -114,13 +118,97 @@ FINAL_E: Final[int]
|
|||
FINAL_E = 1
|
||||
FINAL_F: Final = 1
|
||||
|
||||
# TODO: all of these should be errors
|
||||
FINAL_A = 2
|
||||
FINAL_B = 2
|
||||
FINAL_C = 2
|
||||
FINAL_D = 2
|
||||
FINAL_E = 2
|
||||
FINAL_F = 2
|
||||
FINAL_A = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_A` is not allowed"
|
||||
FINAL_B = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_B` is not allowed"
|
||||
FINAL_C = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_C` is not allowed"
|
||||
FINAL_D = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_D` is not allowed"
|
||||
FINAL_E = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_E` is not allowed"
|
||||
FINAL_F = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_F` is not allowed"
|
||||
|
||||
def global_use():
|
||||
global FINAL_A, FINAL_B, FINAL_C, FINAL_D, FINAL_E, FINAL_F
|
||||
FINAL_A = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_A` is not allowed"
|
||||
FINAL_B = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_B` is not allowed"
|
||||
FINAL_C = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_C` is not allowed"
|
||||
FINAL_D = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_D` is not allowed"
|
||||
FINAL_E = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_E` is not allowed"
|
||||
FINAL_F = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_F` is not allowed"
|
||||
|
||||
def local_use():
|
||||
# These are not errors, because they refer to local variables
|
||||
FINAL_A = 2
|
||||
FINAL_B = 2
|
||||
FINAL_C = 2
|
||||
FINAL_D = 2
|
||||
FINAL_E = 2
|
||||
FINAL_F = 2
|
||||
|
||||
def nonlocal_use():
|
||||
X: Final[int] = 1
|
||||
def inner():
|
||||
nonlocal X
|
||||
# TODO: this should be an error
|
||||
X = 2
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from mod import FINAL_A, FINAL_B, FINAL_C, FINAL_D, FINAL_E, FINAL_F
|
||||
|
||||
FINAL_A = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_A` is not allowed"
|
||||
FINAL_B = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_B` is not allowed"
|
||||
FINAL_C = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_C` is not allowed"
|
||||
FINAL_D = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_D` is not allowed"
|
||||
FINAL_E = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_E` is not allowed"
|
||||
FINAL_F = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `FINAL_F` is not allowed"
|
||||
```
|
||||
|
||||
### Attributes
|
||||
|
||||
Assignments to attributes qualified with `Final` are also not allowed:
|
||||
|
||||
```py
|
||||
from typing import Final
|
||||
|
||||
class C:
|
||||
FINAL_A: Final[int] = 1
|
||||
FINAL_B: Final = 1
|
||||
|
||||
def __init__(self):
|
||||
self.FINAL_C: Final[int] = 1
|
||||
self.FINAL_D: Final = 1
|
||||
|
||||
# TODO: these should be errors (that mention `Final`)
|
||||
C.FINAL_A = 2
|
||||
# error: [invalid-assignment] "Object of type `Literal[2]` is not assignable to attribute `FINAL_B` of type `Literal[1]`"
|
||||
C.FINAL_B = 2
|
||||
|
||||
# TODO: these should be errors (that mention `Final`)
|
||||
c = C()
|
||||
c.FINAL_A = 2
|
||||
# error: [invalid-assignment] "Object of type `Literal[2]` is not assignable to attribute `FINAL_B` of type `Literal[1]`"
|
||||
c.FINAL_B = 2
|
||||
c.FINAL_C = 2
|
||||
c.FINAL_D = 2
|
||||
```
|
||||
|
||||
## Mutability
|
||||
|
||||
Objects qualified with `Final` *can be modified*. `Final` represents a constant reference to an
|
||||
object, but that object itself may still be mutable:
|
||||
|
||||
```py
|
||||
from typing import Final
|
||||
|
||||
class C:
|
||||
x: int = 1
|
||||
|
||||
FINAL_C_INSTANCE: Final[C] = C()
|
||||
FINAL_C_INSTANCE.x = 2
|
||||
|
||||
FINAL_LIST: Final[list[int]] = [1, 2, 3]
|
||||
FINAL_LIST[0] = 4
|
||||
```
|
||||
|
||||
## Too many arguments
|
||||
|
|
@ -168,4 +256,18 @@ class C:
|
|||
NO_RHS: Final
|
||||
```
|
||||
|
||||
## Full diagnostics
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing import Final
|
||||
|
||||
MY_CONSTANT: Final[int] = 1
|
||||
|
||||
# more code
|
||||
|
||||
MY_CONSTANT = 2 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
[`typing.final`]: https://docs.python.org/3/library/typing.html#typing.Final
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue