For my own education and fun times, I’m exploring the compiler, with a particular eye for incremental compilation. I don’t have a realistic expectation of delivering it, but I’m curious what obstacles I’ll run into along the way, and at the very least will have a deeper appreciation for the crystal core team :)
As a starting point, my first goal is to see if I could build something in my first point from this post, specificaly:
A new crystal tool that adds missing typing information to methods after type inference is complete (would operate similarly to the existing
format
crystal tool in my mind) to quickly add typing everywhere it’s needed.
For my trivial case, I’m using this basic crystal program as my proof-of-concept:
def hello!
"Hello World!"
end
puts hello!
With the inferred type String
as the intended type for that hello!
method.
Using the good ol’ print debugging method, I’ve discovered:
- The
Program
object is the container for all parsed and processed compiler data - Running
program.semantic
on the parsed code is where all of the semantic logic and inference gets applied. - After running
semantic
, there are two variables on it that contain references to the"hello!
definition:defs
anddef_instances
program.defs
(which is nillable) contains a reference to the parsed function definition →program.defs.not_nil!["hello!"]
. This is actually an array ofDefWithMetadata
, withDefWithMetadata#def
being the actual definition.- Since the function definition has no return type,
program.defs.not_nil!["hello!"][0].def.return_type
returnsnil
program.def_instances
contains the actual invocations / resolved definitions, but uses aDefInstanceKey
as the key, which (among other properties, uses thedef_object_id
as the main part of the key.- This little blurb gets the resolved type for the
hello!
function:
# This is inefficient, but whatever
key = program.def_instances.keys.find! do |k|
k.def_object_id == program.defs.not_nil!["hello!"][0].def.object_id
end
program.def_instances[key].return_type # => nil
program.def_instances[key].type # => String
Some additional experimentation along those lines shows the same type of inferencing and typing happens on the method arguments as well, woot! My next step will be a rudimentary tool as a first stab at the above quote block. As an aside, finding a way to serialize and deserialize from file the Program
object is probably where incremental compilation would need to explore more.
I actually don’t know what I intended with this post - I think I started it as a place to post questions about the internals of the compiler, but as I typed them out, I came up with new experiments to learn those answers on my own. So now I’ll use it as a mini-brain dump and very rough “documentation” on how the compiler works for anyone else (including future me) to learn from.
Hope it helps someone!