You can't even decide if a particular "*" is multiplication or a pointer dereference until you resolve templates, which is Turing-complete.
You can't even decide if a particular "*" is multiplication or a pointer dereference until you resolve templates, which is Turing-complete.
So the right implementation choice, for the compiler, is to bull ahead with the common case, and throw the result away and try the other path if you get to an inconsistency. Such inconsistencies practically never happened in production code anyway, and where they did it most usually was, and now almost always is, a result of a bad edit.
So the practical expense is keeping and then GCing old state as it expires, which reduces to stack-like memory management, a solved problem.
We have not needed to go to JITting compile-time constructs, (though it could still happen to expedite constexpr evaluation). Rust might need to, soon, to accommodate parsing under influence of macros, a chore growing with time, not shrinking. (I.e., produce a new parser for each macro added, and jump into it to finish the parse.)
In theory I agree with you that this is horribly ugly - I very much prefer simple grammars (the irony being my favourite language to use is Ruby, which has an atrocious grammar). In practice these parses are rarely that hard to resolve.
You cannot even construct the full parse tree before evaluating a mini-language that is Turing complete. Related article: why C++ compilation is slow (https://digitalmars.com/articles/b54.html)