Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,19 +400,14 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
if not left.names_are_wider_than(right):
return False
for name, l, r in left.zip(right):
if not is_equivalent(l, r,
ignore_type_params=self.ignore_type_params):
if not is_subtype(l, r,
ignore_type_params=self.ignore_type_params):
return False
# Non-required key is not compatible with a required key since
# indexing may fail unexpectedly if a required key is missing.
# Required key is not compatible with a non-required key since
# the prior doesn't support 'del' but the latter should support
# it.
#
# NOTE: 'del' support is currently not implemented (#3550). We
# don't want to have to change subtyping after 'del' support
# lands so here we are anticipating that change.
if (name in left.required_keys) != (name in right.required_keys):
# Required key *is* compatible with a non-required key.
if (name in right.required_keys) and (name not in left.required_keys):
return False
# (NOTE: Fallbacks don't matter.)
return True
Expand Down
42 changes: 33 additions & 9 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,6 @@ def convert(op: ObjectPoint) -> Point:
return op # E: Incompatible return value type (got "ObjectPoint", expected "Point")
[builtins fixtures/dict.pyi]

[case testCannotConvertTypedDictToSimilarTypedDictWithWiderItemTypes]
from mypy_extensions import TypedDict
Point = TypedDict('Point', {'x': int, 'y': int})
ObjectPoint = TypedDict('ObjectPoint', {'x': object, 'y': object})
def convert(p: Point) -> ObjectPoint:
return p # E: Incompatible return value type (got "Point", expected "ObjectPoint")
[builtins fixtures/dict.pyi]

[case testCannotConvertTypedDictToSimilarTypedDictWithIncompatibleItemTypes]
from mypy_extensions import TypedDict
Point = TypedDict('Point', {'x': int, 'y': int})
Expand Down Expand Up @@ -1121,11 +1113,43 @@ c: C
fb(b)
fc(c)
fb(c)
fb(a) # E: Argument 1 to "fb" has incompatible type "A"; expected "B"
fb(a)
fa(b) # E: Argument 1 to "fa" has incompatible type "B"; expected "A"
fc(b) # E: Argument 1 to "fc" has incompatible type "B"; expected "C"
[builtins fixtures/dict.pyi]

[case testTypedDictSubtypingWithOptional]
# flags: --strict-optional
from typing import Optional
from mypy_extensions import TypedDict
A = TypedDict('A', {'x': int})
B = TypedDict('B', {'x': Optional[int]})
def fa(a: A) -> None: pass
def fb(b: B) -> None: pass
a: A
b: B
fb(a)
fa(b) # E: Argument 1 to "fa" has incompatible type "B"; expected "A"
[builtins fixtures/dict.pyi]

[case testTypedDictSubtypingWithSubclass]
# flags: --strict-optional
from typing import Optional
from mypy_extensions import TypedDict
class Foo: pass

class Bar(Foo): pass

A = TypedDict('A', {'x': Bar})
B = TypedDict('B', {'x': Foo})
def fa(a: A) -> None: pass
def fb(b: B) -> None: pass
a: A
b: B
fb(a)
fa(b) # E: Argument 1 to "fa" has incompatible type "B"; expected "A"
[builtins fixtures/dict.pyi]

[case testTypedDictJoinWithTotalFalse]
from typing import TypeVar
from mypy_extensions import TypedDict
Expand Down