Search code examples
kotlinkotlin-interop

Smart cast doesn't work as expected


I have the following Kotlin code:

fun handleResult(clazz: Any){
    val store = App.getBoxStore();
    if(clazz is List<*> && clazz.size > 0){
        val items: List<*> = clazz;
        val item = items.get(0);
        val box = store.boxFor(item!!::class.java)
        box.put(items)
    }
}

It takes a object, checks if it is a collection and if it is, takes a item to check the class of the collection items, create a Box from a library called ObjectBox which is an database, and them put the list of items in the database.

However, i get the following error in the Box.put statment:

Error:(45, 17) None of the following functions can be called with the 
arguments supplied:
public open fun put(@Nullable vararg p0: Nothing!): Unit defined in 
io.objectbox.Box
public open fun put(@Nullable p0: (Nothing..Collection<Nothing!>?)): 
Unit defined in io.objectbox.Box
public open fun put(p0: Nothing!): Long defined in io.objectbox.Box

The signature of the method I want to use is:

 public void put(@Nullable Collection<T> entities)

It recivies a Collection of a generic type, as a list is a collection, it should work.

I've also explicitly casted it to a List, but it still says the same thing.

Thanks!


Solution

  • The problem is that the generic Collection declaration needs an actual type. However, you're using List<*> which does not have a specified type and the compiler is assuming the generic type associated with the Box is "Nothing".

    There are a couple of ways you can fix this.

    • If you know a specific set of types for which this will be used ahead of time, you can use a when statement to do a proper smart cast of the List type and then you'll be able to create a correct instance of Box call the put() method without an issue.

      if(clazz is List<*> && clazz.size > 0){
          val items = clazz
          val item = items.get(0)
          when (item) {
              is Int -> {
                  val box = store.boxFor(Int::class.java)
                  box.put(items.filterIsInstance<Int>())
              }
              is ...
          }
      
      }
      
    • Use reflection to get the put() method from Box and then make the call on it. This will bypass the semantic checks of the compiler, but it's a bit more dubious and could run into issues later.

      if(clazz is List<*> && clazz.size > 0){
          val items = clazz
          val item = items.get(0)
          val box = store.boxFor(Int::class.java)
          val putMethod = box::class.java.getDeclaredMethod("put", item!!::class.java)
          putMethod.invoke(box, items)
      }