intellij-community icon indicating copy to clipboard operation
intellij-community copied to clipboard

KTIJ-19680 'Replace with a for loop' and backward conversion could work for 'forEachIndexed'

Open t-kameyama opened this issue 3 years ago • 4 comments

https://youtrack.jetbrains.com/issue/KTIJ-19680 https://youtrack.jetbrains.com/issue/KTIJ-19679

t-kameyama avatar Oct 03 '21 00:10 t-kameyama

Thank you for your contribution!

It seems that the current implementation is not yet fully correct and needs some more work as well as additional unit tests. Here are some examples there the intention provides wrong results.

  1. If the for loop has no braces, the loop body is lost after the conversion to forEachIndexed.
for ((index, element) in withIndex()) println("$index : $element")

results in

forEachIndexed { index, element ->
}
  1. If the forEachIndexed call is an expression body of a function, the quick fix currently produces the syntactically incorrect result.
fun List<Int>.foo() = forEachIndexed { index, element -> println("$index : $element") }

is replaced by

fun List<Int>.foo() = for ((index, element) in withIndex()) {
    println("$index : $element")
}

The same problem occurs with the forEach call.

I think that the simplest solution is to check if the forEach/'forEachIndexed` call is an expression body, and do not generate the "Replace with a 'for' loop" action for expression bodies.

Another option would be to implicitly convert the expression body to a block body (by reusing an existing quick fix) and then to convert the function call to a for loop, but I think it would be more difficult to implement and may be less clear to users.

  1. You did a good job to convert continue to return@forEachIndexed and vice versa, but the break statement is not supported.

For example, let's look at this sample.

fun test() {
    for ((index, element) in listOf(1, 2, 3).withIndex()) {
        println("$index: $element")
        if (index == 1) break
    }
}

An incorrect code is generated during the conversion, as the break statement is not allowed outside the for loop.

fun test() {
    listOf(1, 2, 3).forEachIndexed { index, element ->
        println("$index: $element")
        if (index == 1) break
    }
}

comitative avatar Oct 08 '21 09:10 comitative

Fixed, please take another look.

  1. If the for loop has no braces, the loop body is lost after the conversion to forEachIndexed.

7d580831829c3f57d7c3b1e017cb13ec458ee0ff

  1. If the forEachIndexed call is an expression body of a function, the quick fix currently produces the syntactically incorrect result.

b162a37aed8171cd68bc9313513a94c972be39bc

  1. You did a good job to convert continue to return@forEachIndexed and vice versa, but the break statement is not supported.

0364a925946b6ce6794e1793439b293271e012db

t-kameyama avatar Oct 08 '21 13:10 t-kameyama

I'll resolve conflicts.

t-kameyama avatar Nov 24 '21 11:11 t-kameyama

I'll resolve conflicts.

Done

t-kameyama avatar Nov 24 '21 13:11 t-kameyama

Created a new PR for KTIJ-19679.

t-kameyama avatar Feb 01 '23 06:02 t-kameyama

Created a new PR for KTIJ-19680.

t-kameyama avatar Feb 04 '23 23:02 t-kameyama