mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-18 19:41:34 +00:00
[ty] Further improve subscript assignment diagnostics (#21411)
## Summary
Further improve subscript assignment diagnostics, especially for
`dict`s:
```py
config: dict[str, int] = {}
config["retries"] = "three"
```
<img width="1276" height="274" alt="image"
src="https://github.com/user-attachments/assets/9762c733-8d1c-4a57-8c8a-99825071dc7d"
/>
I have many more ideas, but this looks like a reasonable first step.
Thank you @AlexWaygood for some of the suggestions here.
## Test Plan
Update tests
This commit is contained in:
parent
12e74ae894
commit
04ab9170d6
14 changed files with 147 additions and 76 deletions
|
|
@ -19,12 +19,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, int].__setitem__(key: str, value: int, /) -> None` cannot be called with a key of type `Literal[0]` and a value of type `Literal[3]` on object of type `dict[str, int]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal[0]` and value of type `Literal[3]` on object of type `dict[str, int]`
|
||||
--> src/mdtest_snippet.py:2:1
|
||||
|
|
||||
1 | config: dict[str, int] = {}
|
||||
2 | config[0] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^-^^^^^
|
||||
| |
|
||||
| Expected key of type `str`, got `Literal[0]`
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-key]: Cannot access `Config` with a key of type `Literal[0]`. Only string literals are allowed as keys on TypedDicts.
|
||||
error[invalid-key]: TypedDict `Config` can only be subscripted with a string literal key, got key of type `Literal[0]`.
|
||||
--> src/mdtest_snippet.py:7:12
|
||||
|
|
||||
6 | def _(config: Config) -> None:
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, int].__setitem__(key: str, value: int, /) -> None` cannot be called with a key of type `Literal["retries"]` and a value of type `Literal["three"]` on object of type `dict[str, int]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["retries"]` and value of type `Literal["three"]` on object of type `dict[str, int]`
|
||||
--> src/mdtest_snippet.py:2:1
|
||||
|
|
||||
1 | config: dict[str, int] = {}
|
||||
2 | config["retries"] = "three" # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^-------
|
||||
| |
|
||||
| Expected value of type `int`, got `Literal["three"]`
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Cannot assign to a subscript on an object of type `ReadOnlyDict` with no `__setitem__` method
|
||||
error[invalid-assignment]: Cannot assign to a subscript on an object of type `ReadOnlyDict`
|
||||
--> src/mdtest_snippet.py:6:1
|
||||
|
|
||||
5 | config = ReadOnlyDict()
|
||||
6 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Consider adding a `__setitem__` method to `ReadOnlyDict`.
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,14 +19,15 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Cannot assign to a subscript on an object of type `None` with no `__setitem__` method
|
||||
error[invalid-assignment]: Cannot assign to a subscript on an object of type `None`
|
||||
--> src/mdtest_snippet.py:2:5
|
||||
|
|
||||
1 | def _(config: dict[str, int] | None) -> None:
|
||||
2 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: The full type of the subscripted object is `dict[str, int] | None`
|
||||
info: `None` does not have a `__setitem__` method.
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, str].__setitem__(key: str, value: str, /) -> None` cannot be called with a key of type `Literal["retries"]` and a value of type `Literal[3]` on object of type `dict[str, str]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["retries"]` and value of type `Literal[3]` on object of type `dict[str, str]`
|
||||
--> src/mdtest_snippet.py:2:5
|
||||
|
|
||||
1 | def _(config: dict[str, int] | dict[str, str]) -> None:
|
||||
2 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^-
|
||||
| |
|
||||
| Expected value of type `str`, got `Literal[3]`
|
||||
|
|
||||
info: The full type of the subscripted object is `dict[str, int] | dict[str, str]`
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
|
|
|||
|
|
@ -21,13 +21,15 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
|||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, int].__setitem__(key: str, value: int, /) -> None` cannot be called with a key of type `Literal["retries"]` and a value of type `float` on object of type `dict[str, int]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["retries"]` and value of type `float` on object of type `dict[str, int]`
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
2 | # error: [invalid-assignment]
|
||||
3 | # error: [invalid-assignment]
|
||||
4 | config["retries"] = 3.0
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^---
|
||||
| |
|
||||
| Expected value of type `int`, got `float`
|
||||
|
|
||||
info: The full type of the subscripted object is `dict[str, int] | dict[str, str]`
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
|
@ -35,13 +37,15 @@ info: rule `invalid-assignment` is enabled by default
|
|||
```
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `bound method dict[str, str].__setitem__(key: str, value: str, /) -> None` cannot be called with a key of type `Literal["retries"]` and a value of type `float` on object of type `dict[str, str]`
|
||||
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["retries"]` and value of type `float` on object of type `dict[str, str]`
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
2 | # error: [invalid-assignment]
|
||||
3 | # error: [invalid-assignment]
|
||||
4 | config["retries"] = 3.0
|
||||
| ^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^---
|
||||
| |
|
||||
| Expected value of type `str`, got `float`
|
||||
|
|
||||
info: The full type of the subscripted object is `dict[str, int] | dict[str, str]`
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ info: rule `invalid-key` is enabled by default
|
|||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: Invalid key of type `str` for TypedDict `Person`
|
||||
error[invalid-key]: TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`
|
||||
--> src/mdtest_snippet.py:16:12
|
||||
|
|
||||
15 | def access_with_str_key(person: Person, str_key: str):
|
||||
|
|
@ -146,7 +146,7 @@ info: rule `invalid-key` is enabled by default
|
|||
```
|
||||
|
||||
```
|
||||
error[invalid-key]: Cannot access `Person` with a key of type `str`. Only string literals are allowed as keys on TypedDicts.
|
||||
error[invalid-key]: TypedDict `Person` can only be subscripted with a string literal key, got key of type `str`.
|
||||
--> src/mdtest_snippet.py:25:12
|
||||
|
|
||||
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ a[0] = 0
|
|||
class NoSetitem: ...
|
||||
|
||||
a = NoSetitem()
|
||||
a[0] = 0 # error: "Cannot assign to a subscript on an object of type `NoSetitem` with no `__setitem__` method"
|
||||
a[0] = 0 # error: "Cannot assign to a subscript on an object of type `NoSetitem`"
|
||||
```
|
||||
|
||||
## `__setitem__` not callable
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ def name_or_age() -> Literal["name", "age"]:
|
|||
carol: Person = {NAME: "Carol", AGE: 20}
|
||||
|
||||
reveal_type(carol[NAME]) # revealed: str
|
||||
# error: [invalid-key] "Invalid key of type `str` for TypedDict `Person`"
|
||||
# error: [invalid-key] "TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`"
|
||||
reveal_type(carol[non_literal()]) # revealed: Unknown
|
||||
reveal_type(carol[name_or_age()]) # revealed: str | int | None
|
||||
|
||||
|
|
@ -553,7 +553,7 @@ def _(
|
|||
# error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "non_existing""
|
||||
reveal_type(person["non_existing"]) # revealed: Unknown
|
||||
|
||||
# error: [invalid-key] "Invalid key of type `str` for TypedDict `Person`"
|
||||
# error: [invalid-key] "TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`"
|
||||
reveal_type(person[str_key]) # revealed: Unknown
|
||||
|
||||
# No error here:
|
||||
|
|
@ -631,10 +631,10 @@ def _(person: Person, union_of_keys: Literal["name", "age"], unknown_value: Any)
|
|||
person[union_of_keys] = None
|
||||
|
||||
def _(person: Person, str_key: str, literalstr_key: LiteralString):
|
||||
# error: [invalid-key] "Cannot access `Person` with a key of type `str`. Only string literals are allowed as keys on TypedDicts."
|
||||
# error: [invalid-key] "TypedDict `Person` can only be subscripted with a string literal key, got key of type `str`."
|
||||
person[str_key] = None
|
||||
|
||||
# error: [invalid-key] "Cannot access `Person` with a key of type `LiteralString`. Only string literals are allowed as keys on TypedDicts."
|
||||
# error: [invalid-key] "TypedDict `Person` can only be subscripted with a string literal key, got key of type `LiteralString`."
|
||||
person[literalstr_key] = None
|
||||
|
||||
def _(person: Person, unknown_key: Any):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue