Sure, it is basically a dynamic language at compile time, but I feel like the downsides of that are significantly mitigated by the fact that it's running at, you know, compile time -- you'll still get compile-time errors for any mistakes!
(Some people even try to write entire programs in dynamic languages, as crazy as that is.)
Regarding "might as well have a makefile rule," it's not really fair -- in practical terms having a seemless way to evaluate functions in your code at compile time is way different than doing codegen. Like, in practice, how would you write a type-safe format function using codegen? You could do it, but it would be very, very gnarly. Very different.
Even Go -- which famously for a while tried to advocate codegen as an alternative to generics (w/ stuff like stringer to codegen print functions for human names for constants) -- resorts to dynamic types & runtime errors for formatting.
So let's see:
formatting approaches we've seen so far:
– dynamic w/ runtime errors (e.g. Go, C#, C, ...)
– static via baked into compiler support
– static via special-case restricted compile-time evaluated language (e.g. rust macros)
- static via full-lang compile-time eval (e.g. zig)
I think you said all those are awful, so, I'm curious -- what's the better approach you have in mind?