SymbolTable

SymbolTable is implemented as a simple HashMap. Here is the interface (types.ts):

  1. interface SymbolTable {
  2. [index: string]: Symbol;
  3. }

SymbolTables are initialized by binding. There are a few SymbolTables used by the compiler:

On Node:

  1. locals?: SymbolTable; // Locals associated with node

On Symbol:

  1. members?: SymbolTable; // Class, interface or literal instance members
  2. exports?: SymbolTable; // Module exports

Note: We saw locals getting initialized (to {}) by bindChildren based on ContainerFlags.

SymbolTable population

SymbolTables are populated with Symbols primarily by a call to declareSymbol. This function is presented below in entirety:

  1. /**
  2. * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names.
  3. * @param symbolTable - The symbol table which node will be added to.
  4. * @param parent - node's parent declaration.
  5. * @param node - The declaration to be added to the symbol table
  6. * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.)
  7. * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations.
  8. */
  9. function declareSymbol(symbolTable: SymbolTable, parent: Symbol, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags): Symbol {
  10. Debug.assert(!hasDynamicName(node));
  11. // The exported symbol for an export default function/class node is always named "default"
  12. let name = node.flags & NodeFlags.Default && parent ? "default" : getDeclarationName(node);
  13. let symbol: Symbol;
  14. if (name !== undefined) {
  15. // Check and see if the symbol table already has a symbol with this name. If not,
  16. // create a new symbol with this name and add it to the table. Note that we don't
  17. // give the new symbol any flags *yet*. This ensures that it will not conflict
  18. // with the 'excludes' flags we pass in.
  19. //
  20. // If we do get an existing symbol, see if it conflicts with the new symbol we're
  21. // creating. For example, a 'var' symbol and a 'class' symbol will conflict within
  22. // the same symbol table. If we have a conflict, report the issue on each
  23. // declaration we have for this symbol, and then create a new symbol for this
  24. // declaration.
  25. //
  26. // If we created a new symbol, either because we didn't have a symbol with this name
  27. // in the symbol table, or we conflicted with an existing symbol, then just add this
  28. // node as the sole declaration of the new symbol.
  29. //
  30. // Otherwise, we'll be merging into a compatible existing symbol (for example when
  31. // you have multiple 'vars' with the same name in the same container). In this case
  32. // just add this node into the declarations list of the symbol.
  33. symbol = hasProperty(symbolTable, name)
  34. ? symbolTable[name]
  35. : (symbolTable[name] = createSymbol(SymbolFlags.None, name));
  36. if (name && (includes & SymbolFlags.Classifiable)) {
  37. classifiableNames[name] = name;
  38. }
  39. if (symbol.flags & excludes) {
  40. if (node.name) {
  41. node.name.parent = node;
  42. }
  43. // Report errors every position with duplicate declaration
  44. // Report errors on previous encountered declarations
  45. let message = symbol.flags & SymbolFlags.BlockScopedVariable
  46. ? Diagnostics.Cannot_redeclare_block_scoped_variable_0
  47. : Diagnostics.Duplicate_identifier_0;
  48. forEach(symbol.declarations, declaration => {
  49. file.bindDiagnostics.push(createDiagnosticForNode(declaration.name || declaration, message, getDisplayName(declaration)));
  50. });
  51. file.bindDiagnostics.push(createDiagnosticForNode(node.name || node, message, getDisplayName(node)));
  52. symbol = createSymbol(SymbolFlags.None, name);
  53. }
  54. }
  55. else {
  56. symbol = createSymbol(SymbolFlags.None, "__missing");
  57. }
  58. addDeclarationToSymbol(symbol, node, includes);
  59. symbol.parent = parent;
  60. return symbol;
  61. }

Which SymbolTable is populated is driven by the first argument to this function. e.g. when adding a declaration to a container of kind SyntaxKind.ClassDeclaration or SyntaxKind.ClassExpression the function declareClassMember will get called which has the following code:

  1. function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) {
  2. return node.flags & NodeFlags.Static
  3. ? declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes)
  4. : declareSymbol(container.symbol.members, container.symbol, node, symbolFlags, symbolExcludes);
  5. }