Bug in 'Where'?

interface TypeSafeService<MARKER : Marker<*>> {
  
    fun <T : Any, F> getObject(
        objClass: KClass<F>
    ): T where F: Marker<T>, F: MARKER
}

interface Marker<T: Any> {
    fun markerType() : KClass<T>
}

I need that an objClass must implement interface Marker<T> and function getObject(...) returns for it value of T.

Why for this case compilation says Type parameter cannot have any other bounds if it's bounded by another type parameter ?

Is it possible to get an error during runtime for this code?

Or are there some new approaches in Kotlin 2.2.0 to get same result?

UPD
The idea is to have an implementation of TypeSafeService that could work only with a specific implementation of a Marker interface and with other implementations of Marker interfaces should be a compilation error:

import kotlin.reflect.*
interface TypeSafeService<MARKER : Marker<*>> {

    @Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
    fun <T : Any, F> getObject(
        objClass: KClass<F>
    ): T where F: Marker<T>, F: MARKER
}

class DefaultTypeSafeService<MARKER: Marker<*>> : TypeSafeService<MARKER> {
    @Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
    override fun <T : Any, F> getObject(objClass: KClass<F>): T where F : Marker<T>, F : MARKER {
        TODO("not implemented")
    }

}

interface Marker<T: Any> {
    fun markerType() : KClass<T>
}

// I need to declare different marker interfaces
// first marker interface:
sealed interface Marker1<T: Any>: Marker<T>
object IntMarker1: Marker1<Int> {
    override fun markerType() = Int::class
}
object StringMarker1: Marker1<String> { override fun markerType() = String::class }

// second marker interface:
sealed interface Marker2<T: Any>: Marker<T>
object IntMarker2: Marker2<Int> {
    override fun markerType() = Int::class
}
object StringMarker2: Marker2<String> { override fun markerType() = String::class }

fun foo() {
    val marker1Service =  DefaultTypeSafeService<Marker1<*>>() // could only be used with Marker1
    val marker2Service =  DefaultTypeSafeService<Marker2<*>>() // could only be used with Marker2
    
    val int1: Int = marker1Service.getObject(IntMarker1::class) // OK 
    val string1: String = marker1Service.getObject(StringMarker1::class) // OK 
    val int2: Int = marker2Service.getObject(IntMarker2::class) // OK 
    val string2: String = marker2Service.getObject(StringMarker2::class) // OK 
    
    marker1Service.getObject(IntMarker2::class) // this should not be allowed
    marker2Service.getObject(IntMarker1::class) // this should not be allowed
}

It’s a Java interop limitation IIRC. You can suppress the error @Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER"). In your situation, I think this would suffice, though:

interface TypeSafeService<MARKER : Marker<MARKER>> {
  
    fun <T : MARKER> getObject(
        objClass: KClass<out T>,
        context: EvaluationContext? = null
    ): T
}

interface Marker<out T: Any> {
    fun markerType() : KClass<out T>
}

But in this case:

interface TypeSafeService<MARKER : Marker<MARKER>> {
  
    fun <T : MARKER> getObject(
        objClass: KClass<out T>,
        context: EvaluationContext? = null
    ): T
}

interface Marker<out T: Any> {
    fun markerType() : KClass<out T>
}

I can’t use one implementation TypeSafeService with different Marker implementation:

typeSafeServiceImpl.getObj(Marker<Int>::class)
typeSafeServiceImpl.getObj(Marker<Boolean>::class)

Do you have a better example for what usage looks like? In particular, what’s the purpose of MARKER as a type parameter?

If I understand your use case right, here’s a version that works pretty well:

import kotlin.reflect.*
interface TypeSafeService {
    fun <F : Marker<T>, T: Any> getObj(objClass: KClass<F>): T
}

inline fun <reified F: Marker<T>, T: Any> TypeSafeService.getObj(): T = getObj(F::class)

interface Marker<T: Any> {
    val markerType : KClass<T>
}

object IntMarker: Marker<Int> { override val markerType = Int::class }
object StringMarker: Marker<String> { override val markerType = String::class }

fun foo(typeSafeServiceImpl: TypeSafeService) {
    typeSafeServiceImpl.getObj<IntMarker, _>()
	typeSafeServiceImpl.getObj<StringMarker, _>()
}

Sorry, I missed some details. The idea is to have an implementation of TypeSafeService that could work only with a specific implementation of a Marker interface and with other implementations of Marker interfaces should be a compilation error:

import kotlin.reflect.*
interface TypeSafeService<MARKER : Marker<*>> {

    @Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
    fun <T : Any, F> getObject(
        objClass: KClass<F>
    ): T where F: Marker<T>, F: MARKER
}

class DefaultTypeSafeService<MARKER: Marker<*>> : TypeSafeService<MARKER> {
    @Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
    override fun <T : Any, F> getObject(objClass: KClass<F>): T where F : Marker<T>, F : MARKER {
        TODO("not implemented")
    }

}

interface Marker<T: Any> {
    fun markerType() : KClass<T>
}

// I need to declare different marker interfaces
// first marker interface:
sealed interface Marker1<T: Any>: Marker<T>
object IntMarker1: Marker1<Int> {
    override fun markerType() = Int::class
}
object StringMarker1: Marker1<String> { override fun markerType() = String::class }

// second marker interface:
sealed interface Marker2<T: Any>: Marker<T>
object IntMarker2: Marker2<Int> {
    override fun markerType() = Int::class
}
object StringMarker2: Marker2<String> { override fun markerType() = String::class }

fun foo() {
    val marker1Service =  DefaultTypeSafeService<Marker1<*>>() // could only be used with Marker1
    val marker2Service =  DefaultTypeSafeService<Marker2<*>>() // could only be used with Marker2
    
    val int1: Int = marker1Service.getObject(IntMarker1::class) // OK 
    val string1: String = marker1Service.getObject(StringMarker1::class) // OK 
    val int2: Int = marker2Service.getObject(IntMarker2::class) // OK 
    val string2: String = marker2Service.getObject(StringMarker2::class) // OK 
    
    marker1Service.getObject(IntMarker2::class) // this should not be allowed
    marker2Service.getObject(IntMarker1::class) // this should not be allowed
}
1 Like

Thanks for the example!

I’m not sure if there’s a workaround for the code in its current form other than the suppress. However, you can massage it a bit by introducing a second type parameter:

import kotlin.reflect.*
interface TypeSafeService<MARKER_TYPE> {

    fun <T : Any, F: Marker<T, MARKER_TYPE>> getObject(objClass: KClass<F>): T
}

class DefaultTypeSafeService<MARKER_TYPE> : TypeSafeService<MARKER_TYPE> {
    override fun <T : Any, F: Marker<T, MARKER_TYPE>> getObject(objClass: KClass<F>): T {
        TODO("not implemented")
    }

}

interface Marker<T: Any, MARKER_TYPE> {
    fun markerType() : KClass<T>
    // if you want, you can have extra methods on MARKER_TYPE that can be accessed safely
    // val extra: MARKER_TYPE
}

// I need to declare different marker interfaces
// first marker interface:
sealed interface Marker1<T: Any>: Marker<T, Marker1.Companion> {
    companion object
}
object IntMarker1: Marker1<Int> {
    override fun markerType() = Int::class
}
object StringMarker1: Marker1<String> { override fun markerType() = String::class }

// second marker interface:
sealed interface Marker2<T: Any>: Marker<T, Marker2.Companion> {
    companion object
}
object IntMarker2: Marker2<Int> {
    override fun markerType() = Int::class
}
object StringMarker2: Marker2<String> { override fun markerType() = String::class }

fun foo() {
    val marker1Service =  DefaultTypeSafeService<Marker1.Companion>() // could only be used with Marker1
    val marker2Service =  DefaultTypeSafeService<Marker2.Companion>() // could only be used with Marker2
    
    val int1: Int = marker1Service.getObject(IntMarker1::class) // OK 
    val string1: String = marker1Service.getObject(StringMarker1::class) // OK 
    val int2: Int = marker2Service.getObject(IntMarker2::class) // OK 
    val string2: String = marker2Service.getObject(StringMarker2::class) // OK 
    
    marker1Service.getObject(IntMarker2::class) // this should not be allowed
    marker2Service.getObject(IntMarker1::class) // this should not be allowed
}
1 Like

Thanks a lot!

1 Like