Package org.ek9lang.compiler.support


package org.ek9lang.compiler.support
Contains critical general components used in the Ek9Compiler listeners.

The SymbolFactory and AggregateManipulator are the primary sources of new Symbols or additions to exiting symbols. There are also other classes like ResolveOrDefineExplicitParameterizedType that take generic types and parameters and create new parameterised types.

So this package is mainly focussed on creation of symbols and validation (to some extent). There are some quite complex processes going on in here, specifically TypeSubstitution.

Another set of classes also deal with the thorny issues around defining and creating parameterised types (but take into account chaining the need to create further dependent parameterised types).

One of the trickiest bits of code is around the creation of parameterised types, this is explained below.

The method ModuleScope.resolveOrDefine(org.ek9lang.compiler.symbols.PossibleGenericSymbol, org.ek9lang.compiler.common.ErrorListener) calls into the CompilableProgram.resolveOrDefine(org.ek9lang.compiler.symbols.PossibleGenericSymbol) This code checks if a parameterised symbol already exists for what is being asked for (the symbol being asked for is not fully defined yet as it is just a form of lookup). This 'lookup' originated in SymbolFactory.newParameterisedSymbol(org.ek9lang.compiler.symbols.PossibleGenericSymbol, java.util.List). That method delegated to ParameterizedSymbolCreator. It was then 'routed via SymbolsAndScopes, this is a critical component used in almost all the 'listeners' that are called when the 'ANTLR' AST is traversed.

Anyway, back to the point; There is a little nuance that needs to be highlighted because it is different from how all other symbols are created. Almost all other symbols are created by the traversal of the AST and then the code that the EK9 developer writes gets pushed into the appropriate symbol (i.e. methods, properties, etc.). But when parameterizing generic types (polymorphic types), we have essentially a 'template' but using conceptual types like 'T' for example.

So when we want to 'define or lookup' a parameterized polymorphic type if that type exists in the appropriate module we just want to reference it. So if the type does exist then we're all good and ResolvedOrDefineResult is returned with the symbol and a flag stating that it is not newly defined.

But if it does not exist, then CompilableProgram will add the 'skeleton' PossibleGenericSymbol to the appropriate module, but will now set the returning flag to new defined. This is really critical, because now that 'skeleton' needs to actually be parameterised. This is done in TypeSubstitution (it is called by ModuleScope and it checks if the type is newly defined or not, if not then it goes about the task of parameterizing it.

The Type Substitution is now driven from the phase of compilation, so the Parameterized Types stay as skeletons for much longer. Only at full resolution are they expanded.

But there's a wrinkle, what if during that parameterization process more parameterized types are needed? The class TypeSubstitution does recursive calls back into the CompilableProgram calling CompilableProgram.resolveOrDefine(org.ek9lang.compiler.symbols.PossibleGenericSymbol) for its needs.

So this is why the construction of parameterized types is in phases, because during parameterization there could (probably will) be references to other parameterized types that are in the process of being created. So by recording the 'skeleton' in the appropriate module, the CompilableProgram will return 'true' for newly defined the first time, but 'false' on additional requests (even though it is still just a skeleton). Eventually (just like any recursive function) the stack of Parameterized types being created will 'unwind', resulting in any newly defined Parameterized types being created/record and then having methods and properties populated with the appropriate types.

The end result is that the first time a Parameterized type is needed it is recorded and populated, but subsequent requests result in that initial type being returned. This is why the recording aspect is done within the CompilableProgram, because access is controlled within a mutex lock.