Trying to tailrec a recursive function fails with a compiler error


#1

Initial code:

    private tailrec fun parseRecursively(contents: List<String>, filename: String, tablesAcc: List<Table> = emptyList()): List<Table> {
    for (line in contents) {
        if (isHeader(line)) {
            val tableBlock = parseTableBlock(line, contents.subList(contents.indexOf(line) + 1, contents.size))
            return parseRecursively(contents.subList(tableBlock.linesRead + 1, contents.size), filename, tablesAcc + tableBlock.table)
        }
    }
    return tablesAcc
}

Which I converted to

    private tailrec fun parseRecursively(contents: List<String>, filename: String, tablesAcc: List<Table> = emptyList()): List<Table> {
    contents.firstOrNull { line -> isHeader(line) }
        ?.let { header ->
            val tableBlock = parseTableBlock(header, contents.subList(contents.indexOf(header) + 1, contents.size))
            return parseRecursively(contents.subList(tableBlock.linesRead + 1, contents.size), filename, tablesAcc + tableBlock.table)
        }
    
    return tablesAcc
}

The new code causes a Couldn't inline method call 'let' into ...

My guess is I’m doing something wrong or ambiguous, for example this works:

    private fun parseRecursively(contents: List<String>, filename: String, tablesAcc: List<Table> = emptyList()): List<Table> {
    return contents.firstOrNull { line -> isHeader(line) }
        ?.let { header ->
            val tableBlock = parseTableBlock(header, contents.subList(contents.indexOf(header) + 1, contents.size))
            parseRecursively(contents.subList(tableBlock.linesRead + 1, contents.size), filename, tablesAcc + tableBlock.table)
        } ?: tablesAcc
}

But now I’m no longer tailrecursive. It’s not so bad because my recursive calls are usually very small, but I’m wondering what exactly I’m doing wrong :o

Here’s more of the error (Pastebin link for the full log) for funsies:

	Error:Kotlin: [Internal Error] java.lang.IllegalStateException: Backend Internal error: Exception during code generation
Cause: Back-end (JVM) Internal error: Couldn't inline method call 'let' into
private final tailrec fun parseRecursively(contents: kotlin.collections.List<kotlin.String>, filename: kotlin.String, tablesAcc: kotlin.collections.List<com.christian.dnd.d100.model.Table> = ...): kotlin.collections.List<com.christian.dnd.d100.model.Table> defined in com.christian.dnd.d100.parsers.block.StructuredTableBlockParser
private tailrec fun parseRecursively(contents: List<String>, filename: String, tablesAcc: List<Table> = emptyList()): List<Table> {
        contents.firstOrNull { line -> isHeader(line) }
            ?.let { header ->
                val tableBlock = parseTableBlock(header, contents.subList(contents.indexOf(header) + 1, contents.size))
                return parseRecursively(contents.subList(tableBlock.linesRead + 1, contents.size), filename, tablesAcc + tableBlock.table)
            }
        return tablesAcc
    }
Cause: let (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;:
  @Lkotlin/internal/InlineOnly;() // invisible
   L0
    LINENUMBER 97 L0
    NOP
   L1
    LINENUMBER 100 L1
    ALOAD 1
    ALOAD 0
    INVOKEINTERFACE kotlin/jvm/functions/Function1.invoke (Ljava/lang/Object;)Ljava/lang/Object; (itf)
    ARETURN
   L2
    LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
    LOCALVARIABLE block Lkotlin/jvm/functions/Function1; L0 L2 1
    LOCALVARIABLE $i$f$let I L0 L2 2
    MAXSTACK = 2
    MAXLOCALS = 3
Cause: Failed to obtain parameter index: value-parameter tablesAcc: kotlin.collections.List<com.christian.dnd.d100.model.Table> = ... defined in com.christian.dnd.d100.parsers.block.StructuredTableBlockParser.parseRecursively[ValueParameterDescriptorImpl@4c040327]
File being compiled at position: (24,15) in /Users/christianbroomfield/Projects/Github/d100/src/main/kotlin/com/christian/dnd/d100/parsers/block/StructuredTableBlockParser.kt
The root cause was thrown at: TailRecursionCodegen.java:153
File being compiled at position: file:///Users/christianbroomfield/Projects/Github/d100/src/main/kotlin/com/christian/dnd/d100/parsers/block/StructuredTableBlockParser.kt
The root cause was thrown at: InlineCodegen.kt:128

#2

Looks like a bug to me, you should create an issue here: https://youtrack.jetbrains.com/issues/KT