Skip to content

Commit 465bf00

Browse files
author
Roy Williams
committed
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
1 parent a207754 commit 465bf00

File tree

2 files changed

+32
-9
lines changed

2 files changed

+32
-9
lines changed

mypy/subtypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
211211
if not left.names_are_wider_than(right):
212212
return False
213213
for (_, l, r) in left.zip(right):
214-
if not is_equivalent(l, r, self.check_type_parameter):
214+
if not is_subtype(l, r, self.check_type_parameter):
215215
return False
216216
# (NOTE: Fallbacks don't matter.)
217217
return True

test-data/unit/check-typeddict.test

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,6 @@ def convert(op: ObjectPoint) -> Point:
8888
return op # E: Incompatible return value type (got "ObjectPoint", expected "Point")
8989
[builtins fixtures/dict.pyi]
9090

91-
[case testCannotConvertTypedDictToSimilarTypedDictWithWiderItemTypes]
92-
from mypy_extensions import TypedDict
93-
Point = TypedDict('Point', {'x': int, 'y': int})
94-
ObjectPoint = TypedDict('ObjectPoint', {'x': object, 'y': object})
95-
def convert(p: Point) -> ObjectPoint:
96-
return p # E: Incompatible return value type (got "Point", expected "ObjectPoint")
97-
[builtins fixtures/dict.pyi]
98-
9991
[case testCannotConvertTypedDictToSimilarTypedDictWithIncompatibleItemTypes]
10092
from mypy_extensions import TypedDict
10193
Point = TypedDict('Point', {'x': int, 'y': int})
@@ -136,6 +128,37 @@ def as_mapping(p: Point) -> Mapping[str, str]:
136128
return p # E: Incompatible return value type (got "Point", expected Mapping[str, str])
137129
[builtins fixtures/dict.pyi]
138130

131+
[case testTypedDictAcceptsIntForFloatDuckTypes]
132+
from mypy_extensions import TypedDict
133+
from typing import Any, Mapping
134+
Point = TypedDict('Point', {'x': float, 'y': float})
135+
def create_point() -> Point:
136+
return Point(x=1, y=2)
137+
[builtins fixtures/dict.pyi]
138+
139+
[case testTypedDictDoesNotAcceptsFloatForInt]
140+
from mypy_extensions import TypedDict
141+
from typing import Any, Mapping
142+
Point = TypedDict('Point', {'x': int, 'y': int})
143+
def create_point() -> Point:
144+
return Point(x=1.2, y=2.5)
145+
[out]
146+
main:5: error: Incompatible return value type (got "TypedDict(x=float, y=float)", expected "Point")
147+
main:5: error: Incompatible types (expression has type "float", TypedDict item "x" has type "int")
148+
main:5: error: Incompatible types (expression has type "float", TypedDict item "y" has type "int")
149+
[builtins fixtures/dict.pyi]
150+
151+
[case testTypedDictAcceptsAnyType]
152+
from mypy_extensions import TypedDict
153+
from typing import Any, Mapping
154+
Point = TypedDict('Point', {'x': float, 'y': float})
155+
def create_point(something: Any) -> Point:
156+
return Point({
157+
'x': something.x,
158+
'y': something.y
159+
})
160+
[builtins fixtures/dict.pyi]
161+
139162
-- TODO: Fix mypy stubs so that the following passes in the test suite
140163
--[case testCanConvertTypedDictToAnySuperclassOfMapping]
141164
--from mypy_extensions import TypedDict

0 commit comments

Comments
 (0)