With care to make the build step "ergonomic", I prefer working with code generation than with macros. Everything about macros is "cleverer" than code generation:
* Implementation of macros require clever algorithms [1] where code generation is mostly straightforward.
* It is harder for IDE's (and most importantly, humans!) to make use of the code because it is not trivial to determine the end result of a macro ("run the code in your head" kind of situation).
There are features that a language can adopt to make it even easier to benefit from code generation, like partial classes [2] and extension methods [3]. Again, these things are a lot more straightforward for both humans and IDEs to understand and work with.
In general, if the macro can't be evaluated to determine its effect, It's not a good macro system. One of the things I like about lisp macros is that you can just apply them to a chunk of code and see what the result would be. Contrast C++ templates, where good luck figuring out either how template application will work or what functions you are actually bind when calling templated code.
* Implementation of macros require clever algorithms [1] where code generation is mostly straightforward.
* It is harder for IDE's (and most importantly, humans!) to make use of the code because it is not trivial to determine the end result of a macro ("run the code in your head" kind of situation).
There are features that a language can adopt to make it even easier to benefit from code generation, like partial classes [2] and extension methods [3]. Again, these things are a lot more straightforward for both humans and IDEs to understand and work with.
--
1: https://www-old.cs.utah.edu/plt/publications/popl16-f.pdf
2: https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
3: https://learn.microsoft.com/en-us/dotnet/csharp/programming-...