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?