Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
70 changes: 70 additions & 0 deletions Lib/test/test_symtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,76 @@ def test_loopvar_in_only_one_scope(self):
self.assertEqual(len([x for x in ids if x == 'x']), 1)


class EntryAttributeTests(unittest.TestCase):
"""Scope attributes of the low-level symbol table entries."""

@classmethod
def setUpClass(cls):
import _symtable
cls.TYPE_ANNOTATION = _symtable.TYPE_ANNOTATION
cls.top = _symtable.symtable(
"async def agen(): yield\n"
"def gen(): yield\n"
"def f(): pass\n"
"x: int = 1\n"
"class C:\n"
" def m(self): return __class__\n"
" if x:\n"
" y: int = 2\n"
"class P:\n"
" pass\n",
"<test>", "exec")

@staticmethod
def find(table, name):
return next(c for c in table.children if c.name == name)

def test_is_generator(self):
self.assertTrue(self.find(self.top, "agen").is_generator)
self.assertTrue(self.find(self.top, "gen").is_generator)
self.assertFalse(self.find(self.top, "f").is_generator)

def test_is_coroutine(self):
self.assertTrue(self.find(self.top, "agen").is_coroutine)
self.assertFalse(self.find(self.top, "gen").is_coroutine)
self.assertFalse(self.find(self.top, "f").is_coroutine)

def test_has_annotations(self):
self.assertTrue(self.top.has_annotations)
self.assertTrue(self.find(self.top, "C").has_annotations)
self.assertFalse(self.find(self.top, "f").has_annotations)

def test_has_conditional_annotations(self):
# Module annotations are always conditional.
self.assertTrue(self.top.has_conditional_annotations)
self.assertTrue(
self.find(self.top, "C").has_conditional_annotations)
self.assertFalse(
self.find(self.top, "P").has_conditional_annotations)

def test_needs_class_closure(self):
self.assertTrue(self.find(self.top, "C").needs_class_closure)
self.assertFalse(self.find(self.top, "P").needs_class_closure)

def test_needs_classdict(self):
self.assertTrue(self.find(self.top, "C").needs_classdict)
self.assertFalse(self.find(self.top, "P").needs_classdict)

def test_annotation_block(self):
anno = self.top.annotation_block
self.assertIsNotNone(anno)
self.assertIn(anno, self.top.children)
self.assertEqual(anno.type, self.TYPE_ANNOTATION)
self.assertIsNone(self.find(self.top, "f").annotation_block)
self.assertIsNotNone(self.find(self.top, "C").annotation_block)

def test_can_see_class_scope(self):
C = self.find(self.top, "C")
self.assertTrue(C.annotation_block.can_see_class_scope)
self.assertFalse(self.top.annotation_block.can_see_class_scope)
self.assertFalse(self.find(self.top, "f").can_see_class_scope)


class ASTInputTests(unittest.TestCase):
maxDiff = None

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Symbol table entries returned by the internal :mod:`!_symtable` module
now expose the scope attributes computed by the analysis:
``is_generator``, ``is_coroutine``, ``has_annotations``,
``has_conditional_annotations``, ``needs_class_closure``,
``needs_classdict``, ``can_see_class_scope`` and ``annotation_block``.
35 changes: 34 additions & 1 deletion Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,45 @@ static PyMemberDef ste_memberlist[] = {
{"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},
{"annotation_block", _Py_T_OBJECT, OFF(ste_annotation_block), Py_READONLY},
{"nested", Py_T_INT, OFF(ste_nested), Py_READONLY},
{"type", Py_T_INT, OFF(ste_type), Py_READONLY},
{"lineno", Py_T_INT, OFF(ste_loc.lineno), Py_READONLY},
{NULL}
};

/* The flag fields are C bitfields, which PyMemberDef cannot describe;
expose them through getters. */
#define STE_BOOL_GETTER(FIELD) \
static PyObject * \
ste_get_ ## FIELD(PyObject *op, void *Py_UNUSED(closure)) \
{ \
PySTEntryObject *ste = (PySTEntryObject *)op; \
return PyBool_FromLong(ste->ste_ ## FIELD); \
}

STE_BOOL_GETTER(generator)
STE_BOOL_GETTER(coroutine)
STE_BOOL_GETTER(annotations_used)
STE_BOOL_GETTER(needs_class_closure)
STE_BOOL_GETTER(needs_classdict)
STE_BOOL_GETTER(can_see_class_scope)
STE_BOOL_GETTER(has_conditional_annotations)

#undef STE_BOOL_GETTER

static PyGetSetDef ste_getsetlist[] = {
{"is_generator", ste_get_generator, NULL, NULL, NULL},
{"is_coroutine", ste_get_coroutine, NULL, NULL, NULL},
{"has_annotations", ste_get_annotations_used, NULL, NULL, NULL},
{"needs_class_closure", ste_get_needs_class_closure, NULL, NULL, NULL},
{"needs_classdict", ste_get_needs_classdict, NULL, NULL, NULL},
{"can_see_class_scope", ste_get_can_see_class_scope, NULL, NULL, NULL},
{"has_conditional_annotations", ste_get_has_conditional_annotations,
NULL, NULL, NULL},
{NULL}
};

PyTypeObject PySTEntry_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"symtable entry",
Expand Down Expand Up @@ -241,7 +274,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
Loading