https://kotlinlang.org logo
#compiler
Title
# compiler
d

dave

09/20/2023, 7:47 AM
We've noticed that Context Receivers don't enforce types in the way that we would expect. Is it by design that this compiles?
Copy code
data object Ctx1
data object Ctx2

interface MyThing<Ctx> {
    context(Ctx)
    fun foo(): String
}

class Ctx1Thing : MyThing<Ctx1> {
    context(Ctx2) override fun foo() = "bob"
}
j

Javier

09/20/2023, 8:10 AM
Does it compile via terminal or just no IDE warning/error?
d

dave

09/20/2023, 8:14 AM
It compiles both in Idea and through our gradle build. We're using: openjdk 20.0.2 Kotlin 1.9.10
j

Javier

09/20/2023, 8:17 AM
I think it shouldn’t as it shouldn’t if you put those context as parameters of foo. Only if those objects would extend the same interface
n

natpryce

09/20/2023, 8:18 AM
Using David’s code, this main method compiles but fails at runtime with a ClassCastException:
Copy code
fun main() {
    val myThing : MyThing<Ctx1> = Ctx1Thing()
    
    with(Ctx2) {
        with(Ctx1) {
            myThing.foo()
        }
    }
}
Copy code
Exception in thread "main" java.lang.ClassCastException: class example.signup.Ctx1 cannot be cast to class example.signup.Ctx2 (example.signup.Ctx1 and example.signup.Ctx2 are in unnamed module of loader 'app')
	at example.signup.Ctx1Thing.foo(Example.kt:11)
	at example.signup.ExampleKt.main(Example.kt:20)
	at example.signup.ExampleKt.main(Example.kt)
d

dave

09/20/2023, 8:18 AM
That still doesn't fail to compile - it actually was our original version before we simpified the example 🙂
Copy code
interface MyCtx

data object Ctx1 : MyCtx
data object Ctx2 : MyCtx

interface MyThing<MyCtx> {
    context(MyCtx)
    fun foo(): String
}

class Ctx1Thing : MyThing<Ctx1> {
    context(Ctx2) override fun foo() = "bob"
}
And as @natpryce has pointed out - we get a class cast at runtime 😞
j

Javier

09/20/2023, 8:20 AM
I mean if you want to get the same behavior you can remove context and put them as foo parameters. context is just sugar for that. The compiler is not catching this issue with context but it should
n

natpryce

09/20/2023, 8:24 AM
Agreed. Contexts are a convenient way of passing parameters to methods without repetitive code, and so should be typechecked in the same way.
j

Javier

09/20/2023, 8:26 AM
Copy code
class Ctx1Thing : MyThing<Ctx1> {
    context(Ctx2) override fun foo() = "bob"
}
This can't work as
Ctx1
is not related to
Ctx2
The next would fail too
Copy code
interface MyCtx

data object Ctx1 : MyCtx
data object Ctx2 : MyCtx

interface MyThing<MyCtx> {
    fun foo(param: MyCtx): String
}

class Ctx1Thing : MyThing<Ctx1> {
    override fun foo(param: Ctx1) = "bob" // works
    override fun foo(param: Ctx2) = "bob" // fails
}
Probably this issue is already reported in YouTrack
d

dave

09/20/2023, 8:26 AM
But it doesn't fail! 🙂
That's why I was asking here. If we can confirm that this is not designed behaviour then that's fine. But for a "feature" of a statically types language to not be actually type checked seems like a bug. 🙂
I've raised this to cover the problem. Please upvote 🙂