Container

An AST node can be a container. This determines the kinds of SymbolTables the Node and associated Symbol will have. Container is an abstract concept (i.e. has no associated data structure). The concept is driven by a few things, one being the ContainerFlags enum. The function getContainerFlags (in binder.ts) drives this flag and is presented below:

  1. function getContainerFlags(node: Node): ContainerFlags {
  2. switch (node.kind) {
  3. case SyntaxKind.ClassExpression:
  4. case SyntaxKind.ClassDeclaration:
  5. case SyntaxKind.InterfaceDeclaration:
  6. case SyntaxKind.EnumDeclaration:
  7. case SyntaxKind.TypeLiteral:
  8. case SyntaxKind.ObjectLiteralExpression:
  9. return ContainerFlags.IsContainer;
  10. case SyntaxKind.CallSignature:
  11. case SyntaxKind.ConstructSignature:
  12. case SyntaxKind.IndexSignature:
  13. case SyntaxKind.MethodDeclaration:
  14. case SyntaxKind.MethodSignature:
  15. case SyntaxKind.FunctionDeclaration:
  16. case SyntaxKind.Constructor:
  17. case SyntaxKind.GetAccessor:
  18. case SyntaxKind.SetAccessor:
  19. case SyntaxKind.FunctionType:
  20. case SyntaxKind.ConstructorType:
  21. case SyntaxKind.FunctionExpression:
  22. case SyntaxKind.ArrowFunction:
  23. case SyntaxKind.ModuleDeclaration:
  24. case SyntaxKind.SourceFile:
  25. case SyntaxKind.TypeAliasDeclaration:
  26. return ContainerFlags.IsContainerWithLocals;
  27. case SyntaxKind.CatchClause:
  28. case SyntaxKind.ForStatement:
  29. case SyntaxKind.ForInStatement:
  30. case SyntaxKind.ForOfStatement:
  31. case SyntaxKind.CaseBlock:
  32. return ContainerFlags.IsBlockScopedContainer;
  33. case SyntaxKind.Block:
  34. // do not treat blocks directly inside a function as a block-scoped-container.
  35. // Locals that reside in this block should go to the function locals. Othewise 'x'
  36. // would not appear to be a redeclaration of a block scoped local in the following
  37. // example:
  38. //
  39. // function foo() {
  40. // var x;
  41. // let x;
  42. // }
  43. //
  44. // If we placed 'var x' into the function locals and 'let x' into the locals of
  45. // the block, then there would be no collision.
  46. //
  47. // By not creating a new block-scoped-container here, we ensure that both 'var x'
  48. // and 'let x' go into the Function-container's locals, and we do get a collision
  49. // conflict.
  50. return isFunctionLike(node.parent) ? ContainerFlags.None : ContainerFlags.IsBlockScopedContainer;
  51. }
  52. return ContainerFlags.None;
  53. }

It is only invoked from the binder’s bindChildren function which sets up a node as a container and/or a blockScopedContainer depending upon the evaluation of the getContainerFlags function. The function bindChildren is presented below:

  1. // All container nodes are kept on a linked list in declaration order. This list is used by
  2. // the getLocalNameOfContainer function in the type checker to validate that the local name
  3. // used for a container is unique.
  4. function bindChildren(node: Node) {
  5. // Before we recurse into a node's chilren, we first save the existing parent, container
  6. // and block-container. Then after we pop out of processing the children, we restore
  7. // these saved values.
  8. let saveParent = parent;
  9. let saveContainer = container;
  10. let savedBlockScopeContainer = blockScopeContainer;
  11. // This node will now be set as the parent of all of its children as we recurse into them.
  12. parent = node;
  13. // Depending on what kind of node this is, we may have to adjust the current container
  14. // and block-container. If the current node is a container, then it is automatically
  15. // considered the current block-container as well. Also, for containers that we know
  16. // may contain locals, we proactively initialize the .locals field. We do this because
  17. // it's highly likely that the .locals will be needed to place some child in (for example,
  18. // a parameter, or variable declaration).
  19. //
  20. // However, we do not proactively create the .locals for block-containers because it's
  21. // totally normal and common for block-containers to never actually have a block-scoped
  22. // variable in them. We don't want to end up allocating an object for every 'block' we
  23. // run into when most of them won't be necessary.
  24. //
  25. // Finally, if this is a block-container, then we clear out any existing .locals object
  26. // it may contain within it. This happens in incremental scenarios. Because we can be
  27. // reusing a node from a previous compilation, that node may have had 'locals' created
  28. // for it. We must clear this so we don't accidently move any stale data forward from
  29. // a previous compilation.
  30. let containerFlags = getContainerFlags(node);
  31. if (containerFlags & ContainerFlags.IsContainer) {
  32. container = blockScopeContainer = node;
  33. if (containerFlags & ContainerFlags.HasLocals) {
  34. container.locals = {};
  35. }
  36. addToContainerChain(container);
  37. }
  38. else if (containerFlags & ContainerFlags.IsBlockScopedContainer) {
  39. blockScopeContainer = node;
  40. blockScopeContainer.locals = undefined;
  41. }
  42. forEachChild(node, bind);
  43. container = saveContainer;
  44. parent = saveParent;
  45. blockScopeContainer = savedBlockScopeContainer;
  46. }

As you might recall from section on binder functions : bindChildren is called from the bind function. So we have the recursive binding setup : bind calls bindChildren calls bind for each child.