Skip to content

Commit 3d72899

Browse files
committed
TDD: write tests first
tests that match the desired behavior for PEP-585 types passed to singledispatch
1 parent 45a3ab5 commit 3d72899

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

Lib/test/test_functools.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from __future__ import annotations
12
import abc
23
import builtins
34
import collections
@@ -2136,6 +2137,123 @@ def cached_staticmeth(x, y):
21362137

21372138

21382139
class TestSingleDispatch(unittest.TestCase):
2140+
2141+
def test_pep585_basic(self):
2142+
@functools.singledispatch
2143+
def g(obj):
2144+
return "base"
2145+
def g_list_int(li):
2146+
return "list of ints"
2147+
# previously this failed with: 'not a class'
2148+
g.register(list[int], g_list_int)
2149+
self.assertEqual(g([1]), "list of ints")
2150+
self.assertIs(g.dispatch(list[int]), g_list_int)
2151+
2152+
def test_pep585_annotation(self):
2153+
@functools.singledispatch
2154+
def g(obj):
2155+
return "base"
2156+
# previously this failed with: 'not a class'
2157+
@g.register
2158+
def g_list_int(li: list[int]):
2159+
return "list of ints"
2160+
self.assertEqual(g([1,2,3]), "list of ints")
2161+
self.assertIs(g.dispatch(tuple[int]), g_list_int)
2162+
2163+
def test_pep585_all_must_match(self):
2164+
@functools.singledispatch
2165+
def g(obj):
2166+
return "base"
2167+
def g_list_int(li):
2168+
return "list of ints"
2169+
def g_list_not_ints(l):
2170+
# should only trigger if list doesnt match `list[int]`
2171+
# ie. at least one element is not an int
2172+
return "!all(int)"
2173+
2174+
g.register(list[int], g_list_int)
2175+
g.register(list, g_list_not_ints)
2176+
2177+
self.assertEqual(g([1,2,3]), "list of ints")
2178+
self.assertEqual(g([1,2,3, "hello"]), "!all(int)")
2179+
self.assertEqual(g([3.14]), "!all(int)")
2180+
2181+
self.assertIs(g.dispatch(list[int]), g_list_int)
2182+
self.assertIs(g.dispatch(list[str]), g_list_not_ints)
2183+
self.assertIs(g.dispatch(list[float]), g_list_not_ints)
2184+
self.assertIs(g.dispatch(list[int|str]), g_list_not_ints)
2185+
2186+
def test_pep585_specificity(self):
2187+
@functools.singledispatch
2188+
def g(obj):
2189+
return "base"
2190+
@g.register
2191+
def g_list(l: list):
2192+
return "basic list"
2193+
@g.register
2194+
def g_list_int(li: list[int]):
2195+
return "int"
2196+
@g.register
2197+
def g_list_str(ls: list[str]):
2198+
return "str"
2199+
@g.register
2200+
def g_list_mixed_int_str(lmis:list[int|str]):
2201+
return "int|str"
2202+
@g.register
2203+
def g_list_mixed_int_float(lmif: list[int|float]):
2204+
return "int|float"
2205+
@g.register
2206+
def g_list_mixed_int_float_str(lmifs: list[int|float|str]):
2207+
return "int|float|str"
2208+
2209+
# this matches list, list[int], list[int|str], list[int|float|str], list[int|...|...|...|...]
2210+
# but list[int] is the most specific, so that is correct
2211+
self.assertEqual(g([1,2,3]), "int")
2212+
2213+
# this cannot match list[int] because of the string
2214+
# it does match list[int|float|str] but this is incorrect because,
2215+
# the most specific is list[int|str]
2216+
self.assertEqual(g([1,2,3, "hello"]), "int|str")
2217+
2218+
# list[float] is not mapped so,
2219+
# list[int|float] is the most specific
2220+
self.assertEqual(g([3.14]), "int|float")
2221+
2222+
self.assertIs(g.dispatch(list[int]), g_list_int)
2223+
self.assertIs(g.dispatch(list[float]), g_list_mixed_int_float)
2224+
self.assertIs(g.dispatch(list[int|str]), g_list_mixed_int_str)
2225+
2226+
def test_pep585_ambiguous(self):
2227+
@functools.singledispatch
2228+
def g(obj):
2229+
return "base"
2230+
@g.register
2231+
def g_list_int_float(l: list[int|float]):
2232+
return "int|float"
2233+
@g.register
2234+
def g_list_int_str(l: list[int|str]):
2235+
return "int|str"
2236+
@g.register
2237+
def g_list_int(l: list[int]):
2238+
return "int only"
2239+
2240+
self.assertEqual(g([3.1]), "int|float") # floats only
2241+
self.assertEqual(g(["hello"]), "int|str") # strings only
2242+
self.assertEqual(g([3.14, 1]), "int|float") # ints and floats
2243+
self.assertEqual(g(["hello", 1]), "int|str") # ints and strings
2244+
2245+
self.assertIs(g.dispatch(list[int]), g_list_int)
2246+
self.assertIs(g.dispatch(list[str]), g_list_int_str)
2247+
self.assertIs(g.dispatch(list[float]), g_list_int_float)
2248+
self.assertIs(g.dispatch(list[int|str]), g_list_int_str)
2249+
self.assertIs(g.dispatch(list[int|float]), g_list_int_float)
2250+
2251+
# these should fail because it's unclear which target is "correct"
2252+
with self.assertRaises(RuntimeError):
2253+
g([1])
2254+
2255+
self.assertRaises(RuntimeError, g.dispatch(list[int]))
2256+
21392257
def test_simple_overloads(self):
21402258
@functools.singledispatch
21412259
def g(obj):

0 commit comments

Comments
 (0)