Kotlin case of non-intuitive type inference - kotlin

I found some non-intuitive behavior of type inference. As a result, the semantically equivalent code works differently, depending on what information the compiler infers about function return type. It is more or less clear what is going on when you reproduce this case in a minimum unit test. But I afraid that when writing framework code, such behavior could be dangerous.
The code below illustrates the problem, and my questions are:
Why the puzzler1 call from notok1 unconditionally throws NPE? As far as I understand from the bytecode, ACONST_NULL ATHROW throws NPE right after puzzler1 call, ignoring the returned value.
Is it normal that upper bound (<T : TestData>) is ignored when compiler infers the type?
Is it a bug that NPE becomes ClassCastException if you add suspend modifier to the function? Of course, I understand that runBlocking+suspend call gives us the different bytecode, but shouldn't the "coroutinized" code be as equivalent as possible to conventional code?
Is there a way to rewrite puzzler1 code somehow, eliminating the unclearness?
#Suppress("UnnecessaryVariable", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST", "RedundantSuspendModifier")
class PuzzlerTest {
open class TestData(val value: String)
lateinit var whiteboxResult: TestData
fun <T : TestData> puzzler1(
resultWrapper: (String) -> T
): T {
val result = try {
resultWrapper("hello")
} catch (t: Throwable) {
TestData(t.message!!) as T
}
whiteboxResult = result
return result // will always return TestData type
}
// When the type of `puzzler1` is inferred to TestData, the code works as expected:
#Test
fun ok() {
val a = puzzler1 { TestData("$it world") }
// the same result inside `puzzler1` and outside of it:
assertEquals("hello world", whiteboxResult.value)
assertEquals("hello world", a.value)
}
// But when the type of `puzzler1` is not inferred to TestData, the result is rather unexpected.
// And compiler ignores the upper bound <T : TestData>:
#Test
fun notok1() {
val a = try {
puzzler1 { throw RuntimeException("goodbye") }
} catch (t: Throwable) {
t
}
assertEquals("goodbye", whiteboxResult.value)
assertTrue(a is NullPointerException) // this is strange
}
// The same code as above, but with enough information for the compiler to infer the type:
#Test
fun notok2() {
val a = puzzler1 {
#Suppress("ConstantConditionIf")
if (true)
throw RuntimeException("goodbye")
else {
// the type is inferred from here
TestData("unreachable")
// The same result if we write:
// puzzler1<TestData> { throw RuntimeException("goodbye") }
}
}
assertEquals("goodbye", whiteboxResult.value)
assertEquals("goodbye", (a as? TestData)?.value) // this is stranger
}
// Now create the `puzzler2` which only difference from `puzzler1` is `suspend` modifier:
suspend fun <T : TestData> puzzler2(
resultWrapper: (String) -> T
): T {
val result = try {
resultWrapper("hello")
} catch (t: Throwable) {
TestData(t.message!!) as T
}
whiteboxResult = result
return result
}
// Do exactly the same test as `notok1` and NullPointerException magically becomes ClassCastException:
#Test
fun notok3() = runBlocking {
val a = try {
puzzler2 { throw RuntimeException("goodbye") }
} catch (t: Throwable) {
t
}
assertEquals("goodbye", whiteboxResult.value)
assertTrue(a is ClassCastException) // change to coroutines and NullPointerException becomes ClassCastException
}
// The "fix" is the same as `notok2` by providing the compiler with info to infer `puzzler2` return type:
#Test
fun notok4() = runBlocking {
val a = try {
puzzler2<TestData> { throw RuntimeException("goodbye") }
// The same result if we write:
// puzzler2 {
// #Suppress("ConstantConditionIf")
// if (true)
// throw RuntimeException("goodbye")
// else
// TestData("unreachable")
// }
} catch (t: Throwable) {
t
}
assertEquals("goodbye", whiteboxResult.value)
assertEquals("goodbye", (a as? TestData)?.value)
}
}

What is the type of throw RuntimeException("goodbye")? Well, since it never returns a value, you can use it anywhere you like, no matter what type of object is expected, and it will always typecheck. We say that it has type Nothing. This type has no values, and it is a subtype of every type. Therefore, in notok1, you have a call to puzzler1<Nothing>. The cast from the constructed TestData to T = Nothing inside puzzler1<Nothing> is unsound but unchecked, and puzzler1 ends up returning when its type signature says it shouldn't be able to. notok1 notices that puzzler1 has returned when it said it would not be able to, and immediately throws an exception itself. It's not very descriptive, but I believe the reason it throws an NPE is because something has gone "terribly wrong" if a function that can't return has returned, so the language decides the program should die as fast as possible.
For notok2, you actually do get T = TestData: one branch of the if returns Nothing, the other TestData, and the LUB of those is TestData (since Nothing is a subtype of TestData). notok2 has no reason to believe that puzzler1<TestData> cannot return, so it doesn't set up the trap to die as soon as puzzler1 returns.
notok3 has essentially the same problem as notok1. The return type, Nothing, implies that the only thing the puzzler2<Nothing> will do is throw an exception. The coroutine handling code in notok3 thus expects the coroutine to hold a Throwable and contains code to rethrow it, but does not contain code to handle an actual return value. When puzzler2 actually does return, notok3 tries to cast that TestData into a Throwable and fails. notok4 works for the same reason notok2 does.
The solution to this mess is simply not using an unsound cast. Sometimes puzzler1<T>/puzzler2<T> will be able to return a T, if the passed function in fact returns a T. But, if that function throws, they can only return a TestData, and a TestData is not a T (a T is a TestData, not the other way around). The correct signature for puzzler1 (and similarly for puzzler2) is
fun <T : TestData> puzzler1(resultWrapper: (String) -> T): TestData
Since functions are covariant in the return type, you can just get rid of the type parameter
fun puzzler1(resultWrapper: (String) -> TestData): TestData

Related

How to convert kotlin Result type from List<Result<T>> to Result<List<T>>

I have List<Result<String>> and I would like to convert it to Result<List<String>>. I understand that List<Result<String>> could have both failure and successful results but I would like to terminate in the first failure.
If you want to have a failure as soon there is one Result that is a failure you can do this :
fun <T> List<Result<T>>.toResult() = if (any { it.isFailure }) {
Result.failure<List<Result<Any>>>(Throwable("A result has errors"))
} else {
Result.success(map { it.getOrNull() })
}
With this code, you get a failure as soon as there is one value has a failure.
Or if you don't care handling the error yourself :
fun <T> List<Result<T>>.toResult() = runCatching {
Result.success(map { it.getOrThrow() })
}
In most libraries this function is known as sequence.
Kotlin's Arrow library implements it for its implementation of the type Either, which is a generalization of Result: https://arrow-kt.io/docs/apidocs/arrow-core/arrow.core/sequence.html
With Arrow's Either you would write:
val xs: List<Result<String>> = ...
val ys: Result<List<String>> = xs.sequence()
The Kotlin stdlib does not seem to have it. You could define it as an extension method using getOrThrow, catching any thrown Throwable and wrapping in a Resultagain:
fun <T> List<Result<T>>.sequence(): Result<List<T>> = try {
Result.success(this.map { it.getOrThrow() })
}
catch (e:Throwable) { Result.failure(e) }

Kotlin custom Scope function return type not behaving as expected

this custom function call's a lambda block when null.
I expected the function definition below to enforce the same return type. : T
Is their a way to enforce that be block returns type T ?
inline fun <T> T?.whenNull(block: () -> T): T {
if (this == null) {
return block() //accepts any return type
} else {
return this
}
}
fun main() {
val x : Int? = null
println(x ?: 42)
println(x.whenNull { 42 })
println(x.whenNull { "why is kotlin not enforcing return of the same type?" })
}
T in the second whenAll call is being inferred as Any. Imagine that all occurrences of T are replaced Any, the call would be valid, wouldn't you agree? Int and String are both subtypes of Any, after all.
inline fun Any?.whenNull(block: () -> Any): Any {
if (this == null) {
return block()
} else {
return this
}
}
fun main() {
println(x.whenNull { "why is kotlin not enforcing return of the same type?" })
}
Basically, the Kotlin compiler is "trying too hard" here to make your code compile, and infers an unexpected type for your type parameter.
There exists an internal annotation #kotlin.internal.InputTypesOnly that would prevent your code from compiling if the type inferred is not mentioned in one of the input types (parameter types, receiver type, etc) of the function.
In this case, the input type is just Int?, and T is inferred to be Any, so it would be make your code not compile as expected. Unfortunately though, this annotation is internal, and you cannot use it :( KT-13198 is the ticket about making it public.
Interestingly, when passing this to the block the type is preserved and works as expected.
inline fun <T> T?.whenNullAlt(block: (T?) -> T): T {
if (this == null) {
return block(this) // "this" is superfluous, but enforces same return type
} else {
return this
}
}
fun main() {
val x : Int? = null
println(x.whenNullAlt { 42 })
println(x.whenNullAlt { "does not compile" })
}
As other answers pointed out, this is technically correct from Kotlin viewpoint because it can infer T to Any and it would compile.
However, while technically correct, this is a known problem in Kotlin language and is going to be fixed some day. That noted, let's try to understand why your answers's code works while your questions' doesn't.
The reason for this is that lambdas are always inferred last: imagine it as being a queue on what parts of the expression need to have their types inferred and anything inside a lambda is always at the end of the queue, no matter where the lambda is in the expression. So, when going over your example, it infers everything else, decides that the type of this should be Int?, than goes to the lambda, sees a String return type and merges them into Any being very proud of itself.
In the other example, however, the lambda is passed an external fact about it's parameter — the already inferred Int from the receiver (lambda is always the last to get the info, remember?). That way the inference inside the lambda fails because the argument and the result type are in disagreement.

Undetected throw declaration (Kotlin)

Let's have a function which just computes something in try-catch and returns the result:
private fun compute(): String {
return try {
// do some computation
// ...
"result"
} catch(t: Throwable) {
throw RuntimeException("Uups") // <-- GOAL: extract this to a dedicated method
}
}
I would like to extract the throw declaration to a separate function (which contains the my boilerplate code).
However, I'm unable to compile such setup in Kotlin.
A simplistic (and still uncompilable) version of the described problem:
private fun compute(): String {
return try {
// do some computation
// ...
"result"
} catch(t: Throwable) {
justThrow() // <-- NOT COMPILABLE, STRING EXPECTED
}
}
#Throws(RuntimeException::class)
private fun justThrow() {
// some boilerplate code
// ...
throw RuntimeException("Uups")
}
How write justThrow() method in Kotlin so that the whole code is compilable?
In Java this case would be detected by a compiler (I suppose).
Kotlin version: 1.4.21
You can declare the return type of your method as Nothing. This type can be used for any method that does not return normally. That might be because it will always throw an exception, or simply never returns at all, for instance because it contains an infinite loop.
private fun justThrow(): Nothing {
// some boilerplate code
// ...
throw RuntimeException("Uups")
}

Is there a way to cast from 'String' to 'KType'?

Simply, I want a function like:
fun <T> convert(val foo: String, fooT: KType) : T {
...?
}
For Int, it would return foo.toInt(), for Double, foo.toDouble(), and to some unknown type, just throw an exception. I think it's not so hard to create my own switch statement for the types I expect, but out of curiosity - is there a way already?
Recommended way
Unfortunately, there's no easy generic way because we're not dealing with casts, but method calls. This would be my approach:
fun <T> convert(str: String, type: KType) : T {
val result: Any = when (type.jvmErasure)
{
Long::class -> str.toLong()
Int::class -> str.toInt()
Short::class -> str.toShort()
Byte::class -> str.toByte()
...
else -> throw IllegalArgumentException("'$str' cannot be converted to $type")
}
return result as T // unchecked cast, but we know better than compiler
}
Usage:
#UseExperimental(ExperimentalStdlibApi::class)
fun main() {
val int = convert<Int>("32", typeOf<Int>())
println("converted: $int")
}
Instead of a KType parameter, you could also use a Class<T> and make the function reified, so it can be called as convert<Int>("32") or even "32".toGeneric<Int>().
Hardcore way
While there is no easy way, it is possible to access the type using heavy reflection and relying on implementation details. For this, we can extract the type name from the KType object, find an extension method (in a different class) that matches, and call it using reflection.
We have to use to*OrNull() instead of to*(), because the latter is inline and won't be found by reflection. Also, we need to resort to Java reflection -- at this time, Kotlin reflection throws UnsupportedOperationException for the types involved.
I do not recommend this in productive code, as it's inefficient and can break with future standard library versions, but it's a nice experiment:
fun convert(str: String, type: KType): Any {
val conversionClass = Class.forName("kotlin.text.StringsKt")
// here, the to*OrNull() methods are stored
// we effectively look for static method StringsKt.to*OrNull(String)
val typeName = type.jvmErasure.simpleName
val funcName = "to${typeName}OrNull" // those are not inline
val func = try {
conversionClass.getMethod(funcName, String::class.java) // Java lookup
} catch (e: NoSuchMethodException) {
throw IllegalArgumentException("Type $type is not a valid string conversion target")
}
func.isAccessible = true // make sure we can call it
return func.invoke(null, str) // call it (null -> static method)
?: throw IllegalArgumentException("'$str' cannot be parsed to type $type")
}

UninferredParameterTypeConstructor exception during build when generic parameters not specified explicitly

I have the following code (using RxKotlin 0.40.1):
class Result<T, E>(val data: T? = null, val error: E? = null)
fun <T, E> wrapResult(errorInfoFactory: (Throwable) -> E): (Observable<T>) -> Observable<Result<T, E>> = { it.map { Result<T, E>(it) }.onErrorReturn { Result<T, E>(error = errorInfoFactory(it)) } }
Just FYI this is needed to easily transform Observable which may throw errors to an Observable which always returns Result object, so onError is never triggered.
And I want to use it like this:
fun dangerousOperation() = 0
fun getErrorMessage(t: Throwable) = "error occurred"
fun test() {
val resultObservable = Observable.fromCallable(::dangerousOperation).compose(wrapResult(::getErrorMessage))
}
And Android Studio 1.5.1 with Kotlin plugin 1.0.0-release-IJ141-56 doesn't highlight any errors in this code. But when I try to build it, I get the error:
//Error:(24, 9) org.jetbrains.kotlin.codegen.CompilationException:
Back-end (JVM) Internal error: Error type encountered:
org.jetbrains.kotlin.types.ErrorUtils$UninferredParameterTypeConstructor#68754e6
(ErrorTypeImpl). //Cause: Error type encountered:
org.jetbrains.kotlin.types.ErrorUtils$UninferredParameterTypeConstructor#68754e6
(ErrorTypeImpl). //File being compiled and position: (24,9) in
/home/d/work/workspace/Hitch-hiking-Stats/app/src/main/java/ru/netimen/hitch_hikingstats/MemoryRepo.kt
//PsiElement: val resultObservable =
Observable.just(dangerousOperation()).compose(wrapResult(::getErrorMessage))
//The root cause was thrown at: JetTypeMapper.java:435 // at
org.jetbrains.kotlin.codegen.ExpressionCodegen.genQualified(ExpressionCodegen.java:299)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.genStatement(ExpressionCodegen.java:339)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.generateBlock(ExpressionCodegen.java:1532)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.generateBlock(ExpressionCodegen.java:1485)
// at
org.jetbrains.kotlin.codegen.CodegenStatementVisitor.visitBlockExpression(CodegenStatementVisitor.java:56)
// at
org.jetbrains.kotlin.codegen.CodegenStatementVisitor.visitBlockExpression(CodegenStatementVisitor.java:22)
// at
org.jetbrains.kotlin.psi.KtBlockExpression.accept(KtBlockExpression.java:44)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.genQualified(ExpressionCodegen.java:280)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.genStatement(ExpressionCodegen.java:339)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.gen(ExpressionCodegen.java:309)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.returnExpression(ExpressionCodegen.java:1873)
// at
org.jetbrains.kotlin.codegen.FunctionGenerationStrategy$FunctionDefault.doGenerateBody(FunctionGenerationStrategy.java:50)
// at
org.jetbrains.kotlin.codegen.FunctionGenerationStrategy$CodegenBased.generateBody(FunctionGenerationStrategy.java:72)
// at
org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:364)
// at
org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:203)
// at
org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:138)
But when I change my test function to
fun test() {
val resultObservable = Observable.fromCallable(::dangerousOperation).compose(wrapResult<Int, String>(::getErrorMessage))
}
everything compiles OK. But why I get that exception when I don't specify the generic parameters of wrapResult explicitly if the IDE doesn't highlight any errors?
It is compiler bug: https://youtrack.jetbrains.com/issue/KT-11144.
Main reason why this bug is appear: function wrapResult uses generic parameter T only in own return type.