Setting a nullable UShort using kotlin-reflect

233 views Asked by At

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:

  • Force value to be of type UShort? in case the nullability was the problem (though that's not a problem when I try to do the same thing to a nullable String var)
1

There are 1 answers

2
Mohsen On BEST ANSWER

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
   ...