Skip to content

Commit c344496

Browse files
JukkaLgvanrossum
authored andcommitted
Support overloading with TypedDict (#3612)
Fix #3609.
1 parent 0b4fde6 commit c344496

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

mypy/checkexpr.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,12 @@ def overload_arg_similarity(actual: Type, formal: Type) -> int:
26752675
return overload_arg_similarity(actual.ret_type, formal.item)
26762676
else:
26772677
return 0
2678+
if isinstance(actual, TypedDictType):
2679+
if isinstance(formal, TypedDictType):
2680+
# Don't support overloading based on the keys or value types of a TypedDict since
2681+
# that would be complicated and probably only marginally useful.
2682+
return 2
2683+
return overload_arg_similarity(actual.fallback, formal)
26782684
if isinstance(formal, Instance):
26792685
if isinstance(actual, CallableType):
26802686
actual = actual.fallback

mypy/meet.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ class C(A, B): ...
8484
t = t.erase_to_union_or_bound()
8585
if isinstance(s, TypeVarType):
8686
s = s.erase_to_union_or_bound()
87+
if isinstance(t, TypedDictType):
88+
t = t.as_anonymous().fallback
89+
if isinstance(s, TypedDictType):
90+
s = s.as_anonymous().fallback
8791
if isinstance(t, Instance):
8892
if isinstance(s, Instance):
8993
# Consider two classes non-disjoint if one is included in the mro

test-data/unit/check-typeddict.test

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,125 @@ X = TypedDict('Y', {'x': int}) # E: First argument 'Y' to TypedDict() does not
10861086
[builtins fixtures/dict.pyi]
10871087

10881088

1089+
-- Overloading
1090+
1091+
[case testTypedDictOverloading]
1092+
from typing import overload, Iterable
1093+
from mypy_extensions import TypedDict
1094+
1095+
A = TypedDict('A', {'x': int})
1096+
1097+
@overload
1098+
def f(x: Iterable[str]) -> str: ...
1099+
@overload
1100+
def f(x: int) -> int: ...
1101+
def f(x): pass
1102+
1103+
a: A
1104+
reveal_type(f(a)) # E: Revealed type is 'builtins.str'
1105+
reveal_type(f(1)) # E: Revealed type is 'builtins.int'
1106+
[builtins fixtures/dict.pyi]
1107+
[typing fixtures/typing-full.pyi]
1108+
1109+
[case testTypedDictOverloading2]
1110+
from typing import overload, Iterable
1111+
from mypy_extensions import TypedDict
1112+
1113+
A = TypedDict('A', {'x': int})
1114+
1115+
@overload
1116+
def f(x: Iterable[int]) -> None: ...
1117+
@overload
1118+
def f(x: int) -> None: ...
1119+
def f(x): pass
1120+
1121+
a: A
1122+
f(a) # E: Argument 1 to "f" has incompatible type "A"; expected Iterable[int]
1123+
[builtins fixtures/dict.pyi]
1124+
[typing fixtures/typing-full.pyi]
1125+
1126+
[case testTypedDictOverloading3]
1127+
from typing import overload
1128+
from mypy_extensions import TypedDict
1129+
1130+
A = TypedDict('A', {'x': int})
1131+
1132+
@overload
1133+
def f(x: str) -> None: ...
1134+
@overload
1135+
def f(x: int) -> None: ...
1136+
def f(x): pass
1137+
1138+
a: A
1139+
f(a) # E: No overload variant of "f" matches argument types [TypedDict(x=builtins.int, _fallback=__main__.A)]
1140+
[builtins fixtures/dict.pyi]
1141+
[typing fixtures/typing-full.pyi]
1142+
1143+
[case testTypedDictOverloading4]
1144+
from typing import overload
1145+
from mypy_extensions import TypedDict
1146+
1147+
A = TypedDict('A', {'x': int})
1148+
B = TypedDict('B', {'x': str})
1149+
1150+
@overload
1151+
def f(x: A) -> int: ...
1152+
@overload
1153+
def f(x: int) -> str: ...
1154+
def f(x): pass
1155+
1156+
a: A
1157+
b: B
1158+
reveal_type(f(a)) # E: Revealed type is 'builtins.int'
1159+
reveal_type(f(1)) # E: Revealed type is 'builtins.str'
1160+
f(b) # E: Argument 1 to "f" has incompatible type "B"; expected "A"
1161+
[builtins fixtures/dict.pyi]
1162+
[typing fixtures/typing-full.pyi]
1163+
1164+
[case testTypedDictOverloading5]
1165+
from typing import overload
1166+
from mypy_extensions import TypedDict
1167+
1168+
A = TypedDict('A', {'x': int})
1169+
B = TypedDict('B', {'y': str})
1170+
C = TypedDict('C', {'y': int})
1171+
1172+
@overload
1173+
def f(x: A) -> None: ...
1174+
@overload
1175+
def f(x: B) -> None: ...
1176+
def f(x): pass
1177+
1178+
a: A
1179+
b: B
1180+
c: C
1181+
f(a)
1182+
f(b)
1183+
f(c) # E: Argument 1 to "f" has incompatible type "C"; expected "A"
1184+
[builtins fixtures/dict.pyi]
1185+
[typing fixtures/typing-full.pyi]
1186+
1187+
[case testTypedDictOverloading6]
1188+
from typing import overload
1189+
from mypy_extensions import TypedDict
1190+
1191+
A = TypedDict('A', {'x': int})
1192+
B = TypedDict('B', {'y': str})
1193+
1194+
@overload
1195+
def f(x: A) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
1196+
@overload
1197+
def f(x: B) -> str: ...
1198+
def f(x): pass
1199+
1200+
a: A
1201+
b: B
1202+
reveal_type(f(a)) # E: Revealed type is 'Any'
1203+
reveal_type(f(b)) # E: Revealed type is 'Any'
1204+
[builtins fixtures/dict.pyi]
1205+
[typing fixtures/typing-full.pyi]
1206+
1207+
10891208
-- Special cases
10901209

10911210
[case testForwardReferenceInTypedDict]

0 commit comments

Comments
 (0)