I want to write a convenience extension to extract values from a Map while parsing them at the same time. If the parsing fails, the function should return a default value. This all works fine, but I want to tell the Kotlin compiler that when the default value is not null, the result won't be null either. I could to this in Java through the @Contract
annotation, but it seems to not work in Kotlin. Can this be done? Do contracts not work for extension functions? Here is the kotlin attempt:
import org.jetbrains.annotations.Contract
private const val TAG = "ParseExtensions"
@Contract("_, !null -> !null")
fun Map<String, String>.optLong(key: String, default: Long?): Long? {
val value = get(key)
value ?: return default
return try {
java.lang.Long.valueOf(value)
} catch (e: NumberFormatException) {
Log.e(TAG, e)
Log.d(TAG, "Couldn't convert $value to long for key $key")
default
}
}
fun test() {
val a = HashMap<String, String>()
val something: Long = a.optLong("somekey", 1)
}
In the above code, the IDE will highlight an error in the assignment to something
despite optLong
being called with a non null default value of 1. For comparison, here is similar code which tests nullability through annotations and contracts in Java:
public class StackoverflowQuestion
{
@Contract("_, !null -> !null")
static @Nullable Long getLong(@NonNull String key, @Nullable Long def)
{
// Just for testing, no real code here.
return 0L;
}
static void testNull(@NonNull Long value) {
}
static void test()
{
final Long something = getLong("somekey", 1L);
testNull(something);
}
}
The above code doesn't show any error. Only when the @Contract
annotation is removed will the IDE warn about the call to testNull()
with a potentially null value.
You can do this by making the function generic.
fun <T: Long?> Map<String, String>.optLong(key: String, default: T): T
{
// do something.
return default
}
Which can be used like this:
fun main(args: Array<String>) {
val nullable: Long? = 0L
val notNullable: Long = 0L
someMap.optLong(nullable) // Returns type `Long?`
someMap.optLong(notNullable) // Returns type `Long`
}
This works because Long?
is a supertype of Long
. The type will normally be inferred in order to return a nullable or non-nullable type based on the parameters.
This will "tell the Kotlin compiler that when the default value is not null, the result won't be null either."
It's a pity that you can't do this, in Kotlin 1.2 or below.
However, Kotlin is working on contract dsl
which is unannounced yet, which is not available ATM (since they're declared internal
in the stdlib) but you can use some hacks to use them in your codes (by compiling a stdlib yourself, make all of them public).
You can see them in the stdlib ATM:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
Maybe there will be something like
contract {
when(null != default) implies (returnValue != null)
}
in the future that can solve your problem.
Personally I'd recommend you to replace default
's type with a NotNull Long
and call it like
val nullableLong = blabla
val result = nullableLong?.let { oraora.optLong(mudamuda, it) }
result
is Long?
and it's null only when nullableLong
is null.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With