Hello, Is it possible to use KSP to replicate wha...
# ksp
t
Hello, Is it possible to use KSP to replicate what is done by derive in Rust? With editor support. For instance, with
data class
, compiler will generate some standard code. With
suspend
it will perform some transformation as well. Same with
inline
. That’s for keywords, but there are also
@Compose
for Jetpack Compose and
@Serializable
for Kotlinx.serialization. The last one is really cool. It will generate all of what is needed for de/serialization, and methods such as
Json.decodeFromString<Person>(string)
are showing errors right away in the IDE if Person is not annotated with
Serializable
. This does not use KSP (I think), but a compiler plugin. Would KSP be able to reproduce the same experience before execution?
Copy code
import com.me.WithMyFeature
import com.me.useFeature

@WithMyFeature
class Dog

class Person

val dog = Dog()
val person = Person()

dog.useFeature() // No issue

person.useFeature() // Issue
I saw this kind of solution, but that would only work at runtime :/
Copy code
@Suppress("UNUSED_PARAMETER")
fun Any.useFeature() {
  throw NotImplementedError("missing implementation")
}
c
Derive is a proc macro, KSP has some of the same power but not all. For instance it can read the most of the langs AST but nothing expression level. Similarly it can generate new code but can’t edit existing code. For your example yes you could just generate an extension function for the annotated type.
Also note unlike proc macros which work on token streams, ksp works but emitting kotlin source files which are included in the compilation
j
One way to achieve compile-time error reporting for generated functions is to ~ab~use extension function precedence and
@RequiresOptIn
. In your base library you have something like:
Copy code
package myfeature

@RequiresOptIn(
  message = "Missing implementation",
  level = RequiredOptIn.Level.ERROR
)
internal annotation class UseFeaturePlaceholder

@UseFeaturePlaceholder
fun Any.useFeature() = error("Missing implementation")
If you call
foo.useFeature()
you’ll get a compile error with “Missing implementation”. And no one can “opt-in” to the feature because the annotation to do so is internal. Then in your KSP generator you generate the same method signature with a more specific type:
Copy code
package myfeature

// Generated by KSP
fun String.useFeature() = "Real string from feature"
fun Int.useFeature() = 5
Because these have a more specific type but are in the same package they will transparently override the less specific placeholder signature. So now
"hello".useFeature()
will compile, as will
10.useFeature()
, but
true.useFeature()
will report a missing implementation. The other benefit is that you get a level of auto-completion even if you haven’t run KSP for the first time yet. Depending on the generic signature of
useFeature
it’s usually good enough to get you where you need to go