From 054558ad95b328c8150243bd974ff16050f2daaa Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 12 Aug 2016 21:53:56 +0300 Subject: [PATCH] More correct checking of smart cast preserving --- .../loopToCallChain/matchAndConvert.kt | 34 ++++++++----------- ...Broken2.kt.todo => smartCastNotBroken2.kt} | 0 .../smartCastNotBroken2.kt.after2 | 13 +++++++ .../intentions/IntentionTest2Generated.java | 6 ++++ .../intentions/IntentionTestGenerated.java | 6 ++++ 5 files changed, 40 insertions(+), 19 deletions(-) rename idea/testData/intentions/loopToCallChain/{smartCastNotBroken2.kt.todo => smartCastNotBroken2.kt} (100%) create mode 100644 idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt.after2 diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt index b1ce4b731a5..a3e98bb32b9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt @@ -34,7 +34,6 @@ import org.jetbrains.kotlin.idea.util.CommentSaver import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils.isSubtypeOfClass @@ -273,40 +272,37 @@ private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: MatchRe val dataFlowInfo = bindingContext.getDataFlowInfo(loop) val newBindingContext = callChain.analyzeInContext(resolutionScope, loop, dataFlowInfo = dataFlowInfo) - val smartCastBroken = callChain.anyDescendantOfType { expression -> + var preservedSmartCastCount = 0 + callChain.forEachDescendantOfType { expression -> val smartCastType = expression.getCopyableUserData(SMARTCAST_KEY) if (smartCastType != null) { - if (newBindingContext[BindingContext.SMARTCAST, expression] != smartCastType && newBindingContext.getType(expression) != smartCastType) { - return@anyDescendantOfType true + if (newBindingContext[BindingContext.SMARTCAST, expression] == smartCastType || newBindingContext.getType(expression) == smartCastType) { + preservedSmartCastCount++ } - smartCastCount-- } val implicitReceiverSmartCastType = expression.getCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY) if (implicitReceiverSmartCastType != null) { - if (newBindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression] != implicitReceiverSmartCastType) { - return@anyDescendantOfType true + if (newBindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression] == implicitReceiverSmartCastType) { + preservedSmartCastCount++ } - smartCastCount-- } - - false } - if (smartCastBroken) return false + if (preservedSmartCastCount == smartCastCount) return true - assert(smartCastCount >= 0) - if (smartCastCount > 0) { // not all smart cast expressions has been found in the result, perform more expensive check - val expressionToBeReplaced = matchResult.transformationMatch.resultTransformation.expressionToBeReplacedByResultCallChain - if (!tryChangeAndCheckErrors(expressionToBeReplaced, loop) { it.replace(callChain) }) return false - } + // not all smart cast expressions has been found in the result or have the same type after conversion, perform more expensive check + val expressionToBeReplaced = matchResult.transformationMatch.resultTransformation.expressionToBeReplacedByResultCallChain + if (!tryChangeAndCheckErrors(expressionToBeReplaced, loop) { it.replace(callChain) }) return false return true } finally { - loop.forEachDescendantOfType { - it.putCopyableUserData(SMARTCAST_KEY, null) - it.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, null) + if (smartCastCount > 0) { + loop.forEachDescendantOfType { + it.putCopyableUserData(SMARTCAST_KEY, null) + it.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, null) + } } } } diff --git a/idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt.todo b/idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt similarity index 100% rename from idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt.todo rename to idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt diff --git a/idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt.after2 b/idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt.after2 new file mode 100644 index 00000000000..8749d18e93e --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt.after2 @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.map{}.firstOrNull{}'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.map{}.firstOrNull{}'" +fun foo(list: List, o: Any): Int? { + if (o is CharSequence) { + return list + .asSequence() + .map { it.length + (o as String).capitalize().hashCode() } + .map { it * o.length } + .firstOrNull { it > 1000 } + } + return 0 +} \ 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 f461cb1ff0a..0f9ad145297 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java @@ -977,6 +977,12 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 { doTest(fileName); } + @TestMetadata("smartCastNotBroken2.kt") + public void testSmartCastNotBroken2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt"); + doTest(fileName); + } + @TestMetadata("smartCastNotNullRequired.kt") public void testSmartCastNotNullRequired() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCastNotNullRequired.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index a8ef162ce4f..7e203561701 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -8095,6 +8095,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("smartCastNotBroken2.kt") + public void testSmartCastNotBroken2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCastNotBroken2.kt"); + doTest(fileName); + } + @TestMetadata("smartCastNotNullRequired.kt") public void testSmartCastNotNullRequired() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCastNotNullRequired.kt");