Skip to content

Commit 3480124

Browse files
gh-119180: Set the name of the param to __annotate__ to "format" (#124730)
1 parent 2bd5a7a commit 3480124

File tree

3 files changed

+78
-2
lines changed

3 files changed

+78
-2
lines changed

Lib/test/test_pydoc/test_pydoc.py

Lines changed: 2 additions & 2 deletions
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

Lines changed: 49 additions & 0 deletions
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

Lines changed: 27 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy