Skip to content

Commit 9b50e34

Browse files
committed
gh-124157: Lazily create __annotate__ functions for function annotations
1 parent 3480124 commit 9b50e34

File tree

3 files changed

+85
-10
lines changed

3 files changed

+85
-10
lines changed

Include/cpython/funcobject.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,14 @@ typedef struct {
4141
PyObject *func_weakreflist; /* List of weak references */
4242
PyObject *func_module; /* The __module__ attribute, can be anything */
4343
PyObject *func_annotations; /* Annotations, a dict or NULL */
44-
PyObject *func_annotate; /* Callable to fill the annotations dictionary */
44+
/* Callable to fill the annotations dictionary.
45+
* May also be:
46+
* NULL (function has no annotations, or only an annotations dict)
47+
* None (set manually by user)
48+
* a code object (if the function has no closure)
49+
* a tuple containing a code object plus cell variables (for a closure)
50+
*/
51+
PyObject *func_annotate;
4552
PyObject *func_typeparams; /* Tuple of active type variables or NULL */
4653
vectorcallfunc vectorcall;
4754
/* Version number for use by specializer.

Objects/funcobject.c

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
99
#include "pycore_pyerrors.h" // _PyErr_Occurred()
1010

11+
static PyObject *
12+
get_annotate_function(PyFunctionObject *func);
1113

1214
static const char *
1315
func_event_name(PyFunction_WatchEvent event) {
@@ -547,11 +549,13 @@ static PyObject *
547549
func_get_annotation_dict(PyFunctionObject *op)
548550
{
549551
if (op->func_annotations == NULL) {
550-
if (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate)) {
552+
if (op->func_annotate == NULL || Py_IsNone(op->func_annotate)) {
551553
Py_RETURN_NONE;
552554
}
555+
PyObject *annotate = get_annotate_function(op);
553556
PyObject *one = _PyLong_GetOne();
554-
PyObject *ann_dict = _PyObject_CallOneArg(op->func_annotate, one);
557+
PyObject *ann_dict = _PyObject_CallOneArg(annotate, one);
558+
Py_DECREF(annotate);
555559
if (ann_dict == NULL) {
556560
return NULL;
557561
}
@@ -828,10 +832,43 @@ static PyObject *
828832
func_get_annotate(PyObject *self, void *Py_UNUSED(ignored))
829833
{
830834
PyFunctionObject *op = _PyFunction_CAST(self);
831-
if (op->func_annotate == NULL) {
835+
return get_annotate_function(op);
836+
}
837+
838+
static PyObject *
839+
get_annotate_function(PyFunctionObject *op)
840+
{
841+
if (op->func_annotate == NULL || Py_IsNone(op->func_annotate)) {
832842
Py_RETURN_NONE;
833843
}
834-
return Py_NewRef(op->func_annotate);
844+
if (PyCallable_Check(op->func_annotate)) {
845+
return Py_NewRef(op->func_annotate);
846+
}
847+
else if (PyCode_Check(op->func_annotate)) {
848+
return PyFunction_New(op->func_annotate, op->func_globals);
849+
}
850+
else if (PyTuple_CheckExact(op->func_annotate) && PyTuple_GET_SIZE(op->func_annotate) >= 2) {
851+
PyObject *co = PyTuple_GET_ITEM(op->func_annotate, 0);
852+
if (!PyCode_Check(co)) {
853+
PyErr_Format(PyExc_SystemError,
854+
"func_annotate tuple should contain code object, not '%.100s'",
855+
Py_TYPE(co)->tp_name);
856+
return NULL;
857+
}
858+
PyObject *func = PyFunction_New(co, op->func_globals);
859+
PyObject *closure = PyTuple_GetSlice(
860+
op->func_annotate, 1, PyTuple_GET_SIZE(op->func_annotate));
861+
if (closure == NULL) {
862+
Py_DECREF(func);
863+
return NULL;
864+
}
865+
_PyFunction_CAST(func)->func_closure = closure;
866+
return func;
867+
}
868+
PyErr_Format(PyExc_SystemError,
869+
"Invalid func_annotate attribute of type '%.100s'",
870+
Py_TYPE(op->func_annotate)->tp_name);
871+
return NULL;
835872
}
836873

837874
static int
@@ -864,7 +901,7 @@ func_get_annotations(PyObject *self, void *Py_UNUSED(ignored))
864901
{
865902
PyFunctionObject *op = _PyFunction_CAST(self);
866903
if (op->func_annotations == NULL &&
867-
(op->func_annotate == NULL || !PyCallable_Check(op->func_annotate))) {
904+
(op->func_annotate == NULL || Py_IsNone(op->func_annotate))) {
868905
op->func_annotations = PyDict_New();
869906
if (op->func_annotations == NULL)
870907
return NULL;

Python/codegen.c

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ static int codegen_pattern_subpattern(compiler *,
242242
pattern_ty, pattern_context *);
243243
static int codegen_make_closure(compiler *c, location loc,
244244
PyCodeObject *co, Py_ssize_t flags);
245+
static int codegen_make_annotate_function(compiler *c, location loc,
246+
PyCodeObject *co);
245247

246248

247249
/* Add an opcode with an integer argument */
@@ -696,7 +698,8 @@ codegen_setup_annotations_scope(compiler *c, location loc,
696698

697699
static int
698700
codegen_leave_annotations_scope(compiler *c, location loc,
699-
Py_ssize_t annotations_len)
701+
Py_ssize_t annotations_len,
702+
bool is_function)
700703
{
701704
ADDOP_I(c, loc, BUILD_MAP, annotations_len);
702705
ADDOP_IN_SCOPE(c, loc, RETURN_VALUE);
@@ -732,7 +735,8 @@ codegen_leave_annotations_scope(compiler *c, location loc,
732735
if (co == NULL) {
733736
return ERROR;
734737
}
735-
int ret = codegen_make_closure(c, loc, co, 0);
738+
int ret = is_function ? codegen_make_annotate_function(c, loc, co)
739+
: codegen_make_closure(c, loc, co, 0);
736740
Py_DECREF(co);
737741
RETURN_IF_ERROR(ret);
738742
return SUCCESS;
@@ -780,7 +784,8 @@ codegen_process_deferred_annotations(compiler *c, location loc)
780784
}
781785
Py_DECREF(deferred_anno);
782786

783-
RETURN_IF_ERROR(codegen_leave_annotations_scope(c, loc, annotations_len));
787+
RETURN_IF_ERROR(codegen_leave_annotations_scope(c, loc, annotations_len,
788+
/* is_function */false));
784789
RETURN_IF_ERROR(codegen_nameop(c, loc, &_Py_ID(__annotate__), Store));
785790

786791
return SUCCESS;
@@ -852,6 +857,32 @@ _PyCodegen_EnterAnonymousScope(compiler* c, mod_ty mod)
852857
return SUCCESS;
853858
}
854859

860+
static int
861+
codegen_make_annotate_function(compiler *c, location loc,
862+
PyCodeObject *co)
863+
{
864+
// For annotate functions, we don't bother creating the function object
865+
// unless the __annotate__ attribute is accessed.
866+
// We may store the prepared data as follows:
867+
// - Just a code object
868+
// - A tuple containing the code object, followed by a number of cell objects.
869+
ADDOP_LOAD_CONST(c, loc, (PyObject*)co);
870+
if (co->co_nfreevars) {
871+
int i = PyUnstable_Code_GetFirstFree(co);
872+
for (; i < co->co_nlocalsplus; ++i) {
873+
/* Bypass com_addop_varname because it will generate
874+
LOAD_DEREF but LOAD_CLOSURE is needed.
875+
*/
876+
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
877+
int arg = _PyCompile_LookupArg(c, co, name);
878+
RETURN_IF_ERROR(arg);
879+
ADDOP_I(c, loc, LOAD_CLOSURE, arg);
880+
}
881+
ADDOP_I(c, loc, BUILD_TUPLE, co->co_nfreevars + 1);
882+
}
883+
return SUCCESS;
884+
}
885+
855886
static int
856887
codegen_make_closure(compiler *c, location loc,
857888
PyCodeObject *co, Py_ssize_t flags)
@@ -1065,7 +1096,7 @@ codegen_annotations(compiler *c, location loc,
10651096
c, codegen_annotations_in_scope(c, loc, args, returns, &annotations_len)
10661097
);
10671098
RETURN_IF_ERROR(
1068-
codegen_leave_annotations_scope(c, loc, annotations_len)
1099+
codegen_leave_annotations_scope(c, loc, annotations_len, /* is_function */true)
10691100
);
10701101
return MAKE_FUNCTION_ANNOTATE;
10711102
}

0 commit comments

Comments
 (0)