Skip to content

Commit c517a10

Browse files
JelleZijlstrasrinivasreddy
authored andcommitted
pythongh-119180: Set the name of the param to __annotate__ to "format" (python#124730)
1 parent 63242ad commit c517a10

File tree

3 files changed

+78
-2
lines changed

3 files changed

+78
-2
lines changed

Lib/test/test_pydoc/test_pydoc.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class A(builtins.object)
7979
class B(builtins.object)
8080
| Methods defined here:
8181
|
82-
| __annotate__(...)
82+
| __annotate__(format, /)
8383
|
8484
| ----------------------------------------------------------------------
8585
| Data descriptors defined here:
@@ -180,7 +180,7 @@ class A(builtins.object)
180180
181181
class B(builtins.object)
182182
Methods defined here:
183-
__annotate__(...)
183+
__annotate__(format, /)
184184
----------------------------------------------------------------------
185185
Data descriptors defined here:
186186
__dict__

Lib/test/test_type_annotations.py

+49
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import annotationlib
2+
import inspect
23
import textwrap
34
import types
45
import unittest
@@ -380,6 +381,11 @@ class X:
380381
annotate(None)
381382
self.assertEqual(annotate(annotationlib.Format.VALUE), {"x": int})
382383

384+
sig = inspect.signature(annotate)
385+
self.assertEqual(sig, inspect.Signature([
386+
inspect.Parameter("format", inspect.Parameter.POSITIONAL_ONLY)
387+
]))
388+
383389
def test_comprehension_in_annotation(self):
384390
# This crashed in an earlier version of the code
385391
ns = run_code("x: [y for y in range(10)]")
@@ -400,6 +406,7 @@ def f(x: int) -> int: pass
400406

401407
def test_name_clash_with_format(self):
402408
# this test would fail if __annotate__'s parameter was called "format"
409+
# during symbol table construction
403410
code = """
404411
class format: pass
405412
@@ -408,3 +415,45 @@ def f(x: format): pass
408415
ns = run_code(code)
409416
f = ns["f"]
410417
self.assertEqual(f.__annotations__, {"x": ns["format"]})
418+
419+
code = """
420+
class Outer:
421+
class format: pass
422+
423+
def meth(self, x: format): ...
424+
"""
425+
ns = run_code(code)
426+
self.assertEqual(ns["Outer"].meth.__annotations__, {"x": ns["Outer"].format})
427+
428+
code = """
429+
def f(format):
430+
def inner(x: format): pass
431+
return inner
432+
res = f("closure var")
433+
"""
434+
ns = run_code(code)
435+
self.assertEqual(ns["res"].__annotations__, {"x": "closure var"})
436+
437+
code = """
438+
def f(x: format):
439+
pass
440+
"""
441+
ns = run_code(code)
442+
# picks up the format() builtin
443+
self.assertEqual(ns["f"].__annotations__, {"x": format})
444+
445+
code = """
446+
def outer():
447+
def f(x: format):
448+
pass
449+
if False:
450+
class format: pass
451+
return f
452+
f = outer()
453+
"""
454+
ns = run_code(code)
455+
with self.assertRaisesRegex(
456+
NameError,
457+
"cannot access free variable 'format' where it is not associated with a value in enclosing scope",
458+
):
459+
ns["f"].__annotations__

Python/codegen.c

+27
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,33 @@ codegen_leave_annotations_scope(compiler *c, location loc,
701701
ADDOP_I(c, loc, BUILD_MAP, annotations_len);
702702
ADDOP_IN_SCOPE(c, loc, RETURN_VALUE);
703703
PyCodeObject *co = _PyCompile_OptimizeAndAssemble(c, 1);
704+
705+
// We want the parameter to __annotate__ to be named "format" in the
706+
// signature shown by inspect.signature(), but we need to use a
707+
// different name (.format) in the symtable; if the name
708+
// "format" appears in the annotations, it doesn't get clobbered
709+
// by this name. This code is essentially:
710+
// co->co_localsplusnames = ("format", *co->co_localsplusnames[1:])
711+
const Py_ssize_t size = PyObject_Size(co->co_localsplusnames);
712+
if (size == -1) {
713+
return ERROR;
714+
}
715+
PyObject *new_names = PyTuple_New(size);
716+
if (new_names == NULL) {
717+
return ERROR;
718+
}
719+
PyTuple_SET_ITEM(new_names, 0, Py_NewRef(&_Py_ID(format)));
720+
for (int i = 1; i < size; i++) {
721+
PyObject *item = PyTuple_GetItem(co->co_localsplusnames, i);
722+
if (item == NULL) {
723+
Py_DECREF(new_names);
724+
return ERROR;
725+
}
726+
Py_INCREF(item);
727+
PyTuple_SET_ITEM(new_names, i, item);
728+
}
729+
Py_SETREF(co->co_localsplusnames, new_names);
730+
704731
_PyCompile_ExitScope(c);
705732
if (co == NULL) {
706733
return ERROR;

0 commit comments

Comments
 (0)