Inline classes as annotation values

The introduction of inline classes is a really nice feature, but they could be even more useful if they can be used as values in annotation classes as well. This feature would be limited to inline classes that are backed by known supported types.

Given the following example:

// Allow inline classes in annotations when backed by primitive types, etc.
annotation class Handler(val priority: Priority = Priority.Normal)

inline class Priority(val value: Int) {

    companion object {

        // Allow constant values for inline classes backed by primitive types, etc.

        const val First = Priority(1000)

        const val Normal = Priority(500)

        const val Last = Priority(0)
    }
}
5 Likes

Thanks for sharing this @Cybermaxke. :+1:

I really like the idea. I think this is something that should be mentioned in the KEEP discussion for inline classes: https://github.com/Kotlin/KEEP/issues/104

1 Like

You can use just regular enums for that case.

enum class Priority(val value: Int) {
    First(1000), Normal(500), Last(0)
}

annotation class Handler(val priority: Priority = Priority.Normal)

No, it wouldn’t be the same. Enums prohibit custom instances like Priority(111), while inline classes allow them.

4 Likes

I posted my proposal in the KEEP discussion. In addition I also added an example where custom instances can be specified when using the annotation:

@Handler(priority = Priority.Last)
fun test() {
}

// Also support for custom instances
@Handler(priority = Priority(600))
fun test() {
}

How handle the class?

inline class Priority(val value: Int) {

    init {
        require ( value > 0)
    }
}

const val Last = Priority(0) invokes the constructor? What happens?
A fail in the requirement raises in the annotated class loading?
How handle future changes in the init block, like require ( value in 1..100)?

1 Like

Since it’s a constant value that will be inlined it will never the call the init block directly. If the init block will ever be supported for inline classes.

There’re two options as far I can see:

  1. Ignore the init block when used in annotations.
  2. Call the init block in the static initializer of the class where the constant is defined.

As far as I know, inline classes do not support init yet. Even if they do, we could limit this use case to inline classes that do not have init.

Third option: don’t support inline classes that have init blocks as the value of annotation arguments.

There is a fourth option. Enforce that the init block can be executed at compile time. That way restrictions like i > 0 can be enforced there.
Arguabley this is a bit more complex and would probably require the existence of constexpr in kotlin first (I think there is an existing issue for that). This could also be added at a later point if we go with @fatjoe79’s option number 3.

Also simple cases like only allowing positive numbers are already part of option 3, since it is possible to use UInt instead of Int.

inline class Priority(val value: UInt)

instead of

inline class Priority(val value: Int) {
    init {
        require(value >= 0)
    }
}