Skip to content

Commit 1aaa084

Browse files
authored
[ty] More tests for TypedDict (#20205)
## Summary A small set of additional tests for `TypedDict` that I wrote while going through the spec. Note that this certainly doesn't make the test suite exhaustive (see remaining open points in the updated list here: astral-sh/ty#154).
1 parent b49aa35 commit 1aaa084

File tree

1 file changed

+50
-2
lines changed

1 file changed

+50
-2
lines changed

crates/ty_python_semantic/resources/mdtest/typed_dict.md

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ Person({"name": "Alice"})
128128
accepts_person({"name": "Alice"})
129129
# TODO: this should be an error, similar to the above
130130
house.owner = {"name": "Alice"}
131+
a_person: Person
132+
# TODO: this should be an error, similar to the above
133+
a_person = {"name": "Alice"}
131134
```
132135

133136
All of these have an invalid type for the `name` field:
@@ -144,6 +147,9 @@ Person({"name": None, "age": 30})
144147
accepts_person({"name": None, "age": 30})
145148
# TODO: this should be an error, similar to the above
146149
house.owner = {"name": None, "age": 30}
150+
a_person: Person
151+
# TODO: this should be an error, similar to the above
152+
a_person = {"name": None, "age": 30}
147153
```
148154

149155
All of these have an extra field that is not defined in the `TypedDict`:
@@ -160,6 +166,9 @@ Person({"name": "Alice", "age": 30, "extra": True})
160166
accepts_person({"name": "Alice", "age": 30, "extra": True})
161167
# TODO: this should be an error
162168
house.owner = {"name": "Alice", "age": 30, "extra": True}
169+
# TODO: this should be an error
170+
a_person: Person
171+
a_person = {"name": "Alice", "age": 30, "extra": True}
163172
```
164173

165174
## Type ignore compatibility issues
@@ -242,8 +251,9 @@ invalid_extra = OptionalPerson(name="George", extra=True)
242251

243252
## `Required` and `NotRequired`
244253

245-
You can have fine-grained control over field requirements using `Required` and `NotRequired`
246-
qualifiers, which override the class-level `total=` setting:
254+
You can have fine-grained control over keys using `Required` and `NotRequired` qualifiers. These
255+
qualifiers override the class-level `total` setting, which sets the default (`total=True` means that
256+
all keys are required by default, `total=False` means that all keys are non-required by default):
247257

248258
```py
249259
from typing_extensions import TypedDict, Required, NotRequired
@@ -444,6 +454,12 @@ class Person(TypedDict, total=False):
444454
id: ReadOnly[Required[int]]
445455
name: str
446456
age: int | None
457+
458+
alice: Person = {"id": 1, "name": "Alice", "age": 30}
459+
alice["age"] = 31 # okay
460+
461+
# TODO: this should be an error
462+
alice["id"] = 2
447463
```
448464

449465
## Methods on `TypedDict`
@@ -764,6 +780,38 @@ from typing import TypedDict
764780
x: TypedDict = {"name": "Alice"}
765781
```
766782

783+
### `dict`-subclass inhabitants
784+
785+
Values that inhabit a `TypedDict` type must be instances of `dict` itself, not a subclass:
786+
787+
```py
788+
from typing import TypedDict
789+
790+
class MyDict(dict):
791+
pass
792+
793+
class Person(TypedDict):
794+
name: str
795+
age: int | None
796+
797+
# TODO: this should be an error
798+
x: Person = MyDict({"name": "Alice", "age": 30})
799+
```
800+
801+
### Cannot be used in `isinstance` tests
802+
803+
```py
804+
from typing import TypedDict
805+
806+
class Person(TypedDict):
807+
name: str
808+
age: int | None
809+
810+
def _(obj: object) -> bool:
811+
# TODO: this should be an error
812+
return isinstance(obj, Person)
813+
```
814+
767815
## Diagnostics
768816

769817
<!-- snapshot-diagnostics -->

0 commit comments

Comments
 (0)