Using also with Generics

This fails to compile:

fun <T : Any> fetch(id: ObjectId<T>): T {
    return fetch(id.id ?: throw Exception("Id is unset for $id")) 
        .also { id.obj = it }
}

although this works:

fun <T : Any> fetch(id: ObjectId<T>): T {
    val t: T = fetch(id.id ?: throw Exception("Id is unset for $id"))
    t.also { id.obj = it }
    return t
}

here’s the fetch it’s calling (same class):

fun <T : Any> fetch(stringId: String): T {
    lock.read {
        @Suppress("UNCHECKED_CAST", "TYPE_INFERENCE_ONLY_INPUT_TYPES_WARNING")
        return (cache.get(stringId) ?: throw ObjectNotFound("Id = $stringId")) as T
    }
}
1 Like

For a generic method, like this:

fun <T : Any> fetch(stringId: String): T

to you call it, compiler needs to know, what type T is.

T is not used for any of the parameters, so it cannot be inferred from types of passed arguments.
In such case you need to provide it yourself in some other way.

T is used as return type, so it can be infered from the type of variable it is assigned to.
You can observe that in your working example:

val t: T = fetch(id.id ?: throw Exception("Id is unset for $id"))

However, if the type of variable is not defined explicitly, compiler again has not enough information to infer the type:

val t = fetch(id.id ?: throw Exception("Id is unset for $id")) // does not compile

In your not working example, the method is called as part of an expression.
In this case we could not use any of the mentioned ways to provide the type.

However, we can always define it explicitly in angle brackets:

return fetch<T>(id.id ?: throw Exception("Id is unset for $id")) 
    .also { id.obj = it } // Compiles!
3 Likes

Thanks for that. It helps.

I feel that it’s a compiler bug as it should have been able to chain the inference from the return type, but I’m still learning.

Well, I guess potentially it could be possible for compiler to support it, but I don’t know any technical details.
You may submit a request to kotl.in/issue.