inheritancekotlinclass-extensions

kotlin class extensions resolve parent class's value instead of childs when used in list context


I'm working with class extensions in Kotlin (1.3.20), and I'm running into an issue when extending both the parent class and the child class on the same property and then using instances of those an a list.

Basically what happens is that the child class's instance returns the value set for the parent class's property, and I dont't understand why that is. I expect line 16 in the code below to return "ext-special-thing" but it does return "ext-thing" even though the instance in b[1] definitely is of type ExtSpecialThing.

I suspect the cause is the way extension-properties/extension-functions work under the hood (btw: this issue exists with extension functions as well); but I'm not an expert in that regard.

tl;dr: line 16 fails ... why?

import kotlin.test.*

fun main (args : Array<String>) {
    assertEquals("ext-special-thing", ExtSpecialThing().prop)

    var a = listOf(ImplThing(), ImplSpecialThing())
    assertTrue(a[0] is ImplThing)
    assertTrue(a[1] is ImplSpecialThing)
    assertEquals("impl-thing",         a[0].prop)
    assertEquals("impl-special-thing", a[1].prop)

    var b = listOf(ExtThing(), ExtSpecialThing())
    assertTrue(b[0] is ExtThing)
    assertTrue(b[1] is ExtSpecialThing)
    assertEquals("ext-thing",         b[0].prop)
    assertEquals("ext-special-thing", b[1].prop) // fails ... why?
}

// ======================================
open class ImplThing () {
    open val prop : String = "impl-thing"
}
class ImplSpecialThing : ImplThing() {
    override val prop : String = "impl-special-thing"
}

// -------------------------------------
open class ExtThing () {}
class ExtSpecialThing : ExtThing () {}

val ExtThing.prop : String get() = "ext-thing"
val ExtSpecialThing.prop : String get() = "ext-special-thing"

Solution

  • This is described in the official docs:

    We would like to emphasize that extension functions are dispatched statically, i.e. they are not virtual by receiver type. This means that the extension function being called is determined by the type of the expression on which the function is invoked, not by the type of the result of evaluating that expression at runtime.

    So that means that the following will make your test successful:

    assertEquals("ext-special-thing", (b[1] as ExtSpecialThing).prop)