Skip to content

Commit 2f6d620

Browse files
authored
Set TypedDicts to always be their declared type as opposed to the type inferred when instantiated (2) (#3099)
[This was implemented originally by @rowillia in #2621. This PR includes a few test updates.] * Allow fields on a TypedDict to be subtypes of their declared types. TypedDicts appear to have explicitly decided not to accept subtypes on fields, but this behavior is counter intuitive. This made it so TypedDicts didn't respect `Any` and caused problems with what should have been ducktype compatible. This also brings TypedDicts more in line with other container types and with how fields on classes behave. ```python from typing import Dict def foo() -> Dict[float, object]: return { 1: 32 } ``` This fixes #2610 * Take suggestion from Jukka to have calls to typed dicts always result in exact declared type See #2621 (comment)
1 parent 50f6215 commit 2f6d620

File tree

2 files changed

+45
-7
lines changed

2 files changed

+45
-7
lines changed

mypy/checkexpr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,12 @@ def check_typeddict_call_with_kwargs(self, callee: TypedDictType,
255255
for (item_name, item_expected_type) in callee.items.items():
256256
item_value = kwargs[item_name]
257257

258-
item_actual_type = self.chk.check_simple_assignment(
258+
self.chk.check_simple_assignment(
259259
lvalue_type=item_expected_type, rvalue=item_value, context=item_value,
260260
msg=messages.INCOMPATIBLE_TYPES,
261261
lvalue_name='TypedDict item "{}"'.format(item_name),
262262
rvalue_name='expression')
263-
items[item_name] = item_actual_type
263+
items[item_name] = item_expected_type
264264

265265
mapping_value_type = join.join_type_list(list(items.values()))
266266
fallback = self.chk.named_generic_type('typing.Mapping',

test-data/unit/check-typeddict.test

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class Point(TypedDict):
152152
z = int # E: Invalid statement in TypedDict definition; expected "field_name: field_type"
153153

154154
p = Point(x=42, y=1337, z='whatever')
155-
reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.object])'
155+
reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=Any, _fallback=typing.Mapping[builtins.str, Any])'
156156
[builtins fixtures/dict.pyi]
157157

158158
[case testCanCreateTypedDictTypeWithUnderscoreItemName]
@@ -286,6 +286,44 @@ def as_mapping(p: Point) -> Mapping[str, str]:
286286
return p # E: Incompatible return value type (got "Point", expected Mapping[str, str])
287287
[builtins fixtures/dict.pyi]
288288

289+
[case testTypedDictAcceptsIntForFloatDuckTypes]
290+
from mypy_extensions import TypedDict
291+
from typing import Any, Mapping
292+
Point = TypedDict('Point', {'x': float, 'y': float})
293+
def create_point() -> Point:
294+
return Point(x=1, y=2)
295+
reveal_type(Point(x=1, y=2)) # E: Revealed type is 'TypedDict(x=builtins.float, y=builtins.float, _fallback=typing.Mapping[builtins.str, builtins.float])'
296+
[builtins fixtures/dict.pyi]
297+
298+
[case testTypedDictDoesNotAcceptsFloatForInt]
299+
from mypy_extensions import TypedDict
300+
from typing import Any, Mapping
301+
Point = TypedDict('Point', {'x': int, 'y': int})
302+
def create_point() -> Point:
303+
return Point(x=1.2, y=2.5)
304+
[out]
305+
main:5: error: Incompatible types (expression has type "float", TypedDict item "x" has type "int")
306+
main:5: error: Incompatible types (expression has type "float", TypedDict item "y" has type "int")
307+
[builtins fixtures/dict.pyi]
308+
309+
[case testTypedDictAcceptsAnyType]
310+
from mypy_extensions import TypedDict
311+
from typing import Any, Mapping
312+
Point = TypedDict('Point', {'x': float, 'y': float})
313+
def create_point(something: Any) -> Point:
314+
return Point({
315+
'x': something.x,
316+
'y': something.y
317+
})
318+
[builtins fixtures/dict.pyi]
319+
320+
[case testTypedDictValueTypeContext]
321+
from mypy_extensions import TypedDict
322+
from typing import List
323+
D = TypedDict('D', {'x': List[int]})
324+
reveal_type(D(x=[])) # E: Revealed type is 'TypedDict(x=builtins.list[builtins.int], _fallback=typing.Mapping[builtins.str, builtins.list[builtins.int]])'
325+
[builtins fixtures/dict.pyi]
326+
289327
-- TODO: Fix mypy stubs so that the following passes in the test suite
290328
--[case testCanConvertTypedDictToAnySuperclassOfMapping]
291329
--from mypy_extensions import TypedDict
@@ -341,9 +379,9 @@ CellWithObject = TypedDict('CellWithObject', {'value': object, 'meta': object})
341379
c1 = CellWithInt(value=1, meta=42)
342380
c2 = CellWithObject(value=2, meta='turtle doves')
343381
joined_cells = [c1, c2]
344-
reveal_type(c1) # E: Revealed type is 'TypedDict(value=builtins.int, meta=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
345-
reveal_type(c2) # E: Revealed type is 'TypedDict(value=builtins.int, meta=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.object])'
346-
reveal_type(joined_cells) # E: Revealed type is 'builtins.list[TypedDict(value=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
382+
reveal_type(c1) # E: Revealed type is 'TypedDict(value=builtins.object, meta=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.object])'
383+
reveal_type(c2) # E: Revealed type is 'TypedDict(value=builtins.object, meta=builtins.object, _fallback=typing.Mapping[builtins.str, builtins.object])'
384+
reveal_type(joined_cells) # E: Revealed type is 'builtins.list[TypedDict(value=builtins.object, _fallback=typing.Mapping[builtins.str, builtins.object])]'
347385
[builtins fixtures/dict.pyi]
348386

349387
[case testJoinOfDisjointTypedDictsIsEmptyTypedDict]
@@ -354,7 +392,7 @@ d1 = Point(x=0, y=0)
354392
d2 = Cell(value='pear tree')
355393
joined_dicts = [d1, d2]
356394
reveal_type(d1) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
357-
reveal_type(d2) # E: Revealed type is 'TypedDict(value=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.str])'
395+
reveal_type(d2) # E: Revealed type is 'TypedDict(value=builtins.object, _fallback=typing.Mapping[builtins.str, builtins.object])'
358396
reveal_type(joined_dicts) # E: Revealed type is 'builtins.list[TypedDict(_fallback=typing.Mapping[builtins.str, <uninhabited>])]'
359397
[builtins fixtures/dict.pyi]
360398

0 commit comments

Comments
 (0)