diff --git a/Lib/test/test_type_params.py b/Lib/test/test_type_params.py index 84c1b954136..b6588269e1d 100644 --- a/Lib/test/test_type_params.py +++ b/Lib/test/test_type_params.py @@ -1,4 +1,5 @@ import annotationlib +import inspect import textwrap import types import unittest @@ -1446,6 +1447,30 @@ def f[T: int = int, **P = int, *Ts = int](): pass 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 index 00000000000..d08a1220cbe --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-21-12-55-18.gh-issue-151665.82fmzx.rst @@ -0,0 +1,2 @@ +:func:`inspect.signature` now works on the lazy evaluators of type aliases +and type parameters instead of raising :exc:`ValueError`. diff --git a/Python/codegen.c b/Python/codegen.c index 205c49cff18..e65c308617d 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -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);