https://kotlinlang.org logo
#ksp
Title
# ksp
j

Jakub Syty

03/28/2023, 1:57 PM
I'm trying to create an annotation in which i want to pass an array of "other" annotations, but i don't know types of those ahead. Basically i want user to pass whatever annotations they want there. From docs i read that i can use other annotations as a parameter of annotation. I can do
annotation class MyAnnotation(val list: Array<OtherKnownAnnotation>)
. There is a interface called
Annotation
that is
Base interface implicitly implemented by all annotation interfaces.
. So i tried to do
annotation class MyAnnotation(val list: Array<Annotation>)
. This unfortunately gives me an error
Invalid type of annotation member
. Can i work around this? Anybody had a usecase like this?
Maybe i can expand on what i want to achieve. My annotation will be applied to interface. I want to generate class that implements that interface and have the annotation passed by the user applied. So if user wants the implementation to be injected for example in koin he could annotate the interface like:
@MyAnnotation(list = [Single(binds = [UserType::class])])
. Is that even possible? 🙂
n

Nicklas Jensen

03/28/2023, 3:31 PM
I don’t believe that’s possible. Could you do what you’re trying to do differently by using multiple annotations, e.g:
Copy code
@MyAnnotation
@Single(binds = [UserType::class])
j

Jakub Syty

03/28/2023, 3:35 PM
unfortunately not, since
@Single
works only on classes and not interfaces ;/
that's the main limitation i want to get over
even if i could get around the limitation of application to interface - the koin would then just outright crash the ksp process since i cannot apply
@Single
to classes that cannot be create (so doing abstract class instead of interface results in an error from koin that it cannot create an instance of abstract class. So it's more like i need to defer the processing of the original annotation to the next step - after my implementation is generated
n

Nicklas Jensen

03/28/2023, 6:36 PM
Is it a fixed known set of annotations you need to process? E.g. is it “just”
@Single
and
@Factory
from Koin?
j

Jakub Syty

03/28/2023, 8:24 PM
the goal is to accept anything that can be applied to class. But i could keep some known set and just make it bigger. But i don't see w way to handle that either
i still need some common abstraction to accept the list
n

Nicklas Jensen

03/29/2023, 4:54 AM
I see. I don't believe there's any way to have annotations through abstraction, at least not while providing arguments for its parameters. You could definitely do something to support Koin's annotations functionality explicitly, but I don't believe supporting arbitrary annotations is possible
j

Jakub Syty

03/29/2023, 7:30 AM
That's a shame, i was hoping that this might be possible since the
Annotation
is the implicit interface of all annotations ;/
n

Nicklas Jensen

03/29/2023, 8:01 AM
While that path is not possible, there is an alternative way you can achieve what you're trying to achieve. Assuming you have your annotation
MyAnnotation
Copy code
annotation class MyAnnotation
You can introduce another annotation
@Annotate
like this:
Copy code
@Repeatable
annotation class Annotate(
    val annotation: KClass<*>,
    val members: Array<Member> = [],
)
And a third annotation e.g.
Member
like this:
Copy code
annotation class Member(
    val name: String,
    val value: String,
    val types: Array<KClass<*>> = []
)
You can use it like this:
Copy code
@MyAnnotation
@Annotate(
    annotation = Single::class,
    members = [
        Member("binds", "[%T]", [UserType::class]),
    ]
)
interface FooBar
Then in your symbol processor you can find all types with your annotation
@MyAnnotation
and find all that type's
@Annotate
annotations, then, using KotlinPoet you can create an
AnnotationSpec
like so:
Copy code
fun Annotate.toAnnotationSpec(): AnnotationSpec {
    val type = annotation.asClassName()
    val builder = AnnotationSpec.builder(type)
    members.forEach { builder.addMember("${it.name} = ${it.value}", *it.types) }
    return builder.build()
}
By doing this you're asking your users to declare annotations in a way that's friendly to KSP and KotlinPoet, but it will definitely work 🙂
j

Jakub Syty

03/29/2023, 8:21 AM
while it is really clever and fun to implement, i think asking users to do that i too much
And it's not really typesafe anymore, so here goes the advantage of annotations 🙂
n

Nicklas Jensen

03/29/2023, 9:06 AM
While not type-safe in the IDE, it will effectively be type-safe since Kotlin will compile your generated code and if the generated code is invalid or not type-safe, the compilation will fail 🙂. You can also run your own type checks in your symbol processor to provide clear compilation errors to the user
But I agree that it's asking a bit much from users. If your symbol processor ends up solving a big burden for a user, they may well think it's worth it
156 Views