K2 breaking changes when using context

I have an example with some uncommon approach to use context, but it was working in 1.9.24. In 2.0 is failing and I don’t know how to solve it. The code is:

import kotlin.reflect.*

open class Property<T, V: Any>(val name: String)
class NullableProperty<T, V: Any>(name: String): Property<T, V>(name)

open class Field<T, V: Any>(name: String): Property<T, V>(name)
class NullableField<T, V: Any>(name: String): Field<T, V>(name)

open class Reference<T, V: Any>(name: String): Property<T, V>(name)
class NullableReference<T, V: Any>(name: String): Reference<T, V>(name)

interface Model<T: Model<T>> {
    fun string(name: String) = Field<T, String>(name)
    fun stringList(name: String) = Field<T, List<String>>(name)
    
    fun boolean(name: String) = Field<T, Boolean>(name)
    fun booleanList(name: String) = Field<T, List<Boolean>>(name)

    fun <V: Model<*>> ref(name: String) = Reference<T, V>(name)
    fun <V: Model<*>> refList(name: String) = Reference<T, List<V>>(name)
    
    fun <V: Any> computed(name: String, result: Dependency<T>.() -> V) = Property<T, V>(name)
}


class Dependency<T> {
	operator fun <V: Any> Property<T, V>.invoke() = "DEP" as V
}


typealias OnChange<K> = context(K) Change<K>.() -> Unit

@DslMarker
annotation class MyDsl

@MyDsl
class Change<T>(private val id: String? = null, private val parent: Change<*>? = null) {
    private val changes = mutableMapOf<Field<T, *>, Any>()
    private val childs = mutableMapOf<Reference<T, *>, Change<*>>()
    
    infix fun <V: Any> Field<T, V>.set(value: V) =
    	changes.put(this@Field, value)
    
    infix fun <V: Model<*>> Reference<T, V>.set(onChange: OnChange<V>): Unit {
        val item = AddressView as V // get from store
        
        val result = Change<V>(null, this@Change)
        onChange(item, result)
        childs.put(this@Reference, result)
    }
    
    infix fun <V: Model<*>> Reference<T, List<V>>.on(id: String): Reference<T, V> {
        return Reference<T, V>("$name:$id")
    }
    
    override fun toString() = """
    |  id: ${id}
    |  changes: ${changes}
    |  childs: ${childs}
    """.trimMargin()
}

fun <T: Model<*>> change(id: String, onChange: OnChange<T>): Change<T> {
    val item = UserView as T // get from store

    val result = Change<T>(id)
    onChange(item, result)
    return result
}

//----------------------------------------------------------------------------------
object UserView: Model<UserView> {
    val name = string("name")
    val email = string("email")
    val isActive = boolean("isActive")
    val someList = stringList("someList")
    val address = ref<AddressView>("address")
    val manyAddress = refList<AddressView>("manyAddress")
}

object AddressView: Model<AddressView> {
    val country = string("country")
    val city = string("city")
    val address = computed("address") {
        "${country()} from ${city()}"
    }
}

fun main() {
    val changes = change<UserView>("user-id") {
        name set "Alex"
        isActive set true
        someList set listOf("One", "Two")
        
        address set {
            country set "Portugal"
        }
        
        for (id in listOf("one", "two"))      
            manyAddress on id set {
                city set "$id-Aveiro"
            }
    }
    
    println(changes)
}

Seems to be an issue with type aliases. Inlining the type-alias manually fixes the issue:

import kotlin.reflect.*

open class Property<T, V: Any>(val name: String)
class NullableProperty<T, V: Any>(name: String): Property<T, V>(name)

open class Field<T, V: Any>(name: String): Property<T, V>(name)
class NullableField<T, V: Any>(name: String): Field<T, V>(name)

open class Reference<T, V: Any>(name: String): Property<T, V>(name)
class NullableReference<T, V: Any>(name: String): Reference<T, V>(name)

interface Model<T: Model<T>> {
    fun string(name: String) = Field<T, String>(name)
    fun stringList(name: String) = Field<T, List<String>>(name)
    
    fun boolean(name: String) = Field<T, Boolean>(name)
    fun booleanList(name: String) = Field<T, List<Boolean>>(name)

    fun <V: Model<*>> ref(name: String) = Reference<T, V>(name)
    fun <V: Model<*>> refList(name: String) = Reference<T, List<V>>(name)
    
    fun <V: Any> computed(name: String, result: Dependency<T>.() -> V) = Property<T, V>(name)
}


class Dependency<T> {
	operator fun <V: Any> Property<T, V>.invoke() = "DEP" as V
}

@DslMarker
annotation class MyDsl

@MyDsl
class Change<T>(private val id: String? = null, private val parent: Change<*>? = null) {
    private val changes = mutableMapOf<Field<T, *>, Any>()
    private val childs = mutableMapOf<Reference<T, *>, Change<*>>()
    
    infix fun <V: Any> Field<T, V>.set(value: V) =
    	changes.put(this@Field, value)
    
    infix fun <V: Model<*>> Reference<T, V>.set(onChange: context(V) Change<V>.() -> Unit): Unit {
        val item = AddressView as V // get from store
        
        val result = Change<V>(null, this@Change)
        onChange(item, result)
        childs.put(this@Reference, result)
    }
    
    infix fun <V: Model<*>> Reference<T, List<V>>.on(id: String): Reference<T, V> {
        return Reference<T, V>("$name:$id")
    }
    
    override fun toString() = """
    |  id: ${id}
    |  changes: ${changes}
    |  childs: ${childs}
    """.trimMargin()
}

fun <T: Model<*>> change(id: String, onChange: context(T) Change<T>.() -> Unit): Change<T> {
    val item = UserView as T // get from store

    val result = Change<T>(id)
    onChange(item, result)
    return result
}

//----------------------------------------------------------------------------------
object UserView: Model<UserView> {
    val name = string("name")
    val email = string("email")
    val isActive = boolean("isActive")
    val someList = stringList("someList")
    val address = ref<AddressView>("address")
    val manyAddress = refList<AddressView>("manyAddress")
}

object AddressView: Model<AddressView> {
    val country = string("country")
    val city = string("city")
    val address = computed("address") {
        "${country()} from ${city()}"
    }
}

fun main() {
    val changes = change<UserView>("user-id") {
        name set "Alex"
        isActive set true
        someList set listOf("One", "Two")
        
        address set {
            country set "Portugal"
        }
        
        for (id in listOf("one", "two"))      
            manyAddress on id set {
                city set "$id-Aveiro"
            }
    }
    
    println(changes)
}
2 Likes

Is this a bug in the K2 compiler, or is not suppose to handle typealias like this?

Likely a bug. Please report it at kotl.in/issue
Context receivers are experimental, and so bugs are to be expected!