mirror of https://github.com/python/cpython
gh-120029: make `symtable.Symbol.__repr__` correctly reflect the compiler's flags, add methods (#120099)
Expose :class:`symtable.Symbol` methods :meth:`~symtable.Symbol.is_free_class`, :meth:`~symtable.Symbol.is_comp_iter` and :meth:`~symtable.Symbol.is_comp_cell`. --------- Co-authored-by: Carl Meyer <carl@oddbird.net>
This commit is contained in:
parent
7dd8c37a06
commit
755dab719d
|
@ -155,6 +155,8 @@ Examining Symbol Tables
|
||||||
|
|
||||||
Return ``True`` if the symbol is a type parameter.
|
Return ``True`` if the symbol is a type parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
.. method:: is_global()
|
.. method:: is_global()
|
||||||
|
|
||||||
Return ``True`` if the symbol is global.
|
Return ``True`` if the symbol is global.
|
||||||
|
@ -182,10 +184,42 @@ Examining Symbol Tables
|
||||||
Return ``True`` if the symbol is referenced in its block, but not assigned
|
Return ``True`` if the symbol is referenced in its block, but not assigned
|
||||||
to.
|
to.
|
||||||
|
|
||||||
|
.. method:: is_free_class()
|
||||||
|
|
||||||
|
Return *True* if a class-scoped symbol is free from
|
||||||
|
the perspective of a method.
|
||||||
|
|
||||||
|
Consider the following example::
|
||||||
|
|
||||||
|
def f():
|
||||||
|
x = 1 # function-scoped
|
||||||
|
class C:
|
||||||
|
x = 2 # class-scoped
|
||||||
|
def method(self):
|
||||||
|
return x
|
||||||
|
|
||||||
|
In this example, the class-scoped symbol ``x`` is considered to
|
||||||
|
be free from the perspective of ``C.method``, thereby allowing
|
||||||
|
the latter to return *1* at runtime and not *2*.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
.. method:: is_assigned()
|
.. method:: is_assigned()
|
||||||
|
|
||||||
Return ``True`` if the symbol is assigned to in its block.
|
Return ``True`` if the symbol is assigned to in its block.
|
||||||
|
|
||||||
|
.. method:: is_comp_iter()
|
||||||
|
|
||||||
|
Return ``True`` if the symbol is a comprehension iteration variable.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
|
.. method:: is_comp_cell()
|
||||||
|
|
||||||
|
Return ``True`` if the symbol is a cell in an inlined comprehension.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
.. method:: is_namespace()
|
.. method:: is_namespace()
|
||||||
|
|
||||||
Return ``True`` if name binding introduces new namespace.
|
Return ``True`` if name binding introduces new namespace.
|
||||||
|
|
|
@ -100,6 +100,17 @@ os
|
||||||
by :func:`os.unsetenv`, or made outside Python in the same process.
|
by :func:`os.unsetenv`, or made outside Python in the same process.
|
||||||
(Contributed by Victor Stinner in :gh:`120057`.)
|
(Contributed by Victor Stinner in :gh:`120057`.)
|
||||||
|
|
||||||
|
symtable
|
||||||
|
--------
|
||||||
|
|
||||||
|
* Expose the following :class:`symtable.Symbol` methods:
|
||||||
|
|
||||||
|
* :meth:`~symtable.Symbol.is_free_class`
|
||||||
|
* :meth:`~symtable.Symbol.is_comp_iter`
|
||||||
|
* :meth:`~symtable.Symbol.is_comp_cell`
|
||||||
|
|
||||||
|
(Contributed by Bénédikt Tran in :gh:`120029`.)
|
||||||
|
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -154,7 +154,7 @@ extern PyObject* _Py_Mangle(PyObject *p, PyObject *name);
|
||||||
#define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT)
|
#define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT)
|
||||||
|
|
||||||
/* GLOBAL_EXPLICIT and GLOBAL_IMPLICIT are used internally by the symbol
|
/* GLOBAL_EXPLICIT and GLOBAL_IMPLICIT are used internally by the symbol
|
||||||
table. GLOBAL is returned from PyST_GetScope() for either of them.
|
table. GLOBAL is returned from _PyST_GetScope() for either of them.
|
||||||
It is stored in ste_symbols at bits 13-16.
|
It is stored in ste_symbols at bits 13-16.
|
||||||
*/
|
*/
|
||||||
#define SCOPE_OFFSET 12
|
#define SCOPE_OFFSET 12
|
||||||
|
|
|
@ -4,7 +4,10 @@ import _symtable
|
||||||
from _symtable import (
|
from _symtable import (
|
||||||
USE,
|
USE,
|
||||||
DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL,
|
DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL,
|
||||||
DEF_PARAM, DEF_TYPE_PARAM, DEF_IMPORT, DEF_BOUND, DEF_ANNOT,
|
DEF_PARAM, DEF_TYPE_PARAM,
|
||||||
|
DEF_FREE_CLASS,
|
||||||
|
DEF_IMPORT, DEF_BOUND, DEF_ANNOT,
|
||||||
|
DEF_COMP_ITER, DEF_COMP_CELL,
|
||||||
SCOPE_OFF, SCOPE_MASK,
|
SCOPE_OFF, SCOPE_MASK,
|
||||||
FREE, LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL
|
FREE, LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL
|
||||||
)
|
)
|
||||||
|
@ -158,6 +161,10 @@ class SymbolTable:
|
||||||
for st in self._table.children]
|
for st in self._table.children]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_scope(flags): # like _PyST_GetScope()
|
||||||
|
return (flags >> SCOPE_OFF) & SCOPE_MASK
|
||||||
|
|
||||||
|
|
||||||
class Function(SymbolTable):
|
class Function(SymbolTable):
|
||||||
|
|
||||||
# Default values for instance variables
|
# Default values for instance variables
|
||||||
|
@ -183,7 +190,7 @@ class Function(SymbolTable):
|
||||||
"""
|
"""
|
||||||
if self.__locals is None:
|
if self.__locals is None:
|
||||||
locs = (LOCAL, CELL)
|
locs = (LOCAL, CELL)
|
||||||
test = lambda x: ((x >> SCOPE_OFF) & SCOPE_MASK) in locs
|
test = lambda x: _get_scope(x) in locs
|
||||||
self.__locals = self.__idents_matching(test)
|
self.__locals = self.__idents_matching(test)
|
||||||
return self.__locals
|
return self.__locals
|
||||||
|
|
||||||
|
@ -192,7 +199,7 @@ class Function(SymbolTable):
|
||||||
"""
|
"""
|
||||||
if self.__globals is None:
|
if self.__globals is None:
|
||||||
glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
|
glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)
|
||||||
test = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) in glob
|
test = lambda x: _get_scope(x) in glob
|
||||||
self.__globals = self.__idents_matching(test)
|
self.__globals = self.__idents_matching(test)
|
||||||
return self.__globals
|
return self.__globals
|
||||||
|
|
||||||
|
@ -207,7 +214,7 @@ class Function(SymbolTable):
|
||||||
"""Return a tuple of free variables in the function.
|
"""Return a tuple of free variables in the function.
|
||||||
"""
|
"""
|
||||||
if self.__frees is None:
|
if self.__frees is None:
|
||||||
is_free = lambda x:((x >> SCOPE_OFF) & SCOPE_MASK) == FREE
|
is_free = lambda x: _get_scope(x) == FREE
|
||||||
self.__frees = self.__idents_matching(is_free)
|
self.__frees = self.__idents_matching(is_free)
|
||||||
return self.__frees
|
return self.__frees
|
||||||
|
|
||||||
|
@ -234,7 +241,7 @@ class Symbol:
|
||||||
def __init__(self, name, flags, namespaces=None, *, module_scope=False):
|
def __init__(self, name, flags, namespaces=None, *, module_scope=False):
|
||||||
self.__name = name
|
self.__name = name
|
||||||
self.__flags = flags
|
self.__flags = flags
|
||||||
self.__scope = (flags >> SCOPE_OFF) & SCOPE_MASK # like PyST_GetScope()
|
self.__scope = _get_scope(flags)
|
||||||
self.__namespaces = namespaces or ()
|
self.__namespaces = namespaces or ()
|
||||||
self.__module_scope = module_scope
|
self.__module_scope = module_scope
|
||||||
|
|
||||||
|
@ -303,6 +310,11 @@ class Symbol:
|
||||||
"""
|
"""
|
||||||
return bool(self.__scope == FREE)
|
return bool(self.__scope == FREE)
|
||||||
|
|
||||||
|
def is_free_class(self):
|
||||||
|
"""Return *True* if a class-scoped symbol is free from
|
||||||
|
the perspective of a method."""
|
||||||
|
return bool(self.__flags & DEF_FREE_CLASS)
|
||||||
|
|
||||||
def is_imported(self):
|
def is_imported(self):
|
||||||
"""Return *True* if the symbol is created from
|
"""Return *True* if the symbol is created from
|
||||||
an import statement.
|
an import statement.
|
||||||
|
@ -313,6 +325,16 @@ class Symbol:
|
||||||
"""Return *True* if a symbol is assigned to."""
|
"""Return *True* if a symbol is assigned to."""
|
||||||
return bool(self.__flags & DEF_LOCAL)
|
return bool(self.__flags & DEF_LOCAL)
|
||||||
|
|
||||||
|
def is_comp_iter(self):
|
||||||
|
"""Return *True* if the symbol is a comprehension iteration variable.
|
||||||
|
"""
|
||||||
|
return bool(self.__flags & DEF_COMP_ITER)
|
||||||
|
|
||||||
|
def is_comp_cell(self):
|
||||||
|
"""Return *True* if the symbol is a cell in an inlined comprehension.
|
||||||
|
"""
|
||||||
|
return bool(self.__flags & DEF_COMP_CELL)
|
||||||
|
|
||||||
def is_namespace(self):
|
def is_namespace(self):
|
||||||
"""Returns *True* if name binding introduces new namespace.
|
"""Returns *True* if name binding introduces new namespace.
|
||||||
|
|
||||||
|
|
|
@ -304,6 +304,27 @@ class SymtableTest(unittest.TestCase):
|
||||||
self.assertEqual(repr(self.GenericMine.lookup("T")),
|
self.assertEqual(repr(self.GenericMine.lookup("T")),
|
||||||
"<symbol 'T': LOCAL, DEF_LOCAL|DEF_TYPE_PARAM>")
|
"<symbol 'T': LOCAL, DEF_LOCAL|DEF_TYPE_PARAM>")
|
||||||
|
|
||||||
|
st1 = symtable.symtable("[x for x in [1]]", "?", "exec")
|
||||||
|
self.assertEqual(repr(st1.lookup("x")),
|
||||||
|
"<symbol 'x': LOCAL, USE|DEF_LOCAL|DEF_COMP_ITER>")
|
||||||
|
|
||||||
|
st2 = symtable.symtable("[(lambda: x) for x in [1]]", "?", "exec")
|
||||||
|
self.assertEqual(repr(st2.lookup("x")),
|
||||||
|
"<symbol 'x': CELL, DEF_LOCAL|DEF_COMP_ITER|DEF_COMP_CELL>")
|
||||||
|
|
||||||
|
st3 = symtable.symtable("def f():\n"
|
||||||
|
" x = 1\n"
|
||||||
|
" class A:\n"
|
||||||
|
" x = 2\n"
|
||||||
|
" def method():\n"
|
||||||
|
" return x\n",
|
||||||
|
"?", "exec")
|
||||||
|
# child 0 is for __annotate__
|
||||||
|
func_f = st3.get_children()[1]
|
||||||
|
class_A = func_f.get_children()[0]
|
||||||
|
self.assertEqual(repr(class_A.lookup('x')),
|
||||||
|
"<symbol 'x': LOCAL, DEF_LOCAL|DEF_FREE_CLASS>")
|
||||||
|
|
||||||
def test_symtable_entry_repr(self):
|
def test_symtable_entry_repr(self):
|
||||||
expected = f"<symtable entry top({self.top.get_id()}), line {self.top.get_lineno()}>"
|
expected = f"<symtable entry top({self.top.get_id()}), line {self.top.get_lineno()}>"
|
||||||
self.assertEqual(repr(self.top._table), expected)
|
self.assertEqual(repr(self.top._table), expected)
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Expose :class:`symtable.Symbol` methods :meth:`~symtable.Symbol.is_free_class`,
|
||||||
|
:meth:`~symtable.Symbol.is_comp_iter` and :meth:`~symtable.Symbol.is_comp_cell`.
|
||||||
|
Patch by Bénédikt Tran.
|
||||||
|
|
|
@ -81,6 +81,8 @@ symtable_init_constants(PyObject *m)
|
||||||
if (PyModule_AddIntMacro(m, DEF_IMPORT) < 0) return -1;
|
if (PyModule_AddIntMacro(m, DEF_IMPORT) < 0) return -1;
|
||||||
if (PyModule_AddIntMacro(m, DEF_BOUND) < 0) return -1;
|
if (PyModule_AddIntMacro(m, DEF_BOUND) < 0) return -1;
|
||||||
if (PyModule_AddIntMacro(m, DEF_ANNOT) < 0) return -1;
|
if (PyModule_AddIntMacro(m, DEF_ANNOT) < 0) return -1;
|
||||||
|
if (PyModule_AddIntMacro(m, DEF_COMP_ITER) < 0) return -1;
|
||||||
|
if (PyModule_AddIntMacro(m, DEF_COMP_CELL) < 0) return -1;
|
||||||
|
|
||||||
if (PyModule_AddIntConstant(m, "TYPE_FUNCTION", FunctionBlock) < 0)
|
if (PyModule_AddIntConstant(m, "TYPE_FUNCTION", FunctionBlock) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
Loading…
Reference in New Issue