I tried various ways of having a single hierarchy to use for “tagging” and using it to infer the member types of arbitrary “aggeragates” of data I’d like to be related by a type parameter, and ran into various inference limitations:
open class Wrapper<T>(
val value: T,
)
class IntWrapper(
value: Int,
) : Wrapper<Int>(value)
class AnotherWrapper<T>(
val value: T,
)
class WrappersHolder<T>(
val a: Wrapper<T>,
val b: Wrapper<T>,
val c: AnotherWrapper<T>,
val d: T,
)
fun test() {
val myWrappersHolder: WrappersHolder<*> =
WrappersHolder(
Wrapper(1),
Wrapper(2),
AnotherWrapper(3),
4,
)
when (myWrappersHolder.a) {
is IntWrapper -> {
val inferredA = myWrappersHolder.a // Wrapper<out Any?>, doesn't infer properly
val explicitA: Wrapper<Int> = myWrappersHolder.a // but this works
val extractedB: Wrapper<Int> = myWrappersHolder.b // ERROR: initializer type mismatch, actual 'Wrapper<CapturedType(*)>'
val extractedC: AnotherWrapper<Int> = myWrappersHolder.c // ERROR: initializer type mismatch, actual 'AnotherWrapper<CapturedType(*)>'
val extractedD: Int = myWrappersHolder.d // ERROR: initializer type mismatch, actual 'CapturedType(*)'
val inferredWrappersHolder: WrappersHolder<Int> = myWrappersHolder // ERROR: initializer type mismatch, actual 'WrappersHolder<*>'
}
}
}
2nd attempt:
interface TaggedObjectData<out T>
sealed interface TaggedObject<out T, out U : TaggedObjectData<T>> {
val data: U
}
class IntTaggedObject<out U : TaggedObjectData<Int>>(
override val data: U,
) : TaggedObject<Int, U>
class FieldUpdateData<out T>(
val oldValue: T,
val newValue: T,
) : TaggedObjectData<T>
// ERROR: type parameter 'T' of 'interface TaggedObjectData<out T>: Any' has inconsistent type bounds: R, kotlin.Any?
fun <R, U> U.unify(): FieldUpdateData<R> where U : TaggedObjectData<R>, U : FieldUpdateData<*> =
this as FieldUpdateData<R>
fun test() {
val fieldUpdate: TaggedObject<*, FieldUpdateData<*>> = IntTaggedObject(FieldUpdateData(1, 2))
when (fieldUpdate) {
is IntTaggedObject<*> -> {
val e = fieldUpdate.data // TaggedObjectData<Int> & FieldUpdateData<*>
}
}
}
I attempted to manually unify the inferred intersection type with .unify().