I’ve noticed this weird issue when trying to access property values via reflection. When the type of the property is nullable and any of the unsigned primitives ( UByte?
, UShort?
, UInt?
, ULong?
), I get an IllegalArgumentException
on access via KProperty<T, *>.get()
.
After some further digging, I found that this is an issue with all value classes.
Here is an MCVE:
import kotlin.reflect.KClass
import kotlin.reflect.full.declaredMemberProperties
@Suppress("UNCHECKED_CAST")
fun <T : Any> getValueViaCapturedType(propertyName: String, t: T) =
(t::class as KClass<T>)
.declaredMemberProperties
.single { it.name == propertyName }
.get(t)
object DoubleObject {
val nonNullable: Double = 0.0
val nullable: Double? = 0.0
}
object FloatObject {
val nonNullable: Float = 0f
val nullable: Float? = 0f
}
object LongObject {
val nonNullable: Long = 0
val nullable: Long? = 0
}
object IntObject {
val nonNullable: Int = 0
val nullable: Int? = 0
}
object ShortObject {
val nonNullable: Short = 0
val nullable: Short? = 0
}
object CharObject {
val nonNullable: Char = Char(0)
val nullable: Char? = Char(0)
}
object ByteObject {
val nonNullable: Byte = 0
val nullable: Byte? = 0
}
object ULongObject {
val nonNullable: ULong = 0u
val nullable: ULong? = 0u
}
object UIntObject {
val nonNullable: UInt = 0u
val nullable: UInt? = 0u
}
object UShortObject {
val nonNullable: UShort = 0u
val nullable: UShort? = 0u
}
object UByteObject {
val nonNullable: UByte = 0u
val nullable: UByte? = 0u
}
object StringObject {
val nonNullable: String = ""
val nullable: String? = ""
}
object UnitObject {
val nonNullable: Unit = Unit
val nullable: Unit? = Unit
}
object ValueObject {
val nonNullable: SomeValueClass = SomeValueClass()
val nullable: SomeValueClass? = SomeValueClass()
}
@JvmInline
value class MyValueClass(val value: Int = 0)
private val subjects = listOf(
DoubleObject,
FloatObject,
LongObject,
IntObject,
ShortObject,
CharObject,
ByteObject,
ULongObject,
UIntObject,
UShortObject,
UByteObject,
StringObject,
UnitObject,
ValueObject,
)
fun runTest(propertyName: String) {
var maybeThrowable: Throwable? = null
subjects.forEach { subject ->
val paddedClassName = subject::class.simpleName!!.padEnd(12)
val result = runCatching { getValueViaCapturedType(propertyName, subject) }
result
.map { "✓" }
.getOrElse { "ERROR" }
.also { println("$paddedClassName $it") }
maybeThrowable = result.exceptionOrNull() ?: maybeThrowable
}
maybeThrowable?.run {
println()
println("last stack trace:")
printStackTrace()
}
}
fun main() {
runTest("nonNullable")
println()
println()
runTest("nullable")
}
Running this MCVE will yield the following results:
~~ nonNullable ~~
DoubleObject ✓
FloatObject ✓
LongObject ✓
IntObject ✓
ShortObject ✓
CharObject ✓
ByteObject ✓
ULongObject ✓
UIntObject ✓
UShortObject ✓
UByteObject ✓
StringObject ✓
UnitObject ✓
ValueObject ✓
~~ nullable ~~
DoubleObject ✓
FloatObject ✓
LongObject ✓
IntObject ✓
ShortObject ✓
CharObject ✓
ByteObject ✓
ULongObject ERROR
UIntObject ERROR
UShortObject ERROR
UByteObject ERROR
StringObject ✓
UnitObject ✓
ValueObject ERROR
last stack trace:
java.lang.IllegalArgumentException: argument type mismatch
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at kotlin.reflect.jvm.internal.calls.InlineClassAwareCaller.call(InlineClassAwareCaller.kt:145)
at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
at kotlin.reflect.jvm.internal.KProperty1Impl.get(KProperty1Impl.kt:35)
at net.marvk.fs.bgl.ReflectionIssueTestKt.getValueViaCapturedType(ReflectionIssueTest.kt:11)
at net.marvk.fs.bgl.ReflectionIssueTestKt.runTest(ReflectionIssueTest.kt:100)
at net.marvk.fs.bgl.ReflectionIssueTestKt.main(ReflectionIssueTest.kt:120)
at net.marvk.fs.bgl.ReflectionIssueTestKt.main(ReflectionIssueTest.kt)
Process finished with exit code 0
As one can see, an exception is being thrown as described.
I all out of ideas with this and it sure seems like a bug, but maybe I’m missing something? Is this a known limitation?