Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Include/internal/pycore_symtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ typedef struct _symtable_entry {
PyObject_HEAD
PyObject *ste_id; /* int: key in ste_table->st_blocks */
PyObject *ste_symbols; /* dict: variable names to flags */
PyObject *ste_name; /* string: name of current block */
PyObject *ste_name; /* string: name of current block (for annotation blocks, may be the name of the corresponding function instead) */
PyObject *ste_varnames; /* list of function parameters */
PyObject *ste_children; /* list of child blocks */
PyObject *ste_directives;/* locations of global and nonlocal statements */
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_type_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,3 +835,20 @@ def test_complex_comprehension_inlining_exec(self):
genexp = annos["unique_name_2"][0]
lamb = list(genexp)[0]
self.assertEqual(lamb(), 42)

def test_annotate_qualname(self):
code = """
def f() -> None:
def nested() -> None: pass
return nested
class Outer:
x: int
def method(self, x: int):
pass
"""
ns = run_code(code)
method = ns["Outer"].method
self.assertEqual(method.__annotate__.__qualname__, "Outer.method.__annotate__")
self.assertEqual(ns["f"].__annotate__.__qualname__, "f.__annotate__")
self.assertEqual(ns["f"]().__annotate__.__qualname__, "f.<locals>.nested.__annotate__")
self.assertEqual(ns["Outer"].__annotate__.__qualname__, "Outer.__annotate__")
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix the ``__qualname__`` attribute of ``__annotate__`` functions on
functions.
38 changes: 33 additions & 5 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@ compiler_set_qualname(compiler *c)
struct compiler_unit *u = c->u;
PyObject *name, *base;

PyObject *u_name, *function_name = NULL;
if (u->u_scope_type == COMPILE_SCOPE_ANNOTATIONS) {
u_name = &_Py_ID(__annotate__);
_Py_DECLARE_STR(empty, "");
if (u->u_ste->ste_name != &_Py_STR(empty)) {
function_name = u->u_ste->ste_name;
}
}
else {
u_name = u->u_metadata.u_name;
}

base = NULL;
stack_size = PyList_GET_SIZE(c->c_stack);
assert(stack_size >= 1);
Expand All @@ -248,7 +260,7 @@ compiler_set_qualname(compiler *c)
if (stack_size == 2) {
// If we're immediately within the module, we can skip
// the rest and just set the qualname to be the same as name.
u->u_metadata.u_qualname = Py_NewRef(u->u_metadata.u_name);
u->u_metadata.u_qualname = Py_NewRef(u_name);
return SUCCESS;
}
capsule = PyList_GET_ITEM(c->c_stack, stack_size - 2);
Expand All @@ -260,7 +272,7 @@ compiler_set_qualname(compiler *c)
|| u->u_scope_type == COMPILE_SCOPE_ASYNC_FUNCTION
|| u->u_scope_type == COMPILE_SCOPE_CLASS) {
assert(u->u_metadata.u_name);
mangled = _Py_Mangle(parent->u_private, u->u_metadata.u_name);
mangled = _Py_Mangle(parent->u_private, u_name);
if (!mangled) {
return ERROR;
}
Expand Down Expand Up @@ -289,6 +301,17 @@ compiler_set_qualname(compiler *c)
base = Py_NewRef(parent->u_metadata.u_qualname);
}
}
if (function_name != NULL) {
PyObject *tmp = base;
base = PyUnicode_FromFormat("%U.%U", base, function_name);
Py_DECREF(tmp);
if (base == NULL) {
return ERROR;
}
}
}
else if (function_name != NULL) {
base = Py_NewRef(function_name);
}

if (base != NULL) {
Expand All @@ -297,13 +320,13 @@ compiler_set_qualname(compiler *c)
if (name == NULL) {
return ERROR;
}
PyUnicode_Append(&name, u->u_metadata.u_name);
PyUnicode_Append(&name, u_name);
if (name == NULL) {
return ERROR;
}
}
else {
name = Py_NewRef(u->u_metadata.u_name);
name = Py_NewRef(u_name);
}
u->u_metadata.u_qualname = name;

Expand Down Expand Up @@ -595,7 +618,12 @@ _PyCompile_EnterScope(compiler *c, identifier name, int scope_type,
compiler_unit_free(u);
return ERROR;
}
u->u_metadata.u_name = Py_NewRef(name);
if (u->u_ste->ste_type == AnnotationBlock) {
u->u_metadata.u_name = Py_NewRef(&_Py_ID(__annotate__));
}
else {
u->u_metadata.u_name = Py_NewRef(name);
}
u->u_metadata.u_varnames = list2dict(u->u_ste->ste_varnames);
if (!u->u_metadata.u_varnames) {
compiler_unit_free(u);
Expand Down
25 changes: 21 additions & 4 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,17 @@ ste_dealloc(PyObject *op)

#define OFF(x) offsetof(PySTEntryObject, x)

static PyObject *
ste_get_name(PySTEntryObject *ste, void *context)
{
if (ste->ste_type == AnnotationBlock) {
return Py_NewRef(&_Py_ID(__annotate__));
}
return Py_NewRef(ste->ste_name);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was assuming that the ste_name of an annotations ste would be set to __annotations__ so you wouldn’t need to do this. Is that not possible?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your previous comment suggested using the ste_name to store the name of the corresponding function instead. We can't do both.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we set the ste_name to funcname.__annotations__?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we'd end up with that in the __name__ of the function. I guess we could do that, but having a period in the __name__ seems odd.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm not sure which option is best. I'll approve with whichever you choose.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are at least two options, the initial version of this PR (adding ste_function_name) and the one I pushed earlier this morning after your comment (overloading ste_name). The advantage of the ste_function_name version is that it's simpler: less code and ste_name always means the same thing. The advantage of the version that overloads ste_name is that we save a little on memory usage: one fewer pointer in the symbol table struct.

Personally I prefer the ste_function_name version since it's simpler even if it uses slightly more memory during compilation, but if you prefer the other version, I'm OK with that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with the original version. Thank you for indulging my musings.


static PyMemberDef ste_memberlist[] = {
{"id", _Py_T_OBJECT, OFF(ste_id), Py_READONLY},
{"name", _Py_T_OBJECT, OFF(ste_name), Py_READONLY},
{"symbols", _Py_T_OBJECT, OFF(ste_symbols), Py_READONLY},
{"varnames", _Py_T_OBJECT, OFF(ste_varnames), Py_READONLY},
{"children", _Py_T_OBJECT, OFF(ste_children), Py_READONLY},
Expand All @@ -208,6 +216,14 @@ static PyMemberDef ste_memberlist[] = {
{NULL}
};

static PyGetSetDef ste_getsetlist[] = {
{"name",
(getter)ste_get_name, NULL,
NULL,
NULL},
{NULL}
};

PyTypeObject PySTEntry_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"symtable entry",
Expand Down Expand Up @@ -238,7 +254,7 @@ PyTypeObject PySTEntry_Type = {
0, /* tp_iternext */
0, /* tp_methods */
ste_memberlist, /* tp_members */
0, /* tp_getset */
ste_getsetlist, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
Expand Down Expand Up @@ -2771,7 +2787,8 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, void *key)
struct _symtable_entry *parent_ste = st->st_cur;
if (parent_ste->ste_annotation_block == NULL) {
_Py_block_ty current_type = parent_ste->ste_type;
if (!symtable_enter_block(st, &_Py_ID(__annotate__), AnnotationBlock,
_Py_DECLARE_STR(empty, "");
if (!symtable_enter_block(st, &_Py_STR(empty), AnnotationBlock,
key, LOCATION(annotation))) {
return 0;
}
Expand Down Expand Up @@ -2826,7 +2843,7 @@ symtable_visit_annotations(struct symtable *st, stmt_ty o, arguments_ty a, expr_
{
int is_in_class = st->st_cur->ste_can_see_class_scope;
_Py_block_ty current_type = st->st_cur->ste_type;
if (!symtable_enter_block(st, &_Py_ID(__annotate__), AnnotationBlock,
if (!symtable_enter_block(st, function_ste->ste_name, AnnotationBlock,
(void *)a, LOCATION(o))) {
return 0;
}
Expand Down
Loading