From e5fcfae969ec5202c3fe0234206387e640640bdc Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 17 Oct 2016 16:48:22 +0300 Subject: [PATCH] KT-13998 Loop to call chain converter produces 'none' instead of 'all' #KT-13998 Fixed --- .../result/FindTransformationMatcher.kt | 37 +++++++++++-------- .../loopToCallChain/sequence/Condition.kt | 2 +- .../intentions/loopToCallChain/any/KT13998.kt | 10 +++++ .../loopToCallChain/any/KT13998.kt.after | 7 ++++ .../loopToCallChain/any/KT13998_1.kt | 13 +++++++ .../loopToCallChain/any/KT13998_1.kt.after | 10 +++++ .../intentions/IntentionTest2Generated.java | 12 ++++++ .../intentions/IntentionTestGenerated.java | 12 ++++++ 8 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 idea/testData/intentions/loopToCallChain/any/KT13998.kt create mode 100644 idea/testData/intentions/loopToCallChain/any/KT13998.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/any/KT13998_1.kt create mode 100644 idea/testData/intentions/loopToCallChain/any/KT13998_1.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt index f57d5ef41ad..c7b533b099e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.Condition import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformationBase import org.jetbrains.kotlin.idea.intentions.negate import org.jetbrains.kotlin.lexer.KtTokens @@ -225,7 +226,7 @@ object FindTransformationMatcher : TransformationMatcher { assert(valueIfFound.isPhysical) assert(valueIfNotFound.isPhysical) - val filter = filterTransformation?.effectiveCondition?.asExpression() + val filterCondition = filterTransformation?.effectiveCondition if (indexVariable != null) { if (filterTransformation == null) return null // makes no sense, indexVariable must be always null @@ -233,14 +234,15 @@ object FindTransformationMatcher : TransformationMatcher { //TODO: what if value when not found is not "-1"? if (valueIfFound.isVariableReference(indexVariable) && valueIfNotFound.text == "-1") { - val containsArgument = filter!!.isFilterForContainsOperation(inputVariable, loop) + val filterExpression = filterCondition!!.asExpression() + val containsArgument = filterExpression.isFilterForContainsOperation(inputVariable, loop) if (containsArgument != null) { val functionName = if (findFirst) "indexOf" else "lastIndexOf" return SimpleGenerator(functionName, inputVariable, null, containsArgument) } else { val functionName = if (findFirst) "indexOfFirst" else "indexOfLast" - return SimpleGenerator(functionName, inputVariable, filter) + return SimpleGenerator(functionName, inputVariable, filterExpression) } } @@ -267,16 +269,16 @@ object FindTransformationMatcher : TransformationMatcher { when { valueIfFound.isVariableReference(inputVariable) -> { val functionName = if (findFirst) "firstOrNull" else "lastOrNull" - val generator = SimpleGenerator(functionName, inputVariable, filter) + val generator = SimpleGenerator(functionName, inputVariable, filterCondition?.asExpression()) return generator.useElvisOperatorIfNeeded() } valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> { - return buildFoundFlagGenerator(loop, inputVariable, filter, negated = false) + return buildFoundFlagGenerator(loop, inputVariable, filterCondition, negated = false) } valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> { - return buildFoundFlagGenerator(loop, inputVariable, filter, negated = true) + return buildFoundFlagGenerator(loop, inputVariable, filterCondition, negated = true) } inputVariable.hasUsages(valueIfFound) -> { @@ -288,9 +290,9 @@ object FindTransformationMatcher : TransformationMatcher { val receiver = qualifiedExpression.receiverExpression val selector = qualifiedExpression.selectorExpression if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) { - return object: FindOperationGenerator("firstOrNull", filter != null, chainCallCount = 2) { + return object: FindOperationGenerator("firstOrNull", filterCondition != null, chainCallCount = 2) { override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { - val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, inputVariable, filter) + val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, inputVariable, filterCondition?.asExpression()) return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true) } }.useElvisOperatorIfNeeded() @@ -300,9 +302,9 @@ object FindTransformationMatcher : TransformationMatcher { // in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found if (inputVariableCanHoldNull) return null - return object : FindOperationGenerator("firstOrNull", filter != null, chainCallCount = 2 /* also includes "let" */) { + return object : FindOperationGenerator("firstOrNull", filterCondition != null, chainCallCount = 2 /* also includes "let" */) { override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { - val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, inputVariable, filter) + val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, inputVariable, filterCondition?.asExpression()) val letBody = generateLambda(inputVariable, valueIfFound) return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true) } @@ -310,7 +312,7 @@ object FindTransformationMatcher : TransformationMatcher { } else -> { - val generator = buildFoundFlagGenerator(loop, inputVariable, filter, negated = false) + val generator = buildFoundFlagGenerator(loop, inputVariable, filterCondition, negated = false) return object : FindOperationGenerator(generator) { override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression { val chainedCall = generator.generate(chainedCallGenerator) @@ -325,14 +327,15 @@ object FindTransformationMatcher : TransformationMatcher { private fun buildFoundFlagGenerator( loop: KtForExpression, inputVariable: KtCallableDeclaration, - filter: KtExpression?, + filter: Condition?, negated: Boolean ): FindOperationGenerator { if (filter == null) { - return SimpleGenerator(if (negated) "none" else "any", inputVariable, filter) + return SimpleGenerator(if (negated) "none" else "any", inputVariable, null) } - val containsArgument = filter.isFilterForContainsOperation(inputVariable, loop) + val filterExpression = filter.asExpression() + val containsArgument = filterExpression.isFilterForContainsOperation(inputVariable, loop) if (containsArgument != null) { val generator = SimpleGenerator("contains", inputVariable, null, containsArgument) if (negated) { @@ -347,7 +350,11 @@ object FindTransformationMatcher : TransformationMatcher { } } - return SimpleGenerator(if (negated) "none" else "any", inputVariable, filter) + if (filterExpression is KtPrefixExpression && filterExpression.operationToken == KtTokens.EXCL) { + return SimpleGenerator(if (negated) "any" else "none", inputVariable, filter.asNegatedExpression()) + } + + return SimpleGenerator(if (negated) "none" else "any", inputVariable, filterExpression) } private fun KtExpression.isFilterForContainsOperation(inputVariable: KtCallableDeclaration, loop: KtForExpression): KtExpression? { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/Condition.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/Condition.kt index a277d402dc9..3384051f4c4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/Condition.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/Condition.kt @@ -83,7 +83,7 @@ class CompositeCondition private constructor(val conditions: Listfor (e in foo) { + if (!e) return false + } + return true +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any/KT13998.kt.after b/idea/testData/intentions/loopToCallChain/any/KT13998.kt.after new file mode 100644 index 00000000000..909c6e656a1 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any/KT13998.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'any{}'" +// IS_APPLICABLE_2: false +fun foo(): Boolean { + val foo = listOf(true, true) + return foo.any { it } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any/KT13998_1.kt b/idea/testData/intentions/loopToCallChain/any/KT13998_1.kt new file mode 100644 index 00000000000..8eb03d58069 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any/KT13998_1.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'any{}'" +// IS_APPLICABLE_2: false +fun foo(): Boolean { + val foo = listOf(true, true) + for (e in foo) { + if (!(f1(e) && f2(e))) return false + } + return true +} + +fun f1(b: Boolean): Boolean = TODO() +fun f2(b: Boolean): Boolean = TODO() \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any/KT13998_1.kt.after b/idea/testData/intentions/loopToCallChain/any/KT13998_1.kt.after new file mode 100644 index 00000000000..c2b043ec7c2 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any/KT13998_1.kt.after @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'any{}'" +// IS_APPLICABLE_2: false +fun foo(): Boolean { + val foo = listOf(true, true) + return foo.any { f1(it) && f2(it) } +} + +fun f1(b: Boolean): Boolean = TODO() +fun f2(b: Boolean): Boolean = TODO() \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java index 5698797d008..7a7d7d31d26 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java @@ -181,6 +181,18 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 { doTest(fileName); } + @TestMetadata("KT13998.kt") + public void testKT13998() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/KT13998.kt"); + doTest(fileName); + } + + @TestMetadata("KT13998_1.kt") + public void testKT13998_1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/KT13998_1.kt"); + doTest(fileName); + } + @TestMetadata("none.kt") public void testNone() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/none.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 8a42705ce52..6e7770f4a53 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -8089,6 +8089,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("KT13998.kt") + public void testKT13998() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/KT13998.kt"); + doTest(fileName); + } + + @TestMetadata("KT13998_1.kt") + public void testKT13998_1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/KT13998_1.kt"); + doTest(fileName); + } + @TestMetadata("none.kt") public void testNone() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/any/none.kt");