@@ -2254,6 +2254,133 @@ def g_list_int(l: list[int]):
22542254
22552255 self .assertRaises (RuntimeError , g .dispatch (list [int ]))
22562256
2257+ def test_pep585_method_basic (self ):
2258+ class A :
2259+ @functools .singledispatchmethod
2260+ def g (obj ):
2261+ return "base"
2262+ def g_list_int (li ):
2263+ return "list of ints"
2264+
2265+ a = A ()
2266+ a .g .register (list [int ], A .g_list_int )
2267+ self .assertEqual (a .g ([1 ]), "list of ints" )
2268+ self .assertIs (a .g .dispatch (list [int ]), A .g_list_int )
2269+
2270+ def test_pep585_method_annotation (self ):
2271+ class A :
2272+ @functools .singledispatchmethod
2273+ def g (obj ):
2274+ return "base"
2275+ # previously this failed with: 'not a class'
2276+ @g .register
2277+ def g_list_int (li : list [int ]):
2278+ return "list of ints"
2279+ a = A ()
2280+ self .assertEqual (a .g ([1 ,2 ,3 ]), "list of ints" )
2281+ self .assertIs (g .dispatch (tuple [int ]), A .g_list_int )
2282+
2283+ def test_pep585_method_all_must_match (self ):
2284+ class A :
2285+ @functools .singledispatch
2286+ def g (obj ):
2287+ return "base"
2288+ def g_list_int (li ):
2289+ return "list of ints"
2290+ def g_list_not_ints (l ):
2291+ # should only trigger if list doesnt match `list[int]`
2292+ # ie. at least one element is not an int
2293+ return "!all(int)"
2294+
2295+ a = A ()
2296+ a .g .register (list [int ], A .g_list_int )
2297+ a .g .register (list , A .g_list_not_ints )
2298+
2299+ self .assertEqual (a .g ([1 ,2 ,3 ]), "list of ints" )
2300+ self .assertEqual (a .g ([1 ,2 ,3 , "hello" ]), "!all(int)" )
2301+ self .assertEqual (a .g ([3.14 ]), "!all(int)" )
2302+
2303+ self .assertIs (a .g .dispatch (list [int ]), A .g_list_int )
2304+ self .assertIs (a .g .dispatch (list [str ]), A .g_list_not_ints )
2305+ self .assertIs (a .g .dispatch (list [float ]), A .g_list_not_ints )
2306+ self .assertIs (a .g .dispatch (list [int | str ]), A .g_list_not_ints )
2307+
2308+ def test_pep585_method_specificity (self ):
2309+ class A :
2310+ @functools .singledispatch
2311+ def g (obj ):
2312+ return "base"
2313+ @g .register
2314+ def g_list (l : list ):
2315+ return "basic list"
2316+ @g .register
2317+ def g_list_int (li : list [int ]):
2318+ return "int"
2319+ @g .register
2320+ def g_list_str (ls : list [str ]):
2321+ return "str"
2322+ @g .register
2323+ def g_list_mixed_int_str (lmis :list [int | str ]):
2324+ return "int|str"
2325+ @g .register
2326+ def g_list_mixed_int_float (lmif : list [int | float ]):
2327+ return "int|float"
2328+ @g .register
2329+ def g_list_mixed_int_float_str (lmifs : list [int | float | str ]):
2330+ return "int|float|str"
2331+
2332+ a = A ()
2333+
2334+ # this matches list, list[int], list[int|str], list[int|float|str], list[int|...|...|...|...]
2335+ # but list[int] is the most specific, so that is correct
2336+ self .assertEqual (a .g ([1 ,2 ,3 ]), "int" )
2337+
2338+ # this cannot match list[int] because of the string
2339+ # it does match list[int|float|str] but this is incorrect because,
2340+ # the most specific is list[int|str]
2341+ self .assertEqual (a .g ([1 ,2 ,3 , "hello" ]), "int|str" )
2342+
2343+ # list[float] is not mapped so,
2344+ # list[int|float] is the most specific
2345+ self .assertEqual (a .g ([3.14 ]), "int|float" )
2346+
2347+ self .assertIs (a .g .dispatch (list [int ]), A .g_list_int )
2348+ self .assertIs (a .g .dispatch (list [float ]), A .g_list_mixed_int_float )
2349+ self .assertIs (a .g .dispatch (list [int | str ]), A .g_list_mixed_int_str )
2350+
2351+ def test_pep585_method_ambiguous (self ):
2352+ class A :
2353+ @functools .singledispatch
2354+ def g (obj ):
2355+ return "base"
2356+ @g .register
2357+ def g_list_int_float (l : list [int | float ]):
2358+ return "int|float"
2359+ @g .register
2360+ def g_list_int_str (l : list [int | str ]):
2361+ return "int|str"
2362+ @g .register
2363+ def g_list_int (l : list [int ]):
2364+ return "int only"
2365+
2366+ a = A ()
2367+
2368+ self .assertEqual (a .g ([3.1 ]), "int|float" ) # floats only
2369+ self .assertEqual (a .g (["hello" ]), "int|str" ) # strings only
2370+ self .assertEqual (a .g ([3.14 , 1 ]), "int|float" ) # ints and floats
2371+ self .assertEqual (a .g (["hello" , 1 ]), "int|str" ) # ints and strings
2372+
2373+ self .assertIs (a .g .dispatch (list [int ]), A .g_list_int )
2374+ self .assertIs (a .g .dispatch (list [str ]), A .g_list_int_str )
2375+ self .assertIs (a .g .dispatch (list [float ]), A .g_list_int_float )
2376+ self .assertIs (a .g .dispatch (list [int | str ]), A .g_list_int_str )
2377+ self .assertIs (a .g .dispatch (list [int | float ]), A .g_list_int_float )
2378+
2379+ # these should fail because it's unclear which target is "correct"
2380+ self .assertRaises (RuntimeError , a .g ([1 ]))
2381+
2382+ self .assertRaises (RuntimeError , a .g .dispatch (list [int ]))
2383+
22572384 def test_simple_overloads (self ):
22582385 @functools .singledispatch
22592386 def g (obj ):
0 commit comments