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:

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:

// All container nodes are kept on a linked list in declaration order. This list is used by// the getLocalNameOfContainer function in the type checker to validate that the local name// used for a container is unique.functionbindChildren(node: Node) {
// Before we recurse into a node's chilren, we first save the existing parent, container// and block-container. Then after we pop out of processing the children, we restore// these saved values.let saveParent = parent;
let saveContainer = container;
let savedBlockScopeContainer = blockScopeContainer;
// This node will now be set as the parent of all of its children as we recurse into them.
parent = node;
// Depending on what kind of node this is, we may have to adjust the current container// and block-container. If the current node is a container, then it is automatically// considered the current block-container as well. Also, for containers that we know// may contain locals, we proactively initialize the .locals field. We do this because// it's highly likely that the .locals will be needed to place some child in (for example,// a parameter, or variable declaration).//// However, we do not proactively create the .locals for block-containers because it's// totally normal and common for block-containers to never actually have a block-scoped// variable in them. We don't want to end up allocating an object for every 'block' we// run into when most of them won't be necessary.//// Finally, if this is a block-container, then we clear out any existing .locals object// it may contain within it. This happens in incremental scenarios. Because we can be// reusing a node from a previous compilation, that node may have had 'locals' created// for it. We must clear this so we don't accidently move any stale data forward from// a previous compilation.let containerFlags = getContainerFlags(node);
if (containerFlags & ContainerFlags.IsContainer) {
container = blockScopeContainer = node;
if (containerFlags & ContainerFlags.HasLocals) {
container.locals = {};
}
addToContainerChain(container);
}
elseif (containerFlags & ContainerFlags.IsBlockScopedContainer) {
blockScopeContainer = node;
blockScopeContainer.locals = undefined;
}
forEachChild(node, bind);
container = saveContainer;
parent = saveParent;
blockScopeContainer = savedBlockScopeContainer;
}

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.