2

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)
wz2b
  • 1,017
  • 7
  • 24

1 Answers1

1

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
   ...
Mohsen
  • 1,246
  • 9
  • 22
  • 1
    Thank you for explaining! I think it will be easiest for me to not use a UShort. I was just excited to find those, because certain things in java - like the fact that a byte is signed - have always driven me a little nuts, so when I saw these UShort and others I got a little too careless. I'll figure out a workaround ... maybe just make it an Int, since 0-65535 fits just fine in an int. – wz2b Oct 16 '20 at 23:42
  • you're welcome. as you know inline class is just a fancy wrapper. it does not remove the sign bit which you have a problem with :D – Mohsen Oct 16 '20 at 23:48