codekingpro/portable-devtools
114k
1"""Interface to the compiler's internal symbol tables"""2 3import _symtable4from _symtable import (5 USE,6 DEF_GLOBAL, # noqa: F4017 DEF_NONLOCAL, DEF_LOCAL,8 DEF_PARAM, DEF_TYPE_PARAM, DEF_FREE_CLASS,9 DEF_IMPORT, DEF_BOUND, DEF_ANNOT,10 DEF_COMP_ITER, DEF_COMP_CELL,11 SCOPE_OFF, SCOPE_MASK,12 FREE, LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL13)14 15import weakref16from enum import StrEnum17 18__all__ = ["symtable", "SymbolTableType", "SymbolTable", "Class", "Function", "Symbol"]19 20def symtable(code, filename, compile_type):21 """ Return the toplevel *SymbolTable* for the source code.22 23 *filename* is the name of the file with the code24 and *compile_type* is the *compile()* mode argument.25 """26 top = _symtable.symtable(code, filename, compile_type)27 return _newSymbolTable(top, filename)28 29class SymbolTableFactory:30 def __init__(self):31 self.__memo = weakref.WeakValueDictionary()32 33 def new(self, table, filename):34 if table.type == _symtable.TYPE_FUNCTION:35 return Function(table, filename)36 if table.type == _symtable.TYPE_CLASS:37 return Class(table, filename)38 return SymbolTable(table, filename)39 40 def __call__(self, table, filename):41 key = table, filename42 obj = self.__memo.get(key, None)43 if obj is None:44 obj = self.__memo[key] = self.new(table, filename)45 return obj46 47_newSymbolTable = SymbolTableFactory()48 49 50class SymbolTableType(StrEnum):51 MODULE = "module"52 FUNCTION = "function"53 CLASS = "class"54 ANNOTATION = "annotation"55 TYPE_ALIAS = "type alias"56 TYPE_PARAMETERS = "type parameters"57 TYPE_VARIABLE = "type variable"58 59 60class SymbolTable:61 62 def __init__(self, raw_table, filename):63 self._table = raw_table64 self._filename = filename65 self._symbols = {}66 67 def __repr__(self):68 if self.__class__ == SymbolTable:69 kind = ""70 else:71 kind = "%s " % self.__class__.__name__72 73 if self._table.name == "top":74 return "<{0}SymbolTable for module {1}>".format(kind, self._filename)75 else:76 return "<{0}SymbolTable for {1} in {2}>".format(kind,77 self._table.name,78 self._filename)79 80 def get_type(self):81 """Return the type of the symbol table.82 83 The value returned is one of the values in84 the ``SymbolTableType`` enumeration.85 """86 if self._table.type == _symtable.TYPE_MODULE:87 return SymbolTableType.MODULE88 if self._table.type == _symtable.TYPE_FUNCTION:89 return SymbolTableType.FUNCTION90 if self._table.type == _symtable.TYPE_CLASS:91 return SymbolTableType.CLASS92 if self._table.type == _symtable.TYPE_ANNOTATION:93 return SymbolTableType.ANNOTATION94 if self._table.type == _symtable.TYPE_TYPE_ALIAS:95 return SymbolTableType.TYPE_ALIAS96 if self._table.type == _symtable.TYPE_TYPE_PARAMETERS:97 return SymbolTableType.TYPE_PARAMETERS98 if self._table.type == _symtable.TYPE_TYPE_VARIABLE:99 return SymbolTableType.TYPE_VARIABLE100 assert False, f"unexpected type: {self._table.type}"101 102 def get_id(self):103 """Return an identifier for the table.104 """105 return self._table.id106 107 def get_name(self):108 """Return the table's name.109 110 This corresponds to the name of the class, function111 or 'top' if the table is for a class, function or112 global respectively.113 """114 return self._table.name115 116 def get_lineno(self):117 """Return the number of the first line in the118 block for the table.119 """120 return self._table.lineno121 122 def is_optimized(self):123 """Return *True* if the locals in the table124 are optimizable.125 """126 return bool(self._table.type == _symtable.TYPE_FUNCTION)127 128 def is_nested(self):129 """Return *True* if the block is a nested class130 or function."""131 return bool(self._table.nested)132 133 def has_children(self):134 """Return *True* if the block has nested namespaces.135 """136 return bool(self._table.children)137 138 def get_identifiers(self):139 """Return a view object containing the names of symbols in the table.140 """141 return self._table.symbols.keys()142 143 def lookup(self, name):144 """Lookup a *name* in the table.145 146 Returns a *Symbol* instance.147 """148 sym = self._symbols.get(name)149 if sym is None:150 flags = self._table.symbols[name]151 namespaces = self.__check_children(name)152 module_scope = (self._table.name == "top")153 sym = self._symbols[name] = Symbol(name, flags, namespaces,154 module_scope=module_scope)155 return sym156 157 def get_symbols(self):158 """Return a list of *Symbol* instances for159 names in the table.160 """161 return [self.lookup(ident) for ident in self.get_identifiers()]162 163 def __check_children(self, name):164 return [_newSymbolTable(st, self._filename)165 for st in self._table.children166 if st.name == name]167 168 def get_children(self):169 """Return a list of the nested symbol tables.170 """171 return [_newSymbolTable(st, self._filename)172 for st in self._table.children]173 174 175def _get_scope(flags): # like _PyST_GetScope()176 return (flags >> SCOPE_OFF) & SCOPE_MASK177 178 179class Function(SymbolTable):180 181 # Default values for instance variables182 __params = None183 __locals = None184 __frees = None185 __globals = None186 __nonlocals = None187 188 def __idents_matching(self, test_func):189 return tuple(ident for ident in self.get_identifiers()190 if test_func(self._table.symbols[ident]))191 192 def get_parameters(self):193 """Return a tuple of parameters to the function.194 """195 if self.__params is None:196 self.__params = self.__idents_matching(lambda x:x & DEF_PARAM)197 return self.__params198 199 def get_locals(self):200 """Return a tuple of locals in the function.201 """202 if self.__locals is None:203 locs = (LOCAL, CELL)204 test = lambda x: _get_scope(x) in locs205 self.__locals = self.__idents_matching(test)206 return self.__locals207 208 def get_globals(self):209 """Return a tuple of globals in the function.210 """211 if self.__globals is None:212 glob = (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)213 test = lambda x: _get_scope(x) in glob214 self.__globals = self.__idents_matching(test)215 return self.__globals216 217 def get_nonlocals(self):218 """Return a tuple of nonlocals in the function.219 """220 if self.__nonlocals is None:221 self.__nonlocals = self.__idents_matching(lambda x:x & DEF_NONLOCAL)222 return self.__nonlocals223 224 def get_frees(self):225 """Return a tuple of free variables in the function.226 """227 if self.__frees is None:228 is_free = lambda x: _get_scope(x) == FREE229 self.__frees = self.__idents_matching(is_free)230 return self.__frees231 232 233class Class(SymbolTable):234 235 __methods = None236 237 def get_methods(self):238 """Return a tuple of methods declared in the class.239 """240 import warnings241 typename = f'{self.__class__.__module__}.{self.__class__.__name__}'242 warnings.warn(f'{typename}.get_methods() is deprecated '243 f'and will be removed in Python 3.16.',244 DeprecationWarning, stacklevel=2)245 246 if self.__methods is None:247 d = {}248 249 def is_local_symbol(ident):250 flags = self._table.symbols.get(ident, 0)251 return ((flags >> SCOPE_OFF) & SCOPE_MASK) == LOCAL252 253 for st in self._table.children:254 # pick the function-like symbols that are local identifiers255 if is_local_symbol(st.name):256 match st.type:257 case _symtable.TYPE_FUNCTION:258 # generators are of type TYPE_FUNCTION with a ".0"259 # parameter as a first parameter (which makes them260 # distinguishable from a function named 'genexpr')261 if st.name == 'genexpr' and '.0' in st.varnames:262 continue263 d[st.name] = 1264 case _symtable.TYPE_TYPE_PARAMETERS:265 # Get the function-def block in the annotation266 # scope 'st' with the same identifier, if any.267 scope_name = st.name268 for c in st.children:269 if c.name == scope_name and c.type == _symtable.TYPE_FUNCTION:270 # A generic generator of type TYPE_FUNCTION271 # cannot be a direct child of 'st' (but it272 # can be a descendant), e.g.:273 #274 # class A:275 # type genexpr[genexpr] = (x for x in [])276 assert scope_name != 'genexpr' or '.0' not in c.varnames277 d[scope_name] = 1278 break279 self.__methods = tuple(d)280 return self.__methods281 282 283class Symbol:284 285 def __init__(self, name, flags, namespaces=None, *, module_scope=False):286 self.__name = name287 self.__flags = flags288 self.__scope = _get_scope(flags)289 self.__namespaces = namespaces or ()290 self.__module_scope = module_scope291 292 def __repr__(self):293 flags_str = '|'.join(self._flags_str())294 return f'<symbol {self.__name!r}: {self._scope_str()}, {flags_str}>'295 296 def _scope_str(self):297 return _scopes_value_to_name.get(self.__scope) or str(self.__scope)298 299 def _flags_str(self):300 for flagname, flagvalue in _flags:301 if self.__flags & flagvalue == flagvalue:302 yield flagname303 304 def get_name(self):305 """Return a name of a symbol.306 """307 return self.__name308 309 def is_referenced(self):310 """Return *True* if the symbol is used in311 its block.312 """313 return bool(self.__flags & USE)314 315 def is_parameter(self):316 """Return *True* if the symbol is a parameter.317 """318 return bool(self.__flags & DEF_PARAM)319 320 def is_type_parameter(self):321 """Return *True* if the symbol is a type parameter.322 """323 return bool(self.__flags & DEF_TYPE_PARAM)324 325 def is_global(self):326 """Return *True* if the symbol is global.327 """328 return bool(self.__scope in (GLOBAL_IMPLICIT, GLOBAL_EXPLICIT)329 or (self.__module_scope and self.__flags & DEF_BOUND))330 331 def is_nonlocal(self):332 """Return *True* if the symbol is nonlocal."""333 return bool(self.__flags & DEF_NONLOCAL)334 335 def is_declared_global(self):336 """Return *True* if the symbol is declared global337 with a global statement."""338 return bool(self.__scope == GLOBAL_EXPLICIT)339 340 def is_local(self):341 """Return *True* if the symbol is local.342 """343 return bool(self.__scope in (LOCAL, CELL)344 or (self.__module_scope and self.__flags & DEF_BOUND))345 346 def is_annotated(self):347 """Return *True* if the symbol is annotated.348 """349 return bool(self.__flags & DEF_ANNOT)350 351 def is_free(self):352 """Return *True* if a referenced symbol is353 not assigned to.354 """355 return bool(self.__scope == FREE)356 357 def is_free_class(self):358 """Return *True* if a class-scoped symbol is free from359 the perspective of a method."""360 return bool(self.__flags & DEF_FREE_CLASS)361 362 def is_imported(self):363 """Return *True* if the symbol is created from364 an import statement.365 """366 return bool(self.__flags & DEF_IMPORT)367 368 def is_assigned(self):369 """Return *True* if a symbol is assigned to."""370 return bool(self.__flags & DEF_LOCAL)371 372 def is_comp_iter(self):373 """Return *True* if the symbol is a comprehension iteration variable.374 """375 return bool(self.__flags & DEF_COMP_ITER)376 377 def is_comp_cell(self):378 """Return *True* if the symbol is a cell in an inlined comprehension.379 """380 return bool(self.__flags & DEF_COMP_CELL)381 382 def is_namespace(self):383 """Returns *True* if name binding introduces new namespace.384 385 If the name is used as the target of a function or class386 statement, this will be true.387 388 Note that a single name can be bound to multiple objects. If389 is_namespace() is true, the name may also be bound to other390 objects, like an int or list, that does not introduce a new391 namespace.392 """393 return bool(self.__namespaces)394 395 def get_namespaces(self):396 """Return a list of namespaces bound to this name"""397 return self.__namespaces398 399 def get_namespace(self):400 """Return the single namespace bound to this name.401 402 Raises ValueError if the name is bound to multiple namespaces403 or no namespace.404 """405 if len(self.__namespaces) == 0:406 raise ValueError("name is not bound to any namespaces")407 elif len(self.__namespaces) > 1:408 raise ValueError("name is bound to multiple namespaces")409 else:410 return self.__namespaces[0]411 412 413_flags = [('USE', USE)]414_flags.extend(kv for kv in globals().items() if kv[0].startswith('DEF_'))415_scopes_names = ('FREE', 'LOCAL', 'GLOBAL_IMPLICIT', 'GLOBAL_EXPLICIT', 'CELL')416_scopes_value_to_name = {globals()[n]: n for n in _scopes_names}417 418 419def main(args):420 import sys421 def print_symbols(table, level=0):422 indent = ' ' * level423 nested = "nested " if table.is_nested() else ""424 if table.get_type() == 'module':425 what = f'from file {table._filename!r}'426 else:427 what = f'{table.get_name()!r}'428 print(f'{indent}symbol table for {nested}{table.get_type()} {what}:')429 for ident in table.get_identifiers():430 symbol = table.lookup(ident)431 flags = ', '.join(symbol._flags_str()).lower()432 print(f' {indent}{symbol._scope_str().lower()} symbol {symbol.get_name()!r}: {flags}')433 print()434 435 for table2 in table.get_children():436 print_symbols(table2, level + 1)437 438 for filename in args or ['-']:439 if filename == '-':440 src = sys.stdin.read()441 filename = '<stdin>'442 else:443 with open(filename, 'rb') as f:444 src = f.read()445 mod = symtable(src, filename, 'exec')446 print_symbols(mod)447 448 449if __name__ == "__main__":450 import sys451 main(sys.argv[1:])452 