Skip to content

Instantly share code, notes, and snippets.

@retronym
Created May 8, 2018 03:09
Show Gist options
  • Save retronym/13036687df355c1eb754ce24fd3f992c to your computer and use it in GitHub Desktop.
Save retronym/13036687df355c1eb754ce24fd3f992c to your computer and use it in GitHub Desktop.
The general pattern of Namer is:
- Enter: create a symbol for a XDef tree, enter in in the enclosing scope, and set its info to be a type completer (aka lazy type) that ends up calling into templateSig/classSig/methodSig etc the first time `thatSymbol.info` is called, which can either be when the typer phase makes it to that spot in that compilation unit, or could be when some other typechecking needs that type earlier on.
- Complete: Create the MethodType/ClassInfoType/etc for the symbol. This can recursively enter symbols for members of the class/object/package. It can also invoke the typechecker (e.g. to typecheck the parent types of a class, or to look up the inheritance chain for the overriden method that might have defaults that are relevant to this method symbol.
The lazy parts of this process capture the relevant `Namer` instance, which has a `Typer` focussed at the right spot in the tree.
This process can create symbols for synthetic methods. For instance, in the type completer for a case class, the `copy` method of a case class is created. The synthetic symbol is added to the ClassInfoType then and there, but there is no direct way add the tree to the compilation unit (as trees are immutable, at least in their structure). So instead, the tree is stashed in a map (unit.synthetics), and the typechecker adds it to the post-typer tree at the right spot.
In the example I gave you, I only used context.synthetics to create a placeholder tree, and filled in the RHS post-typer. My worry was that the logic to generate that RHS _might_ end up creating a spurious cyclic reference error if it was done eagerly. There isn't a more direct way to express the right sort of laziness in the current Namer/Typer handoff, but I'll think about improving that.
So far, I haven't talked about companions. They contribute the remaining complexity to this dance. The problem is dealing with the variations of a) did the user write the companion explicitly or not? and b) if so, is the companion declared before or after the class? And then, how can we make the tree of the ClassDef available when needed to create the synthetic members of the ModuleDef.
The implementation of case classes deals with this by stashing the ClassDef on a tree attachment of that companion symbol when entering the ClassDef, eagerly creating the module symbol if it hasn't already been created by an explicit, preceding definition of the module.
I copied this approach in my example. It's pretty clunky, and maybe we should refactor Namer to make the link between companions more of a first class thing, and always make both the ClassDef and ModuleDef available to the type completers of both.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment