mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] TypedDict: Add support for typing.ReadOnly
(#20241)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
## Summary Add support for `typing.ReadOnly` as a type qualifier to mark `TypedDict` fields as being read-only. If you try to mutate them, you get a new diagnostic: <img width="787" height="234" alt="image" src="https://github.com/user-attachments/assets/f62fddf9-4961-4bcd-ad1c-747043ebe5ff" /> ## Test Plan * New Markdown tests * The typing conformance changes are all correct. There are some false negatives, but those are related to the missing support for the functional form of `TypedDict`, or to overriding of fields via inheritance. Both of these topics are tracked in https://github.com/astral-sh/ty/issues/154
This commit is contained in:
parent
888a22e849
commit
a24a4b55ee
5 changed files with 131 additions and 7 deletions
|
@ -37,6 +37,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/typed_dict.md
|
|||
23 |
|
||||
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||
25 | person[str_key] = "Alice" # error: [invalid-key]
|
||||
26 | from typing_extensions import ReadOnly
|
||||
27 |
|
||||
28 | class Employee(TypedDict):
|
||||
29 | id: ReadOnly[int]
|
||||
30 | name: str
|
||||
31 |
|
||||
32 | def write_to_readonly_key(employee: Employee):
|
||||
33 | employee["id"] = 42 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
@ -127,7 +135,22 @@ error[invalid-key]: Cannot access `Person` with a key of type `str`. Only string
|
|||
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||
25 | person[str_key] = "Alice" # error: [invalid-key]
|
||||
| ^^^^^^^
|
||||
26 | from typing_extensions import ReadOnly
|
||||
|
|
||||
info: rule `invalid-key` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Can not assign to key "id" on TypedDict `Employee`
|
||||
--> src/mdtest_snippet.py:33:5
|
||||
|
|
||||
32 | def write_to_readonly_key(employee: Employee):
|
||||
33 | employee["id"] = 42 # error: [invalid-assignment]
|
||||
| -------- ^^^^ key is marked read-only
|
||||
| |
|
||||
| TypedDict `Employee`
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
@ -444,8 +444,7 @@ def _(person: Person, unknown_key: Any):
|
|||
|
||||
## `ReadOnly`
|
||||
|
||||
`ReadOnly` is not supported yet, but this test makes sure that we do not emit any false positive
|
||||
diagnostics:
|
||||
Assignments to keys that are marked `ReadOnly` will produce an error:
|
||||
|
||||
```py
|
||||
from typing_extensions import TypedDict, ReadOnly, Required
|
||||
|
@ -458,10 +457,26 @@ class Person(TypedDict, total=False):
|
|||
alice: Person = {"id": 1, "name": "Alice", "age": 30}
|
||||
alice["age"] = 31 # okay
|
||||
|
||||
# TODO: this should be an error
|
||||
# error: [invalid-assignment] "Can not assign to key "id" on TypedDict `Person`: key is marked read-only"
|
||||
alice["id"] = 2
|
||||
```
|
||||
|
||||
This also works if all fields on a `TypedDict` are `ReadOnly`, in which case we synthesize a
|
||||
`__setitem__` method with a `key` type of `Never`:
|
||||
|
||||
```py
|
||||
class Config(TypedDict):
|
||||
host: ReadOnly[str]
|
||||
port: ReadOnly[int]
|
||||
|
||||
config: Config = {"host": "localhost", "port": 8080}
|
||||
|
||||
# error: [invalid-assignment] "Can not assign to key "host" on TypedDict `Config`: key is marked read-only"
|
||||
config["host"] = "127.0.0.1"
|
||||
# error: [invalid-assignment] "Can not assign to key "port" on TypedDict `Config`: key is marked read-only"
|
||||
config["port"] = 80
|
||||
```
|
||||
|
||||
## Methods on `TypedDict`
|
||||
|
||||
```py
|
||||
|
@ -846,6 +861,19 @@ def write_to_non_literal_string_key(person: Person, str_key: str):
|
|||
person[str_key] = "Alice" # error: [invalid-key]
|
||||
```
|
||||
|
||||
Assignment to `ReadOnly` keys:
|
||||
|
||||
```py
|
||||
from typing_extensions import ReadOnly
|
||||
|
||||
class Employee(TypedDict):
|
||||
id: ReadOnly[int]
|
||||
name: str
|
||||
|
||||
def write_to_readonly_key(employee: Employee):
|
||||
employee["id"] = 42 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Import aliases
|
||||
|
||||
`TypedDict` can be imported with aliases and should work correctly:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue