Feature request: static interface for type bounds

I have a generic function for querying an API that looks something like:

    inline fun <reified T : Any> queryApiX(
        queryName: String,
        accountToken: String,
    ): List<T> {
        val jsonNode = makeRequest(queryName, accountToken)
        return deserialize<T>(jsonNode)
    }

What I’d like is to be able to define the bound T : XQueryableEntity, such that all implementations of XQueryableEntity must have a static/companion object member “queryName”, so none of my call sites have to know about and repeat the query name and it can just be defined on the type:

    class XUser : XQueryableEntity {
        companion object {
            val queryName = "getUsers" // compile-time checked that this exists
        }
    }
    inline fun <reified T : XQueryableEntity> queryApiX(
        accountToken: String,
    ): List<T> {
        // accessible via bounded type parameter
        val jsonNode = makeRequest(T::queryName, accountToken)
        return deserialize<T>(jsonNode)
    }
    val users: List<XUser> = queryApiX(accountToken)

But there doesn’t seem to be any way to inherit any kind of static property that can be accessed in a type-safe way on a type parameter without having an instance of it.

Companion objects are completely independent from their type, so it isn’t possible to express this kind of requirement for a type.

However, you can do something like:

interface XQueryableEntity {
    …
    val metadata: XQueryableEntityMetadata
}

interface XQueryableEntityMetadata {
    val queryName: String
}

class XUser : XQueryableEntity {
    override val metadata get() = Companion

    companion object : XQueryableEntityMetadata {
        override val queryName = "getUsers"
    }
}

Then, from a given XUser, you can get its query name with user.metadata.queryName.

This pattern is used in a few places, most notably in KotlinX.Coroutines’ CoroutineContext.Key. In my own code, you can see it here.

1 Like

This doesn’t help, the point is I need the queryName before I have any given XUser. I should clarify I’ve pretty much gathered you can’t express this kind of requirement, this is essentially a feature request.

One option would be to have your users pass in the implementation explicitly, but it can still hold the type, so that it does double duty as a type inference hint for T:

interface XQueryableEntityCompanion<T: Any> {
  val queryName: String
}
inline fun <reified T : Any> queryApiX(
        companion: XQueryableEntityCompanion<T>,
        accountToken: String,
) = ...

// You can even theoretically have this unsafe method if you want:
inline fun <reified T : Any> queryApiX(
        accountToken: String,
) = queryApiX<T>(T::class.companionObjectInstance, accountToken) // requires kotlin.reflect.full

//Usage
class XUser {
    companion object: XQueryableEntityCompanion<XUser> {
        val queryName = "getUsers"
    }
}
queryApiX(XUser, "foo)