Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why kotlin doesn't allow covariant mutablemap to be a delegate?

I'm new to Kotlin. When I learn Storing Properties in a Map. I try following usage.

class User(val map: MutableMap<String, String>) {
    val name: String by map
}

class User(val map: MutableMap<String, in String>) {
    val name: String by map
}

class User(val map: MutableMap<String, out String>) {
    val name: String by map
}

The first two are both work, the last one failed. With out modifier, the bytecode of getName like this:

  public final java.lang.String getName();
     0  aload_0 [this]
     1  getfield kotl.User.name$delegate : java.util.Map [11]
     4  astore_1
     5  aload_0 [this]
     6  astore_2
     7  getstatic kotl.User.$$delegatedProperties : kotlin.reflect.KProperty[] [15]
    10  iconst_0
    11  aaload
    12  astore_3
    13  aload_1
    14  aload_3
    15  invokeinterface kotlin.reflect.KProperty.getName() : java.lang.String [19] [nargs: 1]
    20  invokestatic kotlin.collections.MapsKt.getOrImplicitDefaultNullable(java.util.Map, java.lang.Object) : java.lang.Object [25]
    23  checkcast java.lang.Object [4]
    26  aconst_null
    27  athrow
      Local variable table:
        [pc: 0, pc: 28] local: this index: 0 type: kotl.User

As we can see, it will cause a NullPointerException.

Why contravariant is not allowed on a map delegate?

And why kotlin doesn't give me a compile error?

like image 288
Dean Xu Avatar asked Jun 21 '17 01:06

Dean Xu


People also ask

How does kotlin delegate work?

Android Dependency Injection using Dagger with Kotlin Kotlin supports “delegation” design pattern by introducing a new keyword “by”. Using this keyword or delegation methodology, Kotlin allows the derived class to access all the implemented public methods of an interface through a specific object.

What are delegate properties Kotlin?

A delegate is just a class that provides the value for a property and handles its changes. This allows us to move, or delegate, the getter-setter logic from the property itself to a separate class, letting us reuse this logic.

What is MutableMap in Kotlin?

Kotlin MutableMap is an interface of collection frameworks which contains objects in the form of keys and values. It allows the user to efficiently retrieve the values corresponding to each key. The key and values can be of the different pairs like <Int, String>, < Char, String>, etc.

How do you add a mutable map to Kotlin?

The function put() and putAll() are used to add the elements in the MutableMap. put() function adds the single element at a time where as putAll() function adds the collection type elements in the MutableMap. For example: fun main(args: Array<String>) {


1 Answers

Short answer: it's not a bug in the compiler, but rather an unfortunate consequence of how the signature of operator getValue() is declared for MutableMap.

Long answer: delegating properties to maps is possible because of the following three operator functions in the standard library:

// for delegating val to read-only map
operator fun <V, V1: V> Map<in String, @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1

// for delegating var to mutable map
operator fun <V> MutableMap<in String, in V>.getValue(thisRef: Any?, property: KProperty<*>): V

operator fun <V> MutableMap<in String, in V>.setValue(thisRef: Any?, property: KProperty<*>, value: V)

Here the use-site variance of the MutableMap receiver is chosen so that one could delegate a property of some type to a map which can store its supertype:

class Sample(val map: MutableMap<String, Any>) {
    var stringValue: String by map
    var intValue: Int by map
}

Unfortunately, when you try to use an out-projected MutableMap<String, out String> as a delegate for a val property and consequently as a receiver of getValue operator, here what happens:

  • MutableMap<in String, in V>.getValue overload is choosen, because it has more specific receiver type.
  • Since the receiver map has out String type argument projection it's unknown what its actual type argument is (it can be a MutableMap<..., String> or a MutableMap<..., SubTypeOfString>), so the only safe option is to assume it is Nothing, which is a subtype of all possible types.
  • The return type of this function is declared as V which has been inferred to Nothing, and the compiler inserts a check that the actual returned value is of type Nothing, which should always fail, as there couldn't be a value of type Nothing. This check looks like throw null in the bytecode.

I've opened an issue KT-18789 to see what we can be done with the signature of this operator function.

UPD: The signature was fixed in Kotlin 1.2.20

Meanwhile as a workaround you can cast the MutableMap to Map, so that the first overload of getValue is chosen:

class User(val map: MutableMap<String, out String>) {
    val name: String by map as Map<String, String>
}
like image 108
Ilya Avatar answered Sep 27 '22 19:09

Ilya