Tooling can mean many different things, right from the low level of the binary code which implements macro transformations, up to the high level of the IDE where you are implementing or using your macro, which is what abreslav is mentioning. Since macros can change the meaning of the code they encompass, you start having problems like deciding which kind of source code you want to show to the user.
Imagine having a macro which prefixes all the properties with a string, uppercasing naturally the original variable during concatenation of the identifier:
someclass {
prefix("longPrefix") {
val a: String = ""
}
fun bar() {
println("Look at $longPrefixA")
}
}
Imagining that this compiled and worked, you have difficult to answer design decisions, because you are displaying the original source before the macro transformation… but wouldn't it be useful to also see the code after? Also, part of the source code depends on the macro being run, which in turn means that the IDE has to actually compile and run your code while you are typing to validate that the reference to $longPrefixA
is valid. In a way, you would need a compilation time debugger for the compiler/IDE just like you have your usual debugger for the runtime, if you are writing the macro in first place.
There are even more troubles when you think about control flow macros and such where the compilation abstract syntax tree is modified in non obvious ways, which is also one of the reasons macros get bad reputation for hiding the program (though I argue that comes mainly from our C textual preprocessor biased history). Even when you look at other programming languages with built in macro support, their tooling tends to be lackluster, in a way perpetuating the meme.