[ty] Subscript assignment diagnostics follow-up (#21452)

## Summary

Follow up from https://github.com/astral-sh/ruff/pull/21411. Again,
there are more things that could be improved here (like the diagnostics
for `lists`, or extending what we have for `dict` to `OrderedDict` etc),
but that will have to be postponed.
This commit is contained in:
David Peter
2025-11-17 12:14:58 +01:00
committed by GitHub
parent 901e9cdf49
commit 1a86e13472
11 changed files with 110 additions and 32 deletions

View File

@@ -313,7 +313,7 @@ def f(c: C, s: str):
reveal_type(c.x) # revealed: int | None
s = c.x # error: [invalid-assignment]
# error: [invalid-assignment] "Method `__setitem__` of type `Overload[(key: SupportsIndex, value: int, /) -> None, (key: slice[Any, Any, Any], value: Iterable[int], /) -> None]` cannot be called with a key of type `Literal[0]` and a value of type `str` on object of type `list[int]`"
# error: [invalid-assignment] "Invalid subscript assignment with key of type `Literal[0]` and value of type `str` on object of type `list[int]`"
c.l[0] = s
reveal_type(c.l[0]) # revealed: int
```

View File

@@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid key type
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid key type - For a `dict`
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
---

View File

@@ -0,0 +1,31 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid key type - For a `list`
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
---
# Python source files
## mdtest_snippet.py
```
1 | numbers: list[int] = []
2 | numbers["zero"] = 3 # error: [invalid-assignment]
```
# Diagnostics
```
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal["zero"]` and value of type `Literal[3]` on object of type `list[int]`
--> src/mdtest_snippet.py:2:1
|
1 | numbers: list[int] = []
2 | numbers["zero"] = 3 # error: [invalid-assignment]
| ^^^^^^^^^^^^^^^^^^^
|
info: rule `invalid-assignment` is enabled by default
```

View File

@@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid value type
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid value type - For a `dict`
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
---

View File

@@ -0,0 +1,31 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid value type - For a `list`
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
---
# Python source files
## mdtest_snippet.py
```
1 | numbers: list[int] = []
2 | numbers[0] = "three" # error: [invalid-assignment]
```
# Diagnostics
```
error[invalid-assignment]: Invalid subscript assignment with key of type `Literal[0]` and value of type `Literal["three"]` on object of type `list[int]`
--> src/mdtest_snippet.py:2:1
|
1 | numbers: list[int] = []
2 | numbers[0] = "three" # error: [invalid-assignment]
| ^^^^^^^^^^^^^^^^^^^^
|
info: rule `invalid-assignment` is enabled by default
```

View File

@@ -89,7 +89,7 @@ info: rule `invalid-key` is enabled by default
```
```
error[invalid-key]: TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`
error[invalid-key]: TypedDict `Person` can only be subscripted with a string literal key, got key of type `str`
--> src/mdtest_snippet.py:16:12
|
15 | def access_with_str_key(person: Person, str_key: str):

View File

@@ -4,6 +4,15 @@
## Invalid value type
### For a `list`
```py
numbers: list[int] = []
numbers[0] = "three" # error: [invalid-assignment]
```
### For a `dict`
```py
config: dict[str, int] = {}
config["retries"] = "three" # error: [invalid-assignment]
@@ -11,6 +20,15 @@ config["retries"] = "three" # error: [invalid-assignment]
## Invalid key type
### For a `list`
```py
numbers: list[int] = []
numbers["zero"] = 3 # error: [invalid-assignment]
```
### For a `dict`
```py
config: dict[str, int] = {}
config[0] = 3 # error: [invalid-assignment]

View File

@@ -110,6 +110,6 @@ class Identity:
pass
a = Identity()
# error: [invalid-assignment] "Method `__setitem__` of type `bound method Identity.__setitem__(index: int, value: int) -> None` cannot be called with a key of type `Literal["a"]` and a value of type `Literal[0]` on object of type `Identity`"
# error: [invalid-assignment] "Invalid subscript assignment with key of type `Literal["a"]` and value of type `Literal[0]` on object of type `Identity`"
a["a"] = 0
```

View File

@@ -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] "TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`"
# error: [invalid-key] "TypedDict `Person` can only be subscripted with a string literal key, 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] "TypedDict `Person` can only be subscripted with string literal keys, got key of type `str`"
# error: [invalid-key] "TypedDict `Person` can only be subscripted with a string literal key, got key of type `str`"
reveal_type(person[str_key]) # revealed: Unknown
# No error here: