Avoid recursive type checking problem


#1

Any hints how I could avoid a recursive type checking problem in this particular case?

inline fun <reified T : Comparable<T>> calculatePackageVerificationCode(input: List<T>) =
    when (T::class) {
        String::class -> {
            val sha1sums = input as List<String>

            val digest = sha1sums.sorted().fold(DigestUtils.getSha1Digest()) { digest, sha1sum ->
                DigestUtils.updateDigest(digest, sha1sum)
            }.digest()

            Hex.encodeHexString(digest)
        }

        File::class -> {
            val files = input as List<File>

            val sha1sums = files.map { file ->
                file.inputStream().use {
                    DigestUtils.sha1Hex(it)
                }
            }

            // Type checking runs into a recursive problem here.
            calculatePackageVerificationCode(sha1sums)
        }

        else -> throw IllegalArgumentException("Unsupported type of input list.")
    }

#2

Specify return type


#3

Nope, adding : String to the function declaration does not help.

I’m wondering anyway if this code would work, since I’m calling and inline function recursively. Does that even work at all?

Edit: I was mistaken, adding an explicit return type did help, but then the error was exactly about inline functions which cannot be recursive.


#4

You can split the function into three specialized ones (not inline)


#5

Unfortunately, I cannot specialize like

fun calculatePackageVerificationCode(sha1sums: List<String>): String = ""

fun calculatePackageVerificationCode(sha1sums: List<File>): String = ""

as these have the same JVM signature.

Could you please elaborate what you mean with three specialized functions?


#6

Keep both of your current functions and rename them. Maybe add List and String to the end. Than you can create a third function with the original name, which calls the correct sub function. You can than even inline all 3 functions as they would no longer be recursive.


#7

Hi @sschuberth,
sorry for confusion.

I hope this solve your issue.

@JvmName("calculatePackageVerificationCodeForStrings")
fun calculatePackageVerificationCode(sha1sums: List<String>) =
        sha1sums
                .sorted()
                .fold(DigestUtils.getSha1Digest()) { digest, sha1sum -> DigestUtils.updateDigest(digest, sha1sum) }
                .digest()
                .let { Hex.encodeHexString(it) }

@JvmName("calculatePackageVerificationCodeForFiles")
fun calculatePackageVerificationCode(files: List<File>) =
        calculatePackageVerificationCode(
                sha1sums = files.map { file ->
                    file.inputStream().use {
                        DigestUtils.sha1Hex(it)
                    }
                }
        )

/*
@JvmName("calculatePackageVerificationCodeTrap")
fun calculatePackageVerificationCode(something: Comparable<*>) {
    throw IllegalArgumentException("Unsupported type of input list.")
}
*/

#8

Thanks! That works. Just wondering, is it described somewhere what mechanism allows Kotlin to distinguish these functions when called from Kotlin although the JVM can’t? The docs on handling signature clashes do not really provide an answer to that question.


#9

The reason Kotlin can distinguish is that unlike Java it was designed from the ground up to have generics. It therefore doesn’t need to be stupid with compatibility constraints. It knows that the types are different at compile time even though they are not at runtime. The JVM of course only resolves functions on raw types so separate names are still needed.