As discussed previously,
dotc maintains time-indexed views of various compiler artifacts. The following sections discuss how they are managed in the compiler.
Defined in Symbols, a
Symbol is a unique identifier for a definition (e.g. a method, type, or field). A
Symbol and represents either a
class, or a
trait, or an
Symbol can even refer to non-Scala entities, such as from the Java standard library.
Definitions are Dynamic
Traditionally, compilers store context-dependent data in a symbol table. Where a symbol then is the central reference to address context-dependent data.
dotc instead uses a phase-indexed function (known as a Denotation) to compute views of definitions across phases, as many of attributes associated with definitions are phase-dependent. For example:
- types are gradually simplified by several phases,
- owners change in lambdaLift (local methods are lifted to an enclosing class) and flatten (when inner classes are moved to the top level)
- Names are changed when private members need to be accessed from outside their class (for instance from a nested class or a class implementing a trait).
Additionally, symbols are not suitable to be used as a reference to a definition in another compilation unit. In the context of incremental compilation, a symbol from an external compilation unit may be deleted or changed, making the reference stale. To counter this,
dotc types trees of cross-module references with either a
TypeRef. A reference type contains a prefix type and a name. The denotation that the type refers to is established dynamically based on these fields.
On its own a
Symbol has no structure. Its semantic meaning is given by being associated with a Denotation.
A denotation is the result of resolving a name during a given period, containing the information describing some entity (either a term or type), indexed by phase. Denotations usually have a reference to a selected symbol, but not always, for example if the denotation is overloaded, i.e. a
All definition symbols will contain a
SymDenotation. The denotation, in turn, contains:
- a reverse link to the source symbol
- a reference to the enclosing symbol that defined the source symbol:
- for a local variable, the enclosing method
- for a field or class, the enclosing class
- a set of flags, describing the definition (e.g. whether it's a trait or mutable).
- the type of the definition (through the
- a signature, which uniquely identifies overloaded methods (or else
- and more.
A class symbol will instead be associated with a
ClassDenotation, which extends
SymDenotation with some additional fields specific for classes.