kotlinkotlin-reflect

Setting a nullable UShort using kotlin-reflect


Why can't I set a UShort using reflection in kotlin? I extracted my problem into a unit test.

My test looks like this:

class Junk {
    var DA: UShort? = null
}

class Tests {
    @Test
    fun testSetShort() {
        var uut = Junk()
        val value = 100
        val expect = 100

        val properties: Collection<KProperty<*>> = Junk::class.memberProperties
        val property = properties.find { property -> property.name == "DA" }
        if (property is KMutableProperty<*>) {
            property.setter.call(uut, value.toUShort())  /* FAILS HERE */
        }

        assertEquals(expect, uut.DA)
        System.err.println("ok")
    }
}

The result is

argument type mismatch
java.lang.IllegalArgumentException: argument type mismatch
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97)
    at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Instance.call(CallerImpl.kt:113)
    at kotlin.reflect.jvm.internal.calls.InlineClassAwareCaller.call(InlineClassAwareCaller.kt:142)
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
    at Tests.testSetShort(testSetUshort.kt:24)

Things I have tried:


Solution

  • it's a problem with inline classes. as you know inline classed are still experimental and UShort is an inline class which acts as a wrapper around Short:

    public inline class UShort @PublishedApi internal constructor(@PublishedApi internal val data: Short) : Comparable<UShort>
    

    let's take a look at the bytecode for your code. this is the summarized bytecode of your DA property:

    private Lkotlin/UShort; DA
      @Lorg/jetbrains/annotations/Nullable;() // invisible
    
      // access flags 0x11
      public final getDA-XRpZGF0()Lkotlin/UShort;
      @Lorg/jetbrains/annotations/Nullable;() // invisible
      
        ...
    
      public final setDA-ffyZV3s(Lkotlin/UShort;)V
        // annotable parameter count: 1 (visible)
        // annotable parameter count: 1 (invisible)
        @Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
    
        ...
    

    as you know inline classes should be ignored and deleted after compilation but because you defined DA as nullable, the compiled type is still UShort instead of Short.

    however, when you call Int.toUShort on an object, the compiled code has no sign of UShort and it converts to Short instead(as it should because it's an inline class). that's why you get an argument type mismatch error. because the setter needs a UShort but you are giving it a Short.
    that explains why your code runs successfully with Short instead of UShort.

    anyways, if you really need to use UShort in your code, you should not make it nullable, use a lateinit var instead and it works fine. because if it's not nullable, the DA property's type would be Short after compilation

    var DA: UShort = 0u
    
    //bytecode:
    
     private S DA   // S is JVM type for Short
    
      // access flags 0x11
      public final getDA-Mh2AYeg()S
       ...
    
      // access flags 0x11
      public final setDA-xj2QHRw(S)V
       ...