I'd like to use this opportunity to advocate for macros as a language feature to allow implementation of other language features, in following with Kotlin's general preference for all things minimal and pragmatic. Some example features that would be naturally implemented as macros:
Lenses. The idea of lenses are exceptionally popular among functional language purists. Broadly speaking, you set up a "getter" and "setter" lambda and they can then be composed together to allow you to reach deeply into some data structure, update one value within, and get out a suitable new object without actually mutating the old one. So, let's say you want to implement lenses for Kotlin. Ideally, you'd have some kind of annotation on a
data class which would auto-generate the lens-related code. To show how this might work, here's a Scala example using the Monacle library. Note how they use the
@Lenses macro to do all the work and then they show you the manually-written alternative? The implementation of the macro requires cases to deal with the complexity of Scala.
Automatic serialization / deserialization. Say you want to do the same thing, as above, but rather than generating code for lenses, you instead want to generate code for JSON or protocol buffers. You could try to implement this with reflection, but you give up compile-time type safety in your implementation, whereas macro-generated code would then be fed back through the type checker as normal. I haven't looked closely at the Kotlin Serialization project, but a good macro system would provide the sort of building blocks you'd want to make serialization simpler. Once again, here's a Scala library that does this with macros, and the macro code reflects the complexity of the source language.
Linear type annotations. All the rage with Rust these days is its use of linear types, guaranteeing that there is never more than one live reference to any given object. This can have some remarkable benefits if you're willing to wrap your head around their intense type system of "owners" and "borrowers". What if somebody wanted to do something like this in Kotlin? With macros, maybe yes, depending on the degree to which the macro system exposed the type system, you could automatically add type annotations and generate checks to enforce them. This sort of thing could be particularly useful for Kotlin/Native, allowing Kotlin and Rust code to interoperate, since both ride on top of LLVM. So, if a Kotlin/Native user really wants to have a linear-typed subsystem, it would be cool if that could be bolted on without having to disturb the core language.
To some extent, there's not all that much difference between a macro system and a compiler plugin infrastructure, except the macro system has hopefully better support for managing language syntax and some degree of soundness, such that the macro system won't easily allow you to generate code that subsequently fails to compile. Without getting lost in a world of lambda calculus and such, I suppose one of the key challenges in supporting any macro system / compiler plug-in infrastructure is what you want to use as your representation. Do you want to define some "core language" and "desugar" Kotlin down to that first?
EDIT: For what it's worth, Rust's macro system seems very pretty. Kotlin could do worse than to borrow from it.