Search code examples
kotlintype-inferencedeprecation-warning

Design deprecation warning to replace a list with vararg


I have the following methods:

fun RowVector(x: Double, y: Double): RowVector2 = RowVector(listOf(x, y)) as RowVector2
fun RowVector(x: Double, y: Double, z: Double): RowVector3 = RowVector(listOf(x, y, z)) as RowVector3
fun RowVector(vararg elements: Double): RowVector = RowVector(elements.asList())

fun RowVector(elements: List<Double>): RowVector = // Actually creates the RowVector instance

While there are some legitimate use-cases to call RowVector(elements: List<Double>), I would like to encourage callers to use the vararg version of the method, as this allows the compiler to do some smarter type inference, like here:

val vector1 = RowVector(listOf(0.0, 1.0, 2.0)) // Type is inferred to be RowVector
val vector2 = RowVector(0.0, 1.0, 2.0) // Compiler can see that only 3 values are passed and therefore infers RowVector3

I am therefore adding a @Deprecated annotation to RowVector(elements: List<Double>) which suggests the caller to rather use the vararg version, but can easily be suppressed if the use-case is actually legitimate.

The issue that I am facing is with the design of the ReplaceWith part of the @Deprecated annotation. I would like the IDE to replace RowVector(listOf(0.0, 1.0, 2.0)) with RowVector(0.0, 1.0, 2.0), but so far, I have not found a way to do that:

@Deprecated(
    "Prefer the vararg interface if possible as it allows the compiler to infer types better.",
    ReplaceWith("RowVector(*elements.toDoubleArray())")
)

leads to RowVector(*listOf(0.0, 1.0, 2.0).toDoubleArray()), which still requires manual clean-up.

@Deprecated(
    "Prefer the vararg interface if possible as it allows the compiler to infer types better.",
    ReplaceWith("RowVector(*doubleArrayOf(elements))")
)

weirdly does nothing and

@Deprecated(
    "Prefer the vararg interface if possible as it allows the compiler to infer types better.",
    ReplaceWith("RowVector(*elements)")
)

does a literal replacement like so: RowVector(*listOf(0.0, 1.0, 2.0)).

I know that this is a first-world problem and that the caller can just read the warning and remove the listOf() quite easily, but I would nevertheless try to make it as easy as possible for the caller.


Solution

  • Most likely this is just not possible, sadly.

    In your example, you declare the List<Double> directly inside the function call. However, this is not the only way to call the function.

    For example, I could declare the List inside a value first.

    val foo = listOf(0.0)
    RowVector(foo)
    

    In case you were able to declare a replacement using ReplaceWith that would extract the values from the List provided, the IDE effectively would need to inline the declaration, possibly altering behaviour.

    In addition, the List could be the result of a function call, which would make a automatic replacement even more problematic.

    val random = Random()
    fun fooFun() = List(random.nextInt(0, Int.MAX_VALUE)) { random.nextDouble() }
    
    RowVector(fooFun())
    

    As the outcome of this code is (nearly) random, I don't see how a replacement could work here.

    Personally, I would just leave out the ReplaceWith entirely in this case and rather provide a good message with clues on how to properly change the code manually.