Dear Santa,
What I really want for xmas is to have a platform that will take Roc code, which would have to produce another Roc AST which would in turn get compiled again and turned into executable.
Write Roc code that produces more roc code. Big important caveat, I don't want to generated code communicate with parent code. Just based on some Roc.roc to produce more projects and Roc expressions and types, which should be type checked on its own right.
When you say you don't want it to communicate with parent code, what does that mean, specifically (what would be the mechanism of that communication), and why would such communication be a problem?
I would assume it means the generated code being able to refer to variables from wherever it was generated? Macros in Rust can do this for example.
But if you don't have that then it's basically generating strings to dump to a file.
So what kind of support would help with that @Zeljko Nesic ?
Wonder if it's more a feature of the build system than the compiler.
My initial impulse was to have something similar to Haskell's Servant library, but without type-level magic.
One way to being able to archive this to take some definitions, and build out concrete types of the API.
@Brian Carroll You are probably right, this should be just setting some circular pipes in the build system, but still I wonder, maybe there is a better way. That is why I was asking Santa for help. :)
Right. I think the "circular" issue could be dealt with by doing it in passes.
Run a compilation pass. If it generated more Roc code, do another pass. If not, we are finished compiling.
After compiling, run the linker on all the object files from all the passes.
Maybe there's also a case for generating something more structured than text. Or a builder library for code strings.
What would be the practical use case for multiple rounds of code generation? Allowing more than one round would permit compilation to never end, unless we set a limit on the number of rounds, but a limit other than one feels arbitrary to me (3? is that enough? 10? is the programmer doing the right thing if they need 10?)
Would such generation be deterministic and cacheable, such that the IR or result of, possibly multiple stages of generation, do not need to be reevaluated on subsequent builds if the input hasn't changed, and such that multiple distinct compilation phases, including this kind, can be cached together?
No idea, I had no practical purposes in mind, just thinking about how to implement it and handling edge cases out of habit.
But even one level of code generation would be huge!
Why I think this should be actually "compilers" thing, because it would be really sweet if from the first pass compiler would tell you:
dude, this Roc code that I am about to generate doesn't make sense. And then points to offending ~area~ of code.
This also will be step closer to our vision of :octopus: :working_on_it: :tada: the editor :tada: :working_on_it: :octopus:
Apologies for awakening an old thread — I think this is a really wise and prudent idea and I’d love to expand it to other kinds of analyser in the tool chain.
Fairly recently, the .Net team rolled out Source Generators which have a few really wise traits that are worth considering:
1) they are part of the compiler/analyzer chain so the integrate nicely
2) they only allow for one pass (deterministic and cacheable), and they document (I’ll try to dig out the link) exactly why that is
3) they run in real-time in their IDE (Visual Studio) as you code
4) they can never see output from other analysers/deterministic generators (otherwise they’d have ordered dependencies andor not be deterministic; either way DX mess)
What they don’t do, which surprises me and I have t found a justification for:
1) support real time code generation in other IDEs (even their own VS Code)
2) support outputting to a normalised AST (they generate strings, which are passed to the next tool in the chain) —- fortunately they do have access to the syntax tree to analyse the user code
The best example I’ve seen is code which turns a (checked in) openapi.json file and generates types and a client in the IDE as “real” code which can be stepped through and inspected as if it were written.
The same goes with analysers — source generators and analysers come straight from the package manager and integrate into the compiler with no configuration, which is something Roc could excel at.
Aside: I think deciding between IDE analysers vs tool chain analysers is going to be contentious and nuanced for the same reason that writing a language server for external IDEs is discouraged
Last updated: Jun 16 2026 at 16:19 UTC