← Back to index

typeddicts_type_consistency.py

True Positive
False Positive
False Negative
Optional (detected)
Warning or Info
TP: 9
FP: 0
FN: 0
Optional: 1 / 2
1"""
2Tests the type consistency rules for TypedDict objects.
3"""
4
5# Specification: https://typing.readthedocs.io/en/latest/spec/typeddict.html#type-consistency
6
7from typing import Any, Literal, Mapping, TypedDict
8
9
10class A1(TypedDict):
11 x: int | None
14class B1(TypedDict):
15 x: int
18b1: B1 = {"x": 0}
20# > Value types behave invariantly.
21a1: A1 = b1 # E: 'B1' not compatible with 'A1'
[invalid-assignment] Object of type `B1` is not assignable to `A1`
23# > any TypedDict type is consistent with Mapping[str, object]
24v1: Mapping[str, object] = b1
26# > A TypedDict type with a required key is not consistent with a TypedDict
27# > type where the same key is a non-required key.
29class A2(TypedDict, total=False):
30 x: int
33class B2(TypedDict):
34 x: int
37b2: B2 = {"x": 0}
38a2: A2 = b2 # E: 'B2' not compatible with 'A2'
[invalid-assignment] Object of type `B2` is not assignable to `A2`
41# > A TypedDict type A is consistent with TypedDict B if A is structurally
42# > compatible with B. This is true if and only if both of these conditions
43# > are satisfied:
44# > 1. For each key in B, A has the corresponding key and the corresponding
45# > value type in A is consistent with the value type in B. For each key in B,
46# > the value type in B is also consistent with the corresponding value type
47# > in A.
48# > 2. For each required key in B, the corresponding key is required in A. For
49# > each non-required key in B, the corresponding key is not required in A.
52class A3(TypedDict):
53 x: int
56class B3(TypedDict):
57 x: int
58 y: int
61b3: B3 = {"x": 0, "y": 0}
62a3: A3 = b3
64a3 = {"x": 0}
65b3 = a3 # E
[invalid-assignment] Object of type `A3` is not assignable to `B3`
68# This should generate an error because it's a literal assignment.
69a3_1: A3 = {"x": 0, "y": 0} # E
[invalid-key] Unknown key "y" for TypedDict `A3`: Unknown key "y"
71# This should not generate an error.
72a3_2 = b3
74# > A TypedDict isn’t consistent with any Dict[...] type.
76d1: dict[str, int] = b3 # E
[invalid-assignment] Object of type `B3` is not assignable to `dict[str, int]`
77d2: dict[str, object] = b3 # E
[invalid-assignment] Object of type `B3` is not assignable to `dict[str, object]`
78d3: dict[Any, Any] = b3 # E
[invalid-assignment] Object of type `B3` is not assignable to `dict[Any, Any]`
80# > A TypedDict with all int values is not consistent with Mapping[str, int].
82m1: Mapping[str, int] = b3 # E
[invalid-assignment] Object of type `B3` is not assignable to `Mapping[str, int]`
83m2: Mapping[str, object] = b3 # OK
84m3: Mapping[str, Any] = b3 # OK
87# Test "get" method.
88UserType1 = TypedDict("UserType1", {"name": str, "age": int}, total=False)
89user1: UserType1 = {"name": "Bob", "age": 40}
91name1: str = user1.get("name", "n/a")
92age1: int = user1.get("age", 42)
94UserType2 = TypedDict("UserType2", {"name": str, "age": int})
95user2: UserType2 = {"name": "Bob", "age": 40}
97name2: str | None = user2.get("name")
99# The spec does not say whether type checkers should adjust the return type of `.get()`
100# to exclude `None` if it is known that the key exists. Either option is acceptable.
101name3: str = user2.get("name") # E?
[invalid-assignment] Object of type `Unknown | None` is not assignable to `str`
103age2: int = user2.get("age", 42)
105age3: int | str = user2.get("age", "42")
107age4: int = user2.get("age", "42") # E?
109# Test nested TypedDicts.
110class Inner1(TypedDict):
111 inner_key: str
114class Inner2(TypedDict):
115 inner_key: Inner1
118class Outer1(TypedDict):
119 outer_key: Inner2
122o1: Outer1 = {"outer_key": {"inner_key": {"inner_key": "hi"}}}
124# This should generate an error because the inner-most value
125# should be a string.
126o2: Outer1 = {"outer_key": {"inner_key": {"inner_key": 1}}} # E
[invalid-argument-type] Invalid argument to key "inner_key" with declared type `str` on TypedDict `Inner1`: value of type `Literal[1]`
129class Inner3(TypedDict):
130 x: int
133class Inner4(TypedDict):
134 x: int
137class Outer2(TypedDict):
138 y: str
139 z: Literal[""] | Inner3
142class Outer3(TypedDict):
143 y: str
144 z: Literal[""] | Inner4
147def func1(td: Outer3):
148 ...
151o3: Outer2 = {"y": "", "z": {"x": 0}}
152o4: Outer3 = o3