kotlinlambdacontract

"Leaked in-place lambda" warning after an upgrade to Kotlin 2.0


Consider the following Kotlin code which overloads Reader.useLines(). The 1st extension merely invokes Reader.useLines() and has an exactly the same contract, the 2nd one filters the line sequence using some predicate and passes the filtered sequence to the consumer:

import java.io.Reader
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract

private val prefixes: Array<out String> = arrayOf(
    "foo",
    "bar",
    "baz",
)

private fun String.hasPrefix(): Boolean =
    prefixes.any { prefix ->
        this.startsWith(prefix)
    }

@OptIn(ExperimentalContracts::class)
fun <T> Reader.useLinesA(
    block: (Sequence<String>) -> T,
): T {
    contract {
        callsInPlace(block, EXACTLY_ONCE)
    }

    return block.let(this::useLines)
}

@OptIn(ExperimentalContracts::class)
fun <T> Reader.useLinesB(
    block: (Sequence<String>) -> T,
): T {
    contract {
        callsInPlace(block, EXACTLY_ONCE)
    }

    return { lines: Sequence<String> ->
        lines.filterNot(String::hasPrefix).let(block)
    }.let(this::useLines)
}

The code used to compile just fine with Kotlin 1.9, yet now I'm receiving the following warnings:

fun <T> Reader.useLinesA(
    block: (Sequence<String>) -> T,
): T {
    contract { // Leaked in-place lambda 'block: (Sequence<String>) -> T'.
        callsInPlace(block, EXACTLY_ONCE)
    }

    return block.let(this::useLines) // Leaked in-place lambda 'block: (Sequence<String>) -> T'.
}

fun <T> Reader.useLinesB(
    block: (Sequence<String>) -> T,
): T {
    contract { // Leaked in-place lambda 'block: (Sequence<String>) -> T'.
        callsInPlace(block, EXACTLY_ONCE)
    }

    return { lines: Sequence<String> ->
        lines.filterNot(String::hasPrefix).let(block) // Leaked in-place lambda 'block: (Sequence<String>) -> T'.
    }.let(this::useLines)
}

Additionally, for each of the contract() invocations (despite they seem perfectly correct) I receive:

Wrong invocation kind 'EXACTLY_ONCE' for 'block: (Sequence<String>) -> T' specified, the actual invocation kind is 'ZERO'.

So what's wrong with the code above?

Can anyone explain why are there any memory leaks, and how can I refactor the code to avoid them?

Finally, how comes that block gets called ZERO times, if it is actually called EXACTLY_ONCE, even for an empty sequence?


Solution

  • These are known Kotlin issues: issue 1(Leaked in-place lambda), issue 2(called EXACTLY_ONCE). Although the warnings appear - they are false positive.

    The fix for the first issue should be included somewhere in 2.0.0-RC1(2.0.0-RC1-37), 2.0.20-Beta1(2.0.20-dev-121). I'm not sure the fix is in the stable version already, though - you just have to check for yourself. So either update, wait, or ignore.

    The fix for the second issue is partially done in the 2.0.0-Beta3(there is a second part to it), but it looks like there is no build with it present(maybe the stable one does contain it - I'm not sure). The target version for it should be somewhere in 2.1.0.