https://github.com/python/cpython/commit/cb339d3c9eaeedb6e541a36560b8a21567158e25
commit: cb339d3c9eaeedb6e541a36560b8a21567158e25
branch: main
author: Carey Metcalfe <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-04-15T06:24:28-07:00
summary:

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.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst
M Lib/test/test_functools.py
M Lib/test/test_typing.py
M Python/codegen.c

diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index efa85b564f7cdf..a8ee7d119e4bc6 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -3358,6 +3358,21 @@ def t(self, *args, **kwargs):
         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 c6f08ff8a052ab..9c0172f6ba7f23 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -7269,6 +7269,13 @@ class TD[UniqueT](TypedDict):
         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 00000000000000..fe4835ec28cfd5
--- /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 aca590d055f466..a0e5d9f659415e 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(

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to