How does erasure work in Kotlin? - kotlin

In Kotlin, the following code compiles:
class Foo {
fun bar(foo: List<String>): String {
return ""
}
fun bar(foo: List<Int>): Int {
return 2;
}
}
This code, however, does not:
class Foo {
fun bar(foo: List<String>): String {
return ""
}
fun bar(foo: List<Int>): String {
return "2";
}
}
Compiling this will cause the following error:
Error:(8, 5) Kotlin: Platform declaration clash: The following declarations have the same JVM signature (foo(Ljava/util/List;)Ljava/lang/String;):
fun foo(layout: List<Int>): String
fun foo(layout: List<String>): String
In Java, neither example will compile:
class Foo {
String bar(List<Integer> foo) {
return "";
}
Integer bar(List<String> foo) {
return 2;
}
}
class Foo {
String bar(List<Integer> foo) {
return "";
}
String bar(List<String> foo) {
return "2";
}
}
Unsurprisingly, both of the prior snippets generate the familiar compiler error:
Error:(13, 12) java: name clash: bar(java.util.List<java.lang.String>) and bar(java.util.List<java.lang.Integer>) have the same erasure
What surprises me is that the first Kotlin example works at all, and second, if it works, why does the second Kotlin example fail? Does Kotlin consider a method's return type as part of its signature? Furthermore, why do method signatures in Kotlin respect the full parameter type, in contrast with Java?

Actually Kotlin knows the difference between the two methods in your example, but jvm will not. That's why it's a "platform" clash.
You can make your second example compile by using the #JvmName annotation:
class Foo {
#JvmName("barString") fun bar(foo: List<String>): String {
return ""
}
#JvmName("barInt") fun bar(foo: List<Int>): String {
return "2";
}
}
This annotation exists for this very reason. You can read more in the interop documentation.

While #Streloks answer is correct, I wanted to dig deeper regarding why it works.
The reason why the first variant works, is that it is not prohibited within the Java Byte code. While the Java compiler complains about it, i.e. the Java language specification does not allow it, the Byte code does, as was also documented in https://community.oracle.com/docs/DOC-983207 and in https://www.infoq.com/articles/Java-Bytecode-Bending-the-Rules. In the Byte code every method call refers the actual return type of the method, which isn't that way when you write the code.
Unfortunately I couldn't find the actual source, why it is that way.
The document regarding Kotlins name resolution contains some interesting points, but I did not see your actual case there.
What really helped me understand it, was the answer from #Yole to Kotlin type erasure - why are functions differing only in generic type compilable while those only differing in return type are not?, more precisely that the kotlin compiler will not take the type of the variable into account when deciding which method to call.
So, it was a deliberate design decision that specifying the type on a variable will not influence which method is the one to be called but rather the other way around, i.e. the called method (with or without generic information) influences the type to be used.
Applying the rule on the following samples then makes sense:
fun bar(foo: List<String>) = "" (1)
fun bar(foo: List<Int>) = 2 (2)
val x = bar(listOf("")) --> uses (1), type of x becomes String
val y = bar(listOf(2)) --> uses (2), type of y becomes Int
Or having a method supplying a generic type but not even using it:
fun bar(foo: List<*>) = "" (3)
fun <T> bar(foo: List<*>) = 2 (4)
val x = bar(listOf(null)) --> uses (3) as no generic type was specified when calling the method, type of x becomes String
val y = bar<String>(listOf(null)) --> uses (4) as the generic type was specified, type of y becomes Int
And that's also the reason why the following will not work:
fun bar(foo: List<*>) = ""
fun bar(foo: List<*>) = 2
This is not compilable as it leads to a conflicting overload as the type of the assigned variable itself is not taken into consideration when trying to identify the method to be called:
val x : String = bar(listOf(null)) // ambiguous, type of x is not relevant
Now regarding that name clash: as soon as you use the same name, the same return type and the same parameters (whose generic types are erased), you will actually get the very same method signature in the byte code. That's why #JvmName becomes necessary. With that you actually ensure that there are no name clashes in the byte code.

Related

Having trouble with type erasure

I have something like this :
import kotlin.reflect.KClass
class Quantity<T> {
/* ... */
}
class Field<T : Any> {
val type: KClass<T> get() = TODO("This is initialized, don't worry about implentation details, just know that fields know their type.")
fun initValue(value: T) {
/* Do something very useful */
}
/* Other methods */
class Template<T : Any> {
fun initFieldWithValue(value: T): Field<T> {
return Field<T>().apply {
this.initValue(value)
}
}
}
}
class ComponentClass(
val fieldsTemplates: Map<String, Field.Template<*>>
) {
inner class Instance(field: Map<String, Field<*>>)
fun new(fieldValues: Map<String, Quantity<*>>): Instance {
val fields = mutableMapOf<String, Field<*>>()
for ((fieldName, template) in fieldsTemplates) {
fields[fieldName] = fieldsTemplates
.getValue(fieldName)
.initFieldWithValue(fieldValues.getValue(fieldName) /* Here a type error */)
}
return Instance(fields)
}
}
As you might guess, this is intended to work as a 'runtime way' of creating classes that own fields (Field<T> class), each one possessing a typed value (represented by a Quantity<T>).
The problem is that this code won't compile due to the fact that the quantity retrieved from fieldValues when creating the different fields of the future Instance in the new method isn't guaranteed to be of the required type for the field it is stuffed into.
The problem is that I would need a check since filling a Field<Quantity<String>> with a Quantity<Int> is obviously not a good idea, but because of the type erasure I cannot ensure that the quantities passed in are of the good type.
Any idea ? One more thought : Fields know what their type is thanks to their type attribute, but unfortunately I can't do the same for the Quantity class...
Your initFieldWithValue function is enforcing the type of the parameter to match the type known by the Template/Field. But inside your new function, your Template is a Template<*> since you retrieve it from a collection where the values are of this type.
The point of generics is to enforce compile time checks so casting can be done safely and automatically under the hood. This is only useful when your type is known at compile time. In this case, the type is not known at compile time, so the generics are preventing your code from compiling. This is what generics are supposed to do: prevent code from compiling if the compiler cannot check that they types match.
If you want this code to compile, you should change initFieldWithValue so it doesn't enforce generics. You can instead manually check the type and throw an error or exit early if it's incorrect. It will be up to your code elsewhere to ensure you aren't mixing and matching types.
Here's an example of a version that would work. The type check it does requires the Kotlin reflection library. If you're targeting JVM only, you can use the Java Class.isAssignableFrom method instead to do this check.
class Template<T : Any> {
val type: KClass<T> get() = TODO()
/**
* #throws IllegalStateException if [value] is not of the same type
* as this Template's [type].
*/
fun initFieldWithValue(value: Any): Field<T> {
if (!value::class.isSubclassOf(type)) {
error("Invalid value type for Field type of $type")
}
return Field<T>().apply {
#Suppress("UNCHECKED_CAST") // we manually checked it above
initValue(value as T)
}
}
}

Extension method, when called on a null object, is called on the wrong type

fun main() {
val set: Set<Int>?
set = null
val emptySet: Set<Int> = set.orEmpty()
}
Can't figure out why even when explicitly typing the set variable as Set <Int>? the compiler considers that in the extension method set.orEmpty () set - is a string and, accordingly, crashes with an error:
Kotlin: Type mismatch: inferred type is String but Set was expected
But when declaring and initializing in one line, everything happens correctly:
fun main() {
val set: Set<Int>? = null
val emptySet: Set<Int> = set.orEmpty()
}
The behavior you're observing can be explained by the interaction of two Kotlin features:
first, the type of set variable is narrowed to Nothing? as a result of a smart cast after the assignment of null value to it. The smart cast after an assignment can be useful in cases when it narrows variable type to a more specific type, but narrowing to Nothing? does more harm than good.
second, among all overloads of orEmpty function available for a value of type Nothing?, the non-generic one String?.orEmpty() is chosen due to the specific rule of Kotlin overload resolution: a non-generic candidate is preferred to generic ones.
This behavior indeed can be puzzling, so I've reported this problem as KT-50661.
I think this is related to the fact that the compiler is not so smart that it could deduce that the code set = null will be executed exactly once – it could be zero times or more than once.
If you know that it will run exactly one, you can tell the compiler by using a feature called kotlin.contracts:
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
#ExperimentalContracts
fun main() {
val set: Set<Int>?
once { set = null }
val emptySet: Set<Int> = set.orEmpty()
}
#ExperimentalContracts
fun once(lambda: () -> Unit) {
contract { callsInPlace(lambda, InvocationKind.EXACTLY_ONCE) }
lambda()
}
See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.contracts/

Kotlin incorrectly infers nullable Enum return type when a function call is passed as an argument to another function

I may not have done a good job explaining the problem in the title, but here's an example:
fun main() {
acceptEnum(inferType())
}
fun acceptEnum(value: MyEnum?) {}
fun <R : Enum<R>?> inferType(): R = TODO()
enum class MyEnum {
VALUE
}
inferType() function infers its return type and bounds it to be a generic nullable enum. acceptEnum() function has a nullable enum parameter. When we write acceptEnum(inferType()), everything's fine. But if we add one more parameter to acceptEnum() and pass inferType() there again, here's what happens:
fun main() {
// first inferType() does not compile with an error:
// Type mismatch: inferred type is MyEnum? but MyEnum was expected
acceptEnum(inferType(), inferType())
}
fun acceptEnum(value: MyEnum?, value2: MyEnum?) {}
If we add more parameters, every inferType() call except the last one produces this error.
Is this a compiler bug or am I doing something wrong?
Update
Kotlin forum post: https://discuss.kotlinlang.org/t/kotlin-incorrectly-infers-nullable-enum-return-type-when-a-function-call-is-passed-as-an-argument-to-another-function/23650
Update
Kotlin issue https://youtrack.jetbrains.com/issue/KT-50232

Kotlin: generic cast function parameter

Taking my first steps in Kotlin, I'm struggling to find the correct signature for a function that receives an instance of a known class along with the desired output class and then looks in a map of converter lambdas whether the conversion can be done.
Here's an example for Long:
private fun <T> castLong(value: Long, clazz: Class<out T>): T {
// map lookup removed for simplicity
return when (clazz) {
String::class.java -> { value.toString() }
else -> { throw IllegalArgumentException("Unsupported Cast") }
}
}
Where T is the class of the desired return value - let's say String. One should be able to call castLong(aLongValue, String::class.java) and receive an instance of String.
But the compiler says:
Type mismatch: inferred type is String but T was expected
This seems like it should be possible as it is quite straightforward so far but even playing around with reified and other constructs didn't yield any better results.
It happens because it can't smart cast String to T, you have to manually cast it.
Furthermore, since you said you are taking your first steps in Kotlin, I leave here two other "advices" not strictly related to your question:
you can get the class of T making it reified
the brackets of a case using when aren't necessary if the case is one line
private inline fun <reified T> castLong(value: Long): T {
// map lookup removed for simplicity
return when (T::class.java) {
String::class.java -> value.toString()
else -> throw IllegalArgumentException("Unsupported Cast")
} as T
}

How to save a function reference as the value in a Map type, and invoke it with a parameter later on in Kotlin?

val specials:Map<String, (Any)->Unit> = mapOf(
"callMe1" to {asParam1()},
"callMe2" to {asParam2()}
)
fun asParam1(num:Int) {
println(num)
}
fun asParam2(text:String) {
println(text)
}
fun caller() {
specials["callMe1"]?.invoke("print me")
specials["callMe2"]?.invoke(123)
}
fun main(args: Array<String>) {
caller()
}
My requirement is simple, I want to save the function asParam1 and asParam2 as a value in the variable specials. And invoke it later on by fetching the value from a Map.
However, the compiler doesn't like it:
Error:(1, 40) Type inference failed. Expected type mismatch: inferred
type is Map Unit> but Map Unit> was
expected
Error:(1, 69) No value passed for parameter num
Error:(1, 96) No value passed for parameter text
While this task is pretty simple in a weak typed language, I don't know how to do in Kotlin. Any help would be welcome. Thanks!
The correct syntax is "calllme" to ::asParam1.
But then the signatures will be wrong because the Map expects type (Any)->Unit and yours have (Int)->Unit and (String)->Unit. Here is an example that does not produce the error:
val specials:Map<String, (Any)->Unit> = mapOf(
"callMe1" to ::asParam1,
"callMe2" to ::asParam2
)
fun asParam1(num:Any) {
if(num is Int) println(num)
}
fun asParam2(text:Any) {
if(text is String) println(text)
}
fun caller() {
specials["callMe2"]?.invoke("print me")
specials["callMe1"]?.invoke(123)
}
Keep in mind, your code for the caller has special knowledge about how to call each of your functions (i.e., the correct parameter types), but the compiler does not have this same knowledge. You could accidentally call asParam1 passing a String instead of an Int (which is what your caller function was doing, I fixed it in my example) and that is not allowed. Which is why I changed the signatures of both asParam* to accept Any parameter, and then validated the expected type in each function (ignoring bad types).
If your intent is to pass integers in addition to strings to asParam2(), then change the body to test for both Int and String and convert the integer to a string.
When you write { asParam1() }, you create a lambda with an executable code block inside it, so you need to properly call the function asParam1(...), which requires an Int argument.
So, the first change you need to make is: { i -> asParam1(i) }.
But this code will still not pass the type checking, because, matching the type of the map, the lambda will be typed as (Any) -> Unit (the values in the map should all be able to accept Any, and a function that expects a narrower type cannot be a value in this map).
You then need to convert the Any argument to Int to be able to invoke the function: { i -> asParam1(i as Int) }
Finally, the map will look like this:
val specials: Map<String, (Any) -> Unit> = mapOf(
"callMe1" to { i -> asParam1(i as Int) },
"callMe2" to { s -> asParam2(s as String) }
)
The invocation stays unchanged, as in your code sample.
The function reference syntax (::asParam1) would allow you to reference a function that already accepts Any, it would not implicitly make the conversion described above. To use it, you would have to modify your functions to accept Any, as in #Les's answer.