JVM conflict with Kotlin extension functions
When working with type parameters in Kotlin, you might encounter JVM signature clashes. This post explains why these conflicts occur and how to resolve them.
Problem example 1: Extension functions with generic types
We can get into this problem when we add extension methods to a class with a type parameter (also called generics). Look at this post to see why we’d want to do this.
data class Coordinate<T>(val x: T, val y: T)
operator fun Coordinate<Int>.plus(other: Coordinate<Int>) =
Coordinate(x + other.x, y + other.y)
operator fun Coordinate<Double>.plus(other: Coordinate<Double>) =
Coordinate(x + other.x, y + other.y)
fun main() {
println(Coordinate(0, 4) + Coordinate(1, 2))
println(Coordinate(0.1, 0.5) + Coordinate(0.001, 0.4))
}
However, when we try to compile this code, we get the following error:
Kotlin: Platform declaration clash: The following declarations have the same JVM signature (plus(Lexample/Coordinate;Lexample/Coordinate;)Lexample/Coordinate;): fun Coordinate<Double>.plus(other: Coordinate<Double>): Coordinate<Double> defined in example fun Coordinate<Int>.plus(other: Coordinate<Int>): Coordinate<Int> defined in example
Problem example 2: Function overloading with generic parameters
It also happens when we use function overloading.
fun f(l: List<Int>): Int = 1
fun f(l: List<Double>): Int = 2
fun main() {
println(f(listOf(1, 2)))
println(f(listOf(0.1, 0.2)))
}
Kotlin: Platform declaration clash: The following declarations have the same JVM signature (f(Ljava/util/List;)I): fun f(l: List<Double>): Int defined in example fun f(l: List<Int>): Int defined in example
Why does this happen?
This issue occurs due to a concept called type erasure in the JVM. Here’s what happens:
-
In Kotlin (and Java), generic type information exists only at compile time
-
At runtime, the JVM erases the generic type parameters
-
List<Int>
andList<Double>
both become justList
at the JVM level -
Similarly,
Coordinate<Int>
andCoordinate<Double>
both become justCoordinate
As a result, from the JVM’s perspective, our functions have identical signatures:
-
Both
plus
functions becomeplus(Coordinate, Coordinate): Coordinate
-
Both
f
functions becomef(List): Int
The Kotlin compiler detects this conflict and prevents us from creating code that would be ambiguous at runtime.
Solution
Use @JvmName
to manually give the functions different names:
data class Coordinate<T>(val x: T, val y: T)
@JvmName("CoordinatePlusInt")
operator fun Coordinate<Int>.plus(other: Coordinate<Int>) =
Coordinate(x + other.x, y + other.y)
@JvmName("CoordinatePlusDouble")
operator fun Coordinate<Double>.plus(other: Coordinate<Double>) =
Coordinate(x + other.x, y + other.y)
Similarly, for our second example:
@JvmName("fInt")
fun f(l: List<Int>): Int = 1
@JvmName("fDouble")
fun f(l: List<Double>): Int = 2