More correct checking of smart cast preserving

This commit is contained in:
Valentin Kipyatkov
2016-08-12 21:53:56 +03:00
parent bfed954e85
commit 054558ad95
5 changed files with 40 additions and 19 deletions
@@ -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<KtExpression> { expression ->
var preservedSmartCastCount = 0
callChain.forEachDescendantOfType<KtExpression> { 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<KtExpression> {
it.putCopyableUserData(SMARTCAST_KEY, null)
it.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, null)
if (smartCastCount > 0) {
loop.forEachDescendantOfType<KtExpression> {
it.putCopyableUserData(SMARTCAST_KEY, null)
it.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, null)
}
}
}
}
@@ -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<String>, o: Any): Int? {
if (o is CharSequence) {
<caret>return list
.asSequence()
.map { it.length + (o as String).capitalize().hashCode() }
.map { it * o.length }
.firstOrNull { it > 1000 }
}
return 0
}
@@ -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");
@@ -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");