diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt index d0a088ed6c8..5218b36b228 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt @@ -244,15 +244,16 @@ class KtPsiFactory(private val project: Project) { return (createFunction("fun foo() {$text}").bodyExpression as KtBlockExpression).statements.first() as KtDestructuringDeclaration } - fun createDestructuringDeclarationInFor(text: String): KtDestructuringDeclaration { - return ((createFunction("fun foo() {for ($text in foo) {} }").bodyExpression as KtBlockExpression).statements.first() as KtForExpression).destructuringParameter!! - } - fun createDestructuringParameter(text: String): KtDestructuringDeclaration { val dummyFun = createFunction("fun foo() { for ($text in foo) {} }") return ((dummyFun.bodyExpression as KtBlockExpression).statements.first() as KtForExpression).destructuringParameter!! } + fun createDestructuringParameterForLambda(text: String): KtParameter { + val dummyFun = createFunction("fun foo() = { $text -> }") + return (dummyFun.bodyExpression as KtLambdaExpression).functionLiteral.valueParameters.first() + } + fun createDeclaration(text: String): TDeclaration { val file = createFile(text) val declarations = file.declarations diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt index 8fe9c8d3e3c..314916838c9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt @@ -46,6 +46,8 @@ class DestructureIntention : SelfTargetingRangeIntention( ) { override fun applyTo(element: KtParameter, editor: Editor?) { val forLoop = element.parent as? KtForExpression + val functionLiteral = element.parent?.parent as? KtFunctionLiteral + if (forLoop == null && functionLiteral == null) return val (usagesToRemove, removeSelectorInLoopRange) = collectUsagesToRemove(element, forLoop) ?: return val loopRange = forLoop?.loopRange @@ -62,16 +64,23 @@ class DestructureIntention : SelfTargetingRangeIntention( } names.add(name) } - element.replace(factory.createDestructuringDeclarationInFor("(${names.joinToString()})")) + if (forLoop != null) { + element.replace(factory.createDestructuringParameter("(${names.joinToString()})")) - if (removeSelectorInLoopRange && loopRange is KtDotQualifiedExpression) { - loopRange.replace(loopRange.receiverExpression) + if (removeSelectorInLoopRange && loopRange is KtDotQualifiedExpression) { + loopRange.replace(loopRange.receiverExpression) + } + } + else if (functionLiteral != null) { + element.replace(factory.createDestructuringParameterForLambda("(${names.joinToString()})")) } } override fun applicabilityRange(element: KtParameter): TextRange? { - val forLoop = element.parent as? KtForExpression ?: return null - val usagesToRemove = collectUsagesToRemove(element, forLoop) + val forLoopIfAny = element.parent as? KtForExpression + if (forLoopIfAny == null && element.parent?.parent !is KtFunctionLiteral) return null + + val usagesToRemove = collectUsagesToRemove(element, forLoopIfAny) if (usagesToRemove != null && usagesToRemove.first.isNotEmpty()) { return element.textRange } diff --git a/idea/testData/intentions/destructuringInLambda/.intention b/idea/testData/intentions/destructuringInLambda/.intention new file mode 100644 index 00000000000..275f8c5a32b --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.DestructureIntention diff --git a/idea/testData/intentions/destructuringInLambda/dependentLocal.kt b/idea/testData/intentions/destructuringInLambda/dependentLocal.kt new file mode 100644 index 00000000000..c22b44d2128 --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/dependentLocal.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +data class XY(val x: Int, val y: Int) +fun test(xys: Array) { + xys.forEach { xy -> + val x = xy.x + println(x) + val y = xy.y + x + println(y) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/dependentLocal.kt.after b/idea/testData/intentions/destructuringInLambda/dependentLocal.kt.after new file mode 100644 index 00000000000..9e130328c81 --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/dependentLocal.kt.after @@ -0,0 +1,10 @@ +// WITH_RUNTIME + +data class XY(val x: Int, val y: Int) +fun test(xys: Array) { + xys.forEach { (x, y) -> + println(x) + val y = y + x + println(y) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/firstProperties.kt b/idea/testData/intentions/destructuringInLambda/firstProperties.kt new file mode 100644 index 00000000000..5b6db9f5f7f --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/firstProperties.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +fun foo() { + val list = listOf(MyClass(1, 2, 3), MyClass(2, 3, 4)) + list.forEach { klass -> + val a = klass.a + val b = klass.b + } +} + +data class MyClass(val a: Int, val b: Int, val c: Int) \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/firstProperties.kt.after b/idea/testData/intentions/destructuringInLambda/firstProperties.kt.after new file mode 100644 index 00000000000..f82b07c7592 --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/firstProperties.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +fun foo() { + val list = listOf(MyClass(1, 2, 3), MyClass(2, 3, 4)) + list.forEach { (a, b) -> + } +} + +data class MyClass(val a: Int, val b: Int, val c: Int) \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/fold.kt b/idea/testData/intentions/destructuringInLambda/fold.kt new file mode 100644 index 00000000000..82bcf5fbb16 --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/fold.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +data class XY(val x: String, val y: String) + +fun foo(list: List) = list.fold("") { prev, xy -> prev + xy.x + xy.y } \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/fold.kt.after b/idea/testData/intentions/destructuringInLambda/fold.kt.after new file mode 100644 index 00000000000..a09718cc4c7 --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/fold.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +data class XY(val x: String, val y: String) + +fun foo(list: List) = list.fold("") { prev, (x, y) -> prev + x + y } \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/last.kt b/idea/testData/intentions/destructuringInLambda/last.kt new file mode 100644 index 00000000000..7b0496530bb --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/last.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +data class My(val first: String, val second: Int) + +fun foo(list: List) { + list.forEach { my -> + println(my.second) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/last.kt.after b/idea/testData/intentions/destructuringInLambda/last.kt.after new file mode 100644 index 00000000000..718e1b854be --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/last.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +data class My(val first: String, val second: Int) + +fun foo(list: List) { + list.forEach { (first, second) -> + println(second) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/list.kt b/idea/testData/intentions/destructuringInLambda/list.kt new file mode 100644 index 00000000000..3b68dae39de --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/list.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +data class XY(val x: String, val y: String) + +fun foo(list: List) = list.map { it -> it.x + it.y } \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/list.kt.after b/idea/testData/intentions/destructuringInLambda/list.kt.after new file mode 100644 index 00000000000..8a58b1825fb --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/list.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +data class XY(val x: String, val y: String) + +fun foo(list: List) = list.map { (x, y) -> x + y } \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/nullable.kt b/idea/testData/intentions/destructuringInLambda/nullable.kt new file mode 100644 index 00000000000..d69b15c82ae --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/nullable.kt @@ -0,0 +1,11 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +data class XY(val x: String, val y: String) +fun test(xys: Array) { + xys.forEach { xy -> + val x = xy?.x + val y = xy?.y + println(x + y) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/simple.kt b/idea/testData/intentions/destructuringInLambda/simple.kt new file mode 100644 index 00000000000..6b7f98fcbd1 --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/simple.kt @@ -0,0 +1,5 @@ +data class XY(val x: String, val y: String) + +fun convert(xy: XY, foo: (XY) -> String) = foo(xy) + +fun foo(xy: XY) = convert(xy) { it -> it.x + it.y } \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/simple.kt.after b/idea/testData/intentions/destructuringInLambda/simple.kt.after new file mode 100644 index 00000000000..0ea3cb83715 --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/simple.kt.after @@ -0,0 +1,5 @@ +data class XY(val x: String, val y: String) + +fun convert(xy: XY, foo: (XY) -> String) = foo(xy) + +fun foo(xy: XY) = convert(xy) { (x, y) -> x + y } \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/variables.kt b/idea/testData/intentions/destructuringInLambda/variables.kt new file mode 100644 index 00000000000..355543e7bea --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/variables.kt @@ -0,0 +1,9 @@ +data class XY(val x: String, val y: String) + +fun convert(xy: XY, foo: (XY) -> String) = foo(xy) + +fun foo(xy: XY) = convert(xy) { it -> + val x = it.x + val y = it.y + x + y +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringInLambda/variables.kt.after b/idea/testData/intentions/destructuringInLambda/variables.kt.after new file mode 100644 index 00000000000..41cc506ee9d --- /dev/null +++ b/idea/testData/intentions/destructuringInLambda/variables.kt.after @@ -0,0 +1,7 @@ +data class XY(val x: String, val y: String) + +fun convert(xy: XY, foo: (XY) -> String) = foo(xy) + +fun foo(xy: XY) = convert(xy) { (x, y) -> + x + y +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 8a2a7bee687..636ce36ba93 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -6244,6 +6244,63 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } + @TestMetadata("idea/testData/intentions/destructuringInLambda") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class DestructuringInLambda extends AbstractIntentionTest { + public void testAllFilesPresentInDestructuringInLambda() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/destructuringInLambda"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("dependentLocal.kt") + public void testDependentLocal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringInLambda/dependentLocal.kt"); + doTest(fileName); + } + + @TestMetadata("firstProperties.kt") + public void testFirstProperties() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringInLambda/firstProperties.kt"); + doTest(fileName); + } + + @TestMetadata("fold.kt") + public void testFold() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringInLambda/fold.kt"); + doTest(fileName); + } + + @TestMetadata("last.kt") + public void testLast() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringInLambda/last.kt"); + doTest(fileName); + } + + @TestMetadata("list.kt") + public void testList() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringInLambda/list.kt"); + doTest(fileName); + } + + @TestMetadata("nullable.kt") + public void testNullable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringInLambda/nullable.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringInLambda/simple.kt"); + doTest(fileName); + } + + @TestMetadata("variables.kt") + public void testVariables() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringInLambda/variables.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/ifNullToElvis") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)