Skip to content

Commit 7bccaf6

Browse files
committed
Ensure function annotations are returned in order of definition
Previously, when getting type annotations of a function, normal arguments were returned before positional-only ones in the dictionary. Since `functools.singledispatch` relies on this ordering being correct to dispatch based on the type of the first argument, this issue was causing incorrect registrations for functions with positional-only arguments. This commit updates how annotations are generated so that positional-only arguments are generated and added to the dictionary before normal arguments.
1 parent c461aa9 commit 7bccaf6

File tree

4 files changed

+27
-2
lines changed

4 files changed

+27
-2
lines changed

Lib/test/test_functools.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3236,6 +3236,21 @@ def t(self, *args, **kwargs):
32363236
with self.assertRaisesRegex(TypeError, msg):
32373237
A().t(a=1)
32383238

3239+
def test_positional_only_argument(self):
3240+
@functools.singledispatch
3241+
def f(arg, /, extra):
3242+
return "base"
3243+
@f.register
3244+
def f_int(arg: int, /, extra: str):
3245+
return "int"
3246+
@f.register
3247+
def f_str(arg: str, /, extra: int):
3248+
return "str"
3249+
3250+
self.assertEqual(f(None, "extra"), "base")
3251+
self.assertEqual(f(1, "extra"), "int")
3252+
self.assertEqual(f("s", "extra"), "str")
3253+
32393254
def test_union(self):
32403255
@functools.singledispatch
32413256
def f(arg):

Lib/test/test_typing.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7204,6 +7204,13 @@ class TD[UniqueT](TypedDict):
72047204
self.assertEqual(TD.__annotations__, {'a': EqualToForwardRef('UniqueT', owner=TD, module=TD.__module__)})
72057205
self.assertEqual(get_type_hints(TD), {'a': TD.__type_params__[0]})
72067206

7207+
def test_get_type_hints_order(self):
7208+
"""Ensure that the order of function annotations matches the order they're defined"""
7209+
def f(positional: int, /, normal: str, *args: bytes, kwarg: list, **kwargs: bool) -> tuple:
7210+
pass
7211+
7212+
self.assertEqual(list(gth(f)), ["positional", "normal", "args", "kwarg", "kwargs", "return"])
7213+
72077214

72087215
class GetUtilitiesTestCase(TestCase):
72097216
def test_get_origin(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Reorder function annotations so positional-only arguments are returned
2+
before other arguments. This fixes how :func:`functools.singledispatch``
3+
registers functions with positional-only arguments.

Python/codegen.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,10 +1093,10 @@ codegen_annotations_in_scope(compiler *c, location loc,
10931093
Py_ssize_t *annotations_len)
10941094
{
10951095
RETURN_IF_ERROR(
1096-
codegen_argannotations(c, args->args, annotations_len, loc));
1096+
codegen_argannotations(c, args->posonlyargs, annotations_len, loc));
10971097

10981098
RETURN_IF_ERROR(
1099-
codegen_argannotations(c, args->posonlyargs, annotations_len, loc));
1099+
codegen_argannotations(c, args->args, annotations_len, loc));
11001100

11011101
if (args->vararg && args->vararg->annotation) {
11021102
RETURN_IF_ERROR(

0 commit comments

Comments
 (0)