I'm getting a strange issue with `callBy` In which...
# reflect
a
I'm getting a strange issue with
callBy
In which cases, for
@Serializable data class C
and applicable primary constructor params
...
, can the following expression
Copy code
((::C).call(...)::copy).callBy(emptyMap())
fail with
Copy code
KotlinReflectionInternalError(
    "Inconsistent number of parameters in the descriptor and Java reflection object: $arity != $expectedArgsSize\n" +
            "Calling: $descriptor\n" +
            "Parameter types: ${this.parameterTypes})\n" +
            "Default: $isDefault
with
expectedArgsSize
being the number of primary constructor declared properties + 1 and arity being the number of primary constructor declared properties + 2? It seems the -1 is due to the reflection logic determining that it's a bound reference to a static method, which seems nonsensical Reducing the case to ~100 lines made the issue vanish so I'm looking for things to pay attention to for debugging
The overall context for this is parameterized testing where each parameter has the form
Copy code
internal class CheckArg<R> private constructor (
    val argsMap: Map<String, Any?>,
    private val expectedException: KClass<out Throwable>?,
    val matcher: (Any?) -> Unit, // (String)->Unit | (R) -> Unit
) {
    private fun resolveArgs(params: List<KParameter>): Map<KParameter, Any?> =
        params.mapNotNull {
            when {
                it.kind == KParameter.Kind.VALUE && it.name in argsMap ->
                    it to argsMap[it.name]
                else ->
                    null
            }
        }.toMap()
    operator fun invoke(func: KCallable<R>, post: (R) -> Unit = {}) {
        val args = resolveArgs(func.parameters)

        if (expectedException != null) {
            val t = assertThrows(expectedException.java) {
                try {
                    func.callBy(args)
                } catch (e: InvocationTargetException) {
                    throw e.targetException
                }
            }
            matcher(t.message)
        } else {
            val a = assertDoesNotThrow { func.callBy(args) }
            matcher(a)
            post(a)
        }
    }

    companion object {
        /* reifying quasi-constructors */
        /** [CheckArg] expecting an exception with Regex message matcher. */
        inline operator fun <R, reified Th: Throwable> invoke(
            args: Map<String, Any?>,
            matchStr: String
        ) =
            CheckArg<R>(args, Th::class) { assertTrue((it as? String)?.contains(matchStr) ?: false) }

        /** [CheckArg] not expecting an exception with result value matcher. */
        @Suppress("UNCHECKED_CAST")
        operator fun <R> invoke(
            args: Map<String, Any?>,
            matcher: (R) -> Unit = {}
        ) =
            CheckArg<R>(args, null) { matcher(it as R) }

    }
}
so that we can use an ArgumentsProvider producing a Stream of
CheckArg<R, ExceptionType>(argsMap, exceptionMsg)
and the test function is
Copy code
@ParameterizedTest
@ArgumentsSource(ArgSource::class)
fun test(a: CheckArg<R>) {
    if (a.argsMap.size == 2) {
            val ctor: (...) -> C = ::C
            a(ctor as KCallable<C>) { instance = it }
    } else
            a(c!!::copy)
}
thus allowing the approach of "inputs A=A, B=B produce a value C which can then be asserted for whatever (or saved as an object to copy from)", "input D=D produces exception E containing message F" through relatively straightforward parameterization If the test function is simplified to just invoking the ctor and doing the map update ourselves instead of passing a changed-args map to
(instance::copy).callBy
, the issue disappears