https://github.com/python/cpython/commit/348012432155271cfbee71a78d0c27254fc230e2
commit: 348012432155271cfbee71a78d0c27254fc230e2
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2024-12-30T08:19:38-08:00
summary:

gh-119180: Set the name of the param to __annotate__ to "format" (#124730)

files:
M Lib/test/test_pydoc/test_pydoc.py
M Lib/test/test_type_annotations.py
M Python/codegen.c

diff --git a/Lib/test/test_pydoc/test_pydoc.py 
b/Lib/test/test_pydoc/test_pydoc.py
index 3283fde9e12a8a..c798b11f5aa56e 100644
--- a/Lib/test/test_pydoc/test_pydoc.py
+++ b/Lib/test/test_pydoc/test_pydoc.py
@@ -79,7 +79,7 @@ class A(builtins.object)
     class B(builtins.object)
      |  Methods defined here:
      |
-     |  __annotate__(...)
+     |  __annotate__(format, /)
      |
      |  ----------------------------------------------------------------------
      |  Data descriptors defined here:
@@ -180,7 +180,7 @@ class A(builtins.object)
 
 class B(builtins.object)
     Methods defined here:
-        __annotate__(...)
+        __annotate__(format, /)
     ----------------------------------------------------------------------
     Data descriptors defined here:
         __dict__
diff --git a/Lib/test/test_type_annotations.py 
b/Lib/test/test_type_annotations.py
index 7d88f4cdfa3141..0afcd76af153e7 100644
--- a/Lib/test/test_type_annotations.py
+++ b/Lib/test/test_type_annotations.py
@@ -1,4 +1,5 @@
 import annotationlib
+import inspect
 import textwrap
 import types
 import unittest
@@ -380,6 +381,11 @@ class X:
                     annotate(None)
                 self.assertEqual(annotate(annotationlib.Format.VALUE), {"x": 
int})
 
+                sig = inspect.signature(annotate)
+                self.assertEqual(sig, inspect.Signature([
+                    inspect.Parameter("format", 
inspect.Parameter.POSITIONAL_ONLY)
+                ]))
+
     def test_comprehension_in_annotation(self):
         # This crashed in an earlier version of the code
         ns = run_code("x: [y for y in range(10)]")
@@ -400,6 +406,7 @@ def f(x: int) -> int: pass
 
     def test_name_clash_with_format(self):
         # this test would fail if __annotate__'s parameter was called "format"
+        # during symbol table construction
         code = """
         class format: pass
 
@@ -408,3 +415,45 @@ def f(x: format): pass
         ns = run_code(code)
         f = ns["f"]
         self.assertEqual(f.__annotations__, {"x": ns["format"]})
+
+        code = """
+        class Outer:
+            class format: pass
+
+            def meth(self, x: format): ...
+        """
+        ns = run_code(code)
+        self.assertEqual(ns["Outer"].meth.__annotations__, {"x": 
ns["Outer"].format})
+
+        code = """
+        def f(format):
+            def inner(x: format): pass
+            return inner
+        res = f("closure var")
+        """
+        ns = run_code(code)
+        self.assertEqual(ns["res"].__annotations__, {"x": "closure var"})
+
+        code = """
+        def f(x: format):
+            pass
+        """
+        ns = run_code(code)
+        # picks up the format() builtin
+        self.assertEqual(ns["f"].__annotations__, {"x": format})
+
+        code = """
+        def outer():
+            def f(x: format):
+                pass
+            if False:
+                class format: pass
+            return f
+        f = outer()
+        """
+        ns = run_code(code)
+        with self.assertRaisesRegex(
+            NameError,
+            "cannot access free variable 'format' where it is not associated 
with a value in enclosing scope",
+        ):
+            ns["f"].__annotations__
diff --git a/Python/codegen.c b/Python/codegen.c
index 6d3272edfdbf94..7432415b17414e 100644
--- a/Python/codegen.c
+++ b/Python/codegen.c
@@ -701,6 +701,33 @@ codegen_leave_annotations_scope(compiler *c, location loc,
     ADDOP_I(c, loc, BUILD_MAP, annotations_len);
     ADDOP_IN_SCOPE(c, loc, RETURN_VALUE);
     PyCodeObject *co = _PyCompile_OptimizeAndAssemble(c, 1);
+
+    // 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
+    // "format" appears in the annotations, it doesn't get clobbered
+    // by this name.  This code is essentially:
+    // co->co_localsplusnames = ("format", *co->co_localsplusnames[1:])
+    const Py_ssize_t size = PyObject_Size(co->co_localsplusnames);
+    if (size == -1) {
+        return ERROR;
+    }
+    PyObject *new_names = PyTuple_New(size);
+    if (new_names == NULL) {
+        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(new_names);
+            return ERROR;
+        }
+        Py_INCREF(item);
+        PyTuple_SET_ITEM(new_names, i, item);
+    }
+    Py_SETREF(co->co_localsplusnames, new_names);
+
     _PyCompile_ExitScope(c);
     if (co == NULL) {
         return 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