|
| 1 | +from __future__ import annotations |
1 | 2 | import abc |
2 | 3 | import builtins |
3 | 4 | import collections |
@@ -2136,6 +2137,123 @@ def cached_staticmeth(x, y): |
2136 | 2137 |
|
2137 | 2138 |
|
2138 | 2139 | 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 | + |
2139 | 2257 | def test_simple_overloads(self): |
2140 | 2258 | @functools.singledispatch |
2141 | 2259 | def g(obj): |
|
0 commit comments