mirror of https://github.com/astral-sh/ruff
[ty] Baseline for subscript assignment diagnostics (#21404)
## Summary Add (snapshot) tests for subscript assignment diagnostics. This is mainly intended to establish a baseline before I hope to improve some of these messages.
This commit is contained in:
parent
e8e8180888
commit
84c3cecad6
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid key type
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | config: dict[str, int] = {}
|
||||
2 | config[0] = 3 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# 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]`
|
||||
--> src/mdtest_snippet.py:2:1
|
||||
|
|
||||
1 | config: dict[str, int] = {}
|
||||
2 | config[0] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid key type for `TypedDict`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypedDict
|
||||
2 |
|
||||
3 | class Config(TypedDict):
|
||||
4 | retries: int
|
||||
5 |
|
||||
6 | def _(config: Config) -> None:
|
||||
7 | config[0] = 3 # error: [invalid-key]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-key]: Cannot access `Config` with a key of type `Literal[0]`. Only string literals are allowed as keys on TypedDicts.
|
||||
--> src/mdtest_snippet.py:7:12
|
||||
|
|
||||
6 | def _(config: Config) -> None:
|
||||
7 | config[0] = 3 # error: [invalid-key]
|
||||
| ^
|
||||
|
|
||||
info: rule `invalid-key` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid value type
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | config: dict[str, int] = {}
|
||||
2 | config["retries"] = "three" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# 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]`
|
||||
--> src/mdtest_snippet.py:2:1
|
||||
|
|
||||
1 | config: dict[str, int] = {}
|
||||
2 | config["retries"] = "three" # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Invalid value type for `TypedDict`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypedDict
|
||||
2 |
|
||||
3 | class Config(TypedDict):
|
||||
4 | retries: int
|
||||
5 |
|
||||
6 | def _(config: Config) -> None:
|
||||
7 | config["retries"] = "three" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Invalid assignment to key "retries" with declared type `int` on TypedDict `Config`
|
||||
--> src/mdtest_snippet.py:7:5
|
||||
|
|
||||
6 | def _(config: Config) -> None:
|
||||
7 | config["retries"] = "three" # error: [invalid-assignment]
|
||||
| ------ --------- ^^^^^^^ value of type `Literal["three"]`
|
||||
| | |
|
||||
| | key has declared type `int`
|
||||
| TypedDict `Config`
|
||||
|
|
||||
info: Item declaration
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | class Config(TypedDict):
|
||||
4 | retries: int
|
||||
| ------------ Item declared here
|
||||
5 |
|
||||
6 | def _(config: Config) -> None:
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Misspelled key for `TypedDict`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypedDict
|
||||
2 |
|
||||
3 | class Config(TypedDict):
|
||||
4 | retries: int
|
||||
5 |
|
||||
6 | def _(config: Config) -> None:
|
||||
7 | config["Retries"] = 30.0 # error: [invalid-key]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-key]: Invalid key for TypedDict `Config`
|
||||
--> src/mdtest_snippet.py:7:5
|
||||
|
|
||||
6 | def _(config: Config) -> None:
|
||||
7 | config["Retries"] = 30.0 # error: [invalid-key]
|
||||
| ------ ^^^^^^^^^ Unknown key "Retries" - did you mean "retries"?
|
||||
| |
|
||||
| TypedDict `Config`
|
||||
|
|
||||
info: rule `invalid-key` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - No `__setitem__` method
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class ReadOnlyDict:
|
||||
2 | def __getitem__(self, key: str) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | config = ReadOnlyDict()
|
||||
6 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Cannot assign to object of type `ReadOnlyDict` with no `__setitem__` method
|
||||
--> src/mdtest_snippet.py:6:1
|
||||
|
|
||||
5 | config = ReadOnlyDict()
|
||||
6 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Possibly missing `__setitem__` method
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(config: dict[str, int] | None) -> None:
|
||||
2 | config["retries"] = 3 # error: [possibly-missing-implicit-call]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
warning[possibly-missing-implicit-call]: Method `__setitem__` of type `dict[str, int] | None` may be missing
|
||||
--> src/mdtest_snippet.py:2:5
|
||||
|
|
||||
1 | def _(config: dict[str, int] | None) -> None:
|
||||
2 | config["retries"] = 3 # error: [possibly-missing-implicit-call]
|
||||
| ^^^^^^
|
||||
|
|
||||
info: rule `possibly-missing-implicit-call` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Unknown key for all elemens of a union
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypedDict
|
||||
2 |
|
||||
3 | class Person(TypedDict):
|
||||
4 | name: str
|
||||
5 |
|
||||
6 | class Animal(TypedDict):
|
||||
7 | name: str
|
||||
8 | legs: int
|
||||
9 |
|
||||
10 | def _(being: Person | Animal) -> None:
|
||||
11 | being["surname"] = "unknown" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `(key: Literal["name"], value: str, /) -> None` cannot be called with a key of type `Literal["surname"]` and a value of type `Literal["unknown"]` on object of type `Person | Animal`
|
||||
--> src/mdtest_snippet.py:11:5
|
||||
|
|
||||
10 | def _(being: Person | Animal) -> None:
|
||||
11 | being["surname"] = "unknown" # error: [invalid-assignment]
|
||||
| ^^^^^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Unknown key for one element of a union
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypedDict
|
||||
2 |
|
||||
3 | class Person(TypedDict):
|
||||
4 | name: str
|
||||
5 |
|
||||
6 | class Animal(TypedDict):
|
||||
7 | name: str
|
||||
8 | legs: int
|
||||
9 |
|
||||
10 | def _(being: Person | Animal) -> None:
|
||||
11 | being["legs"] = 4 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `(key: Literal["name"], value: str, /) -> None` cannot be called with a key of type `Literal["legs"]` and a value of type `Literal[4]` on object of type `Person | Animal`
|
||||
--> src/mdtest_snippet.py:11:5
|
||||
|
|
||||
10 | def _(being: Person | Animal) -> None:
|
||||
11 | being["legs"] = 4 # error: [invalid-assignment]
|
||||
| ^^^^^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Wrong value type for one element of a union
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(config: dict[str, int] | dict[str, str]) -> None:
|
||||
2 | config["retries"] = 3 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `(bound method dict[str, int].__setitem__(key: str, value: int, /) -> None) | (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, int] | 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]
|
||||
| ^^^^^^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Wrong value type for all elements of a union
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(config: dict[str, int] | dict[str, str]) -> None:
|
||||
2 | config["retries"] = 3.0 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-assignment]: Method `__setitem__` of type `(bound method dict[str, int].__setitem__(key: str, value: int, /) -> None) | (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, int] | dict[str, str]`
|
||||
--> src/mdtest_snippet.py:2:5
|
||||
|
|
||||
1 | def _(config: dict[str, int] | dict[str, str]) -> None:
|
||||
2 | config["retries"] = 3.0 # error: [invalid-assignment]
|
||||
| ^^^^^^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
# Subscript assignment diagnostics
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
## Invalid value type
|
||||
|
||||
```py
|
||||
config: dict[str, int] = {}
|
||||
config["retries"] = "three" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Invalid key type
|
||||
|
||||
```py
|
||||
config: dict[str, int] = {}
|
||||
config[0] = 3 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Invalid value type for `TypedDict`
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Config(TypedDict):
|
||||
retries: int
|
||||
|
||||
def _(config: Config) -> None:
|
||||
config["retries"] = "three" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Invalid key type for `TypedDict`
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Config(TypedDict):
|
||||
retries: int
|
||||
|
||||
def _(config: Config) -> None:
|
||||
config[0] = 3 # error: [invalid-key]
|
||||
```
|
||||
|
||||
## Misspelled key for `TypedDict`
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Config(TypedDict):
|
||||
retries: int
|
||||
|
||||
def _(config: Config) -> None:
|
||||
config["Retries"] = 30.0 # error: [invalid-key]
|
||||
```
|
||||
|
||||
## No `__setitem__` method
|
||||
|
||||
```py
|
||||
class ReadOnlyDict:
|
||||
def __getitem__(self, key: str) -> int:
|
||||
return 42
|
||||
|
||||
config = ReadOnlyDict()
|
||||
config["retries"] = 3 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Possibly missing `__setitem__` method
|
||||
|
||||
```py
|
||||
def _(config: dict[str, int] | None) -> None:
|
||||
config["retries"] = 3 # error: [possibly-missing-implicit-call]
|
||||
```
|
||||
|
||||
## Unknown key for one element of a union
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
|
||||
class Animal(TypedDict):
|
||||
name: str
|
||||
legs: int
|
||||
|
||||
def _(being: Person | Animal) -> None:
|
||||
being["legs"] = 4 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Unknown key for all elemens of a union
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
|
||||
class Animal(TypedDict):
|
||||
name: str
|
||||
legs: int
|
||||
|
||||
def _(being: Person | Animal) -> None:
|
||||
being["surname"] = "unknown" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Wrong value type for one element of a union
|
||||
|
||||
```py
|
||||
def _(config: dict[str, int] | dict[str, str]) -> None:
|
||||
config["retries"] = 3 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Wrong value type for all elements of a union
|
||||
|
||||
```py
|
||||
def _(config: dict[str, int] | dict[str, str]) -> None:
|
||||
config["retries"] = 3.0 # error: [invalid-assignment]
|
||||
```
|
||||
|
|
@ -526,10 +526,20 @@ class Person(TypedDict):
|
|||
name: str
|
||||
age: int | None
|
||||
|
||||
class Animal(TypedDict):
|
||||
name: str
|
||||
|
||||
NAME_FINAL: Final = "name"
|
||||
AGE_FINAL: Final[Literal["age"]] = "age"
|
||||
|
||||
def _(person: Person, literal_key: Literal["age"], union_of_keys: Literal["age", "name"], str_key: str, unknown_key: Any) -> None:
|
||||
def _(
|
||||
person: Person,
|
||||
being: Person | Animal,
|
||||
literal_key: Literal["age"],
|
||||
union_of_keys: Literal["age", "name"],
|
||||
str_key: str,
|
||||
unknown_key: Any,
|
||||
) -> None:
|
||||
reveal_type(person["name"]) # revealed: str
|
||||
reveal_type(person["age"]) # revealed: int | None
|
||||
|
||||
|
|
@ -548,18 +558,30 @@ def _(person: Person, literal_key: Literal["age"], union_of_keys: Literal["age",
|
|||
|
||||
# No error here:
|
||||
reveal_type(person[unknown_key]) # revealed: Unknown
|
||||
|
||||
reveal_type(being["name"]) # revealed: str
|
||||
|
||||
# TODO: A type of `int | None | Unknown` might be better here. The `str` is mixed in
|
||||
# because `Animal.__getitem__` can only return `str`.
|
||||
# error: [invalid-key] "Invalid key for TypedDict `Animal`"
|
||||
reveal_type(being["age"]) # revealed: int | None | str
|
||||
```
|
||||
|
||||
### Writing
|
||||
|
||||
```py
|
||||
from typing_extensions import TypedDict, Final, Literal, LiteralString, Any
|
||||
from ty_extensions import Intersection
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
surname: str
|
||||
age: int | None
|
||||
|
||||
class Animal(TypedDict):
|
||||
name: str
|
||||
legs: int
|
||||
|
||||
NAME_FINAL: Final = "name"
|
||||
AGE_FINAL: Final[Literal["age"]] = "age"
|
||||
|
||||
|
|
@ -583,6 +605,23 @@ def _(person: Person, union_of_keys: Literal["name", "surname"]):
|
|||
# error: [invalid-assignment] "Cannot assign value of type `Literal[1]` to key of type `Literal["name", "surname"]` on TypedDict `Person`"
|
||||
person[union_of_keys] = 1
|
||||
|
||||
def _(being: Person | Animal):
|
||||
being["name"] = "Being"
|
||||
|
||||
# error: [invalid-assignment] "Method `__setitem__` of type `(Overload[(key: Literal["name"], value: str, /) -> None, (key: Literal["surname"], value: str, /) -> None, (key: Literal["age"], value: int | None, /) -> None]) | (Overload[(key: Literal["name"], value: str, /) -> None, (key: Literal["legs"], value: int, /) -> None])` cannot be called with a key of type `Literal["name"]` and a value of type `Literal[1]` on object of type `Person | Animal`"
|
||||
being["name"] = 1
|
||||
|
||||
# error: [invalid-assignment] "Method `__setitem__` of type `(Overload[(key: Literal["name"], value: str, /) -> None, (key: Literal["surname"], value: str, /) -> None, (key: Literal["age"], value: int | None, /) -> None]) | (Overload[(key: Literal["name"], value: str, /) -> None, (key: Literal["legs"], value: int, /) -> None])` cannot be called with a key of type `Literal["surname"]` and a value of type `Literal["unknown"]` on object of type `Person | Animal`"
|
||||
being["surname"] = "unknown"
|
||||
|
||||
def _(centaur: Intersection[Person, Animal]):
|
||||
centaur["name"] = "Chiron"
|
||||
centaur["age"] = 100
|
||||
centaur["legs"] = 4
|
||||
|
||||
# TODO: This should be an `invalid-key` error
|
||||
centaur["unknown"] = "value"
|
||||
|
||||
def _(person: Person, union_of_keys: Literal["name", "age"], unknown_value: Any):
|
||||
person[union_of_keys] = unknown_value
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue