Is there a way to require a generic type to be a data class in Kotlin?

I have the feeling that what you actually want is that T should be able to copy itself with a new ID, and have an ID. Not necessarily that it is a data class. So you could just use an interface to define that.

For example:

interface CopyableWithId<out T> where T: CopyableWithId<T> {
    fun copy(newId: Long): T
    val id: Long
}

data class BarBaz(override var id: Long, var name: String): CopyableWithId<BarBaz> {
    override fun copy(newId: Long): BarBaz = copy(id = newId)
}

class Repository<T> where T : CopyableWithId<T>{

    val map: MutableMap<Long, CopyableWithId<T>> = HashMap()

    fun add(obj: T) {
        val copy = obj.copy(generateID())
        map.put(copy.id, copy)
    }

    private fun generateID(): Long {
        return 1L
    }
}

No, data classes don't have any specific representation in type system and cannot be distinguished from regular classes (similar question).

You can, however, require the methods a data class with certain number of components has using an interface (actually it will be a marker interface on data classes).

Here's an example for data classes with two components:

interface Data2<T1, T2> {
    operator fun component1(): T1
    operator fun component2(): T2
    fun copy(t1: T1, t2: T2): Data2<T1, T2>
}

toString, hashCode and equals can be called on any type anyway.

Then just mark your data class with the interface:

data class Impl(val i: Int, val s: String): Data2<Int, String>

val d: Data2<Int, String> = Impl(1, "2")
val (c1, c2) = d
val copy = d.copy(-1, d.component2())

copy function is not completely type-safe because Kotlin doesn't have self type (and no way to require interface implementations to be subtype of a specific type), but if you only mark your data classes with it, it should work (see another option below).

Another drawback is that you lose default parameters of copy method and have to call it with all the parameters specified:

val d = myD2.copy(newValue, myD2.component2())

Another option is to define these interfaces as Data2<T1, T2, out Self>, class Impl(...): Data2<..., Impl>, and make copy return Self, but it won't make it any better if you use the interface as Data2<SomeType, SomeType, *>.


You also can implement copy or component1, component2 more generalised way.

For example:

    interface Copyable <T> {
        fun copy(fields: T.() -> T): T
    }

    data class BarBaz(var id: Long, var name: String): Copyable<BarBaz> {
        override fun copy(fields: BarBaz.() -> BarBaz): BarBaz {
           val instance = fields(this)
           return copy(id = instance.id, name = instance.name)
        }
    }

class Repository<T> where T : Copyable<T>{

    val map: MutableMap<Long, Copyable<T>> = HashMap()

    fun add(obj: T) {
        val copy = obj.copy{id = generateID()}
        map.put(copy.id, copy)
    }

    private fun generateID(): Long {
        return 1L
    }
}