]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-151665: Fix inspect.signature() on type alias and type parameter evaluators (...
authorTimofei <128279579+deadlovelll@users.noreply.github.com>
Sun, 21 Jun 2026 22:08:01 +0000 (01:08 +0300)
committerGitHub <noreply@github.com>
Sun, 21 Jun 2026 22:08:01 +0000 (15:08 -0700)
Lib/test/test_type_params.py
Misc/NEWS.d/next/Core_and_Builtins/2026-06-21-12-55-18.gh-issue-151665.82fmzx.rst [new file with mode: 0644]
Python/codegen.c

index 84c1b9541367363685f25d6131dddb491ef853eb..b6588269e1d0c657201025ca6353fc2c5a349601 100644 (file)
@@ -1,4 +1,5 @@
 import annotationlib
+import inspect
 import textwrap
 import types
 import unittest
@@ -1446,6 +1447,30 @@ class TestEvaluateFunctions(unittest.TestCase):
                 self.assertIs(annotationlib.call_evaluate_function(case, annotationlib.Format.FORWARDREF), int)
                 self.assertEqual(annotationlib.call_evaluate_function(case, annotationlib.Format.STRING), 'int')
 
+    def test_signature(self):
+        # gh-151665: the ".format" parameter of compiler-generated evaluators
+        # used to break inspect.signature(). It should show up as "format".
+        type Alias = int
+        def f[T: int = int, **P = int, *Ts = int](): pass
+        T, P, Ts = f.__type_params__
+        def g[U: (int, str)](): pass
+        U, = g.__type_params__
+        cases = [
+            Alias.evaluate_value,
+            T.evaluate_bound,
+            T.evaluate_default,
+            P.evaluate_default,
+            Ts.evaluate_default,
+            U.evaluate_constraints,
+        ]
+        for case in cases:
+            with self.subTest(case=case):
+                sig = inspect.signature(case)
+                self.assertEqual(str(sig), '(format=1, /)')
+                param, = sig.parameters.values()
+                self.assertEqual(param.name, 'format')
+                self.assertIs(param.kind, inspect.Parameter.POSITIONAL_ONLY)
+
     def test_constraints(self):
         def f[T: (int, str)](): pass
         T, = f.__type_params__
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-21-12-55-18.gh-issue-151665.82fmzx.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-21-12-55-18.gh-issue-151665.82fmzx.rst
new file mode 100644 (file)
index 0000000..d08a122
--- /dev/null
@@ -0,0 +1,2 @@
+:func:`inspect.signature` now works on the lazy evaluators of type aliases
+and type parameters instead of raising :exc:`ValueError`.
index 205c49cff1827c49b38b72e9696cee76a929f9a7..e65c308617df5eed739c4136dd9b8a26eaa81e82 100644 (file)
@@ -733,14 +733,8 @@ codegen_setup_annotations_scope(compiler *c, location loc,
 }
 
 static int
-codegen_leave_annotations_scope(compiler *c, location loc)
+codegen_rename_annotations_format_param(PyCodeObject *co)
 {
-    ADDOP_IN_SCOPE(c, loc, RETURN_VALUE);
-    PyCodeObject *co = _PyCompile_OptimizeAndAssemble(c, 1);
-    if (co == NULL) {
-        return ERROR;
-    }
-
     // We want the parameter to __annotate__ to be named "format" in the
     // signature  shown by inspect.signature(), but we need to use a
     // different name (.format) in the symtable; if the name
@@ -749,19 +743,16 @@ codegen_leave_annotations_scope(compiler *c, location loc)
     // co->co_localsplusnames = ("format", *co->co_localsplusnames[1:])
     const Py_ssize_t size = PyObject_Size(co->co_localsplusnames);
     if (size == -1) {
-        Py_DECREF(co);
         return ERROR;
     }
     PyObject *new_names = PyTuple_New(size);
     if (new_names == NULL) {
-        Py_DECREF(co);
         return ERROR;
     }
     PyTuple_SET_ITEM(new_names, 0, Py_NewRef(&_Py_ID(format)));
     for (int i = 1; i < size; i++) {
         PyObject *item = PyTuple_GetItem(co->co_localsplusnames, i);
         if (item == NULL) {
-            Py_DECREF(co);
             Py_DECREF(new_names);
             return ERROR;
         }
@@ -769,6 +760,22 @@ codegen_leave_annotations_scope(compiler *c, location loc)
         PyTuple_SET_ITEM(new_names, i, item);
     }
     Py_SETREF(co->co_localsplusnames, new_names);
+    return SUCCESS;
+}
+
+static int
+codegen_leave_annotations_scope(compiler *c, location loc)
+{
+    ADDOP_IN_SCOPE(c, loc, RETURN_VALUE);
+    PyCodeObject *co = _PyCompile_OptimizeAndAssemble(c, 1);
+    if (co == NULL) {
+        return ERROR;
+    }
+
+    if (codegen_rename_annotations_format_param(co) < 0) {
+        Py_DECREF(co);
+        return ERROR;
+    }
 
     _PyCompile_ExitScope(c);
     int ret = codegen_make_closure(c, loc, co, 0);
@@ -1269,6 +1276,10 @@ codegen_type_param_bound_or_default(compiler *c, expr_ty e,
     if (co == NULL) {
         return ERROR;
     }
+    if (codegen_rename_annotations_format_param(co) < 0) {
+        Py_DECREF(co);
+        return ERROR;
+    }
     int ret = codegen_make_closure(c, LOC(e), co, MAKE_FUNCTION_DEFAULTS);
     Py_DECREF(co);
     RETURN_IF_ERROR(ret);
@@ -1770,6 +1781,10 @@ codegen_typealias_body(compiler *c, stmt_ty s)
     if (co == NULL) {
         return ERROR;
     }
+    if (codegen_rename_annotations_format_param(co) < 0) {
+        Py_DECREF(co);
+        return ERROR;
+    }
     int ret = codegen_make_closure(c, loc, co, MAKE_FUNCTION_DEFAULTS);
     Py_DECREF(co);
     RETURN_IF_ERROR(ret);