From: Carey Metcalfe Date: Wed, 15 Apr 2026 13:24:28 +0000 (-0400) Subject: gh-143886: Ensure function annotations are returned in order of definition (#143888) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=cb339d3c9eaeedb6e541a36560b8a21567158e25;p=thirdparty%2FPython%2Fcpython.git gh-143886: Ensure function annotations are returned in order of definition (#143888) 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. --- diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index efa85b564f7c..a8ee7d119e4b 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -3358,6 +3358,21 @@ class TestSingleDispatch(unittest.TestCase): with self.assertRaisesRegex(TypeError, msg): A().t(a=1) + def test_positional_only_argument(self): + @functools.singledispatch + def f(arg, /, extra): + return "base" + @f.register + def f_int(arg: int, /, extra: str): + return "int" + @f.register + def f_str(arg: str, /, extra: int): + return "str" + + self.assertEqual(f(None, "extra"), "base") + self.assertEqual(f(1, "extra"), "int") + self.assertEqual(f("s", "extra"), "str") + def test_union(self): @functools.singledispatch def f(arg): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c6f08ff8a052..9c0172f6ba7f 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -7269,6 +7269,13 @@ class GetTypeHintsTests(BaseTestCase): self.assertEqual(TD.__annotations__, {'a': EqualToForwardRef('UniqueT', owner=TD, module=TD.__module__)}) self.assertEqual(get_type_hints(TD), {'a': TD.__type_params__[0]}) + def test_get_type_hints_order(self): + """Ensure that the order of function annotations matches the order they're defined""" + def f(positional: int, /, normal: str, *args: bytes, kwarg: list, **kwargs: bool) -> tuple: + pass + + self.assertEqual(list(gth(f)), ["positional", "normal", "args", "kwarg", "kwargs", "return"]) + class GetUtilitiesTestCase(TestCase): def test_get_origin(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst new file mode 100644 index 000000000000..fe4835ec28cf --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst @@ -0,0 +1,3 @@ +Reorder function annotations so positional-only arguments are returned +before other arguments. This fixes how :func:`functools.singledispatch` +registers functions with positional-only arguments. diff --git a/Python/codegen.c b/Python/codegen.c index aca590d055f4..a0e5d9f65941 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -1130,10 +1130,10 @@ codegen_annotations_in_scope(compiler *c, location loc, Py_ssize_t *annotations_len) { RETURN_IF_ERROR( - codegen_argannotations(c, args->args, annotations_len, loc)); + codegen_argannotations(c, args->posonlyargs, annotations_len, loc)); RETURN_IF_ERROR( - codegen_argannotations(c, args->posonlyargs, annotations_len, loc)); + codegen_argannotations(c, args->args, annotations_len, loc)); if (args->vararg && args->vararg->annotation) { RETURN_IF_ERROR(