Tsonnet #42 - If then else (finally)
Implementing conditionals in Tsonnet, from grammar rules to mojito recipes.
Welcome to the Tsonnet series!
If you’re not following along, check out how it all started in the first post of the series.
In the previous post, we fixed function calls with zero arguments:
Now conditionals are on the menu. Literally.
The target
This is the Jsonnet tutorial for conditionals:
We’ll finish this post having this implemented.
A new variant
Let’s add a new expr variant:
The last field is an option because the else branch isn’t always mandatory.
Three new keywords
Nothing complex here. Just some precedence rules to ensure the right evaluation order:
Branch discipline
translate_conditional type checks the condition and both branches:
When the else branch is absent, the then branch is the return type of the entire expression. When both are present, they must match — otherwise we raise a type mismatch error.
This is a deliberate design choice. TypeScript would return a union type like string | number, which is flexible and plays well with dynamic languages, but it adds complexity. Most statically typed languages require both branches to be the same type when returning an expression — setting aside languages that treat conditional branches as statements, those are a different story.
And the new error message:
The boring part (by design)
The interpreter is the boring part. It just picks one branch or the other:
The unreachable branch at the end is starting to bother me. The type checker already guarantees the condition is a Bool, so that match arm will never fire. I keep thinking about using phantom types to encode this statically and let the compiler enforce it, instead of leaving a comment that future me will pretend not to see. Something to revisit.
Tests
And it works:
The cram tests will keep it honest:
Conclusion
Conditionals are in. Not a lot of ceremony for something this fundamental — which is usually the sign of a clean design paying off.
Next up: probably, for loops and comprehensions.
The entire diff is available here.











