From 84c3cecad62fb174e532fa5027a7a31999e18a86 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 12 Nov 2025 15:29:26 +0100 Subject: [PATCH] [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. --- ...t…_-_Invalid_key_type_(d3d47de65fb3bad).snap | 31 +++++ ..._Invalid_key_type_for…_(815dae276e2fd2b7).snap | 36 ++++++ ...¦_-_Invalid_value_type_(f87bd015df018509).snap | 31 +++++ ..._Invalid_value_type_f…_(155d53762388f9ad).snap | 48 +++++++ ..._Misspelled_key_for_`…_(7cf0fa634e2a2d59).snap | 38 ++++++ ..._No_`__setitem__`_met…_(468f62a3bdd1d60c).snap | 35 ++++++ ..._Possibly_missing_`__…_(efd3f0c02e9b89e9).snap | 31 +++++ ..._Unknown_key_for_all_…_(1c685d9d10678263).snap | 40 ++++++ ..._Unknown_key_for_one_…_(b515711c0a451a86).snap | 40 ++++++ ..._Wrong_value_type_for…_(57372b65e30392a8).snap | 31 +++++ ..._Wrong_value_type_for…_(ffe39a3bae68cfe4).snap | 31 +++++ .../subscript/assignment_diagnostics.md | 117 ++++++++++++++++++ .../resources/mdtest/typed_dict.md | 41 +++++- 13 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_key_type_(d3d47de65fb3bad).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_key_type_for…_(815dae276e2fd2b7).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_value_type_(f87bd015df018509).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_value_type_f…_(155d53762388f9ad).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Misspelled_key_for_`…_(7cf0fa634e2a2d59).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_No_`__setitem__`_met…_(468f62a3bdd1d60c).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Possibly_missing_`__…_(efd3f0c02e9b89e9).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Unknown_key_for_all_…_(1c685d9d10678263).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Unknown_key_for_one_…_(b515711c0a451a86).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Wrong_value_type_for…_(57372b65e30392a8).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Wrong_value_type_for…_(ffe39a3bae68cfe4).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_key_type_(d3d47de65fb3bad).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_key_type_(d3d47de65fb3bad).snap new file mode 100644 index 0000000000..6081e0f5d9 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_key_type_(d3d47de65fb3bad).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_key_type_for…_(815dae276e2fd2b7).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_key_type_for…_(815dae276e2fd2b7).snap new file mode 100644 index 0000000000..2fbfb5323f --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_key_type_for…_(815dae276e2fd2b7).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_value_type_(f87bd015df018509).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_value_type_(f87bd015df018509).snap new file mode 100644 index 0000000000..125bcdfac1 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_value_type_(f87bd015df018509).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_value_type_f…_(155d53762388f9ad).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_value_type_f…_(155d53762388f9ad).snap new file mode 100644 index 0000000000..c2821569ce --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Invalid_value_type_f…_(155d53762388f9ad).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Misspelled_key_for_`…_(7cf0fa634e2a2d59).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Misspelled_key_for_`…_(7cf0fa634e2a2d59).snap new file mode 100644 index 0000000000..e6036f32e0 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Misspelled_key_for_`…_(7cf0fa634e2a2d59).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_No_`__setitem__`_met…_(468f62a3bdd1d60c).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_No_`__setitem__`_met…_(468f62a3bdd1d60c).snap new file mode 100644 index 0000000000..dec0ab3417 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_No_`__setitem__`_met…_(468f62a3bdd1d60c).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Possibly_missing_`__…_(efd3f0c02e9b89e9).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Possibly_missing_`__…_(efd3f0c02e9b89e9).snap new file mode 100644 index 0000000000..ced810cf72 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Possibly_missing_`__…_(efd3f0c02e9b89e9).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Unknown_key_for_all_…_(1c685d9d10678263).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Unknown_key_for_all_…_(1c685d9d10678263).snap new file mode 100644 index 0000000000..6444c84f36 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Unknown_key_for_all_…_(1c685d9d10678263).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Unknown_key_for_one_…_(b515711c0a451a86).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Unknown_key_for_one_…_(b515711c0a451a86).snap new file mode 100644 index 0000000000..2b840a6783 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Unknown_key_for_one_…_(b515711c0a451a86).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Wrong_value_type_for…_(57372b65e30392a8).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Wrong_value_type_for…_(57372b65e30392a8).snap new file mode 100644 index 0000000000..37ea1c111a --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Wrong_value_type_for…_(57372b65e30392a8).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Wrong_value_type_for…_(ffe39a3bae68cfe4).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Wrong_value_type_for…_(ffe39a3bae68cfe4).snap new file mode 100644 index 0000000000..dfd0136536 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/assignment_diagnosti…_-_Subscript_assignment…_-_Wrong_value_type_for…_(ffe39a3bae68cfe4).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md b/crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md new file mode 100644 index 0000000000..ed23208eb1 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md @@ -0,0 +1,117 @@ +# Subscript assignment 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] +``` diff --git a/crates/ty_python_semantic/resources/mdtest/typed_dict.md b/crates/ty_python_semantic/resources/mdtest/typed_dict.md index b4203ce2b6..8b8fcfffa3 100644 --- a/crates/ty_python_semantic/resources/mdtest/typed_dict.md +++ b/crates/ty_python_semantic/resources/mdtest/typed_dict.md @@ -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