More correct checking of smart cast preserving
This commit is contained in:
@@ -34,7 +34,6 @@ import org.jetbrains.kotlin.idea.util.CommentSaver
|
|||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils.isSubtypeOfClass
|
import org.jetbrains.kotlin.resolve.DescriptorUtils.isSubtypeOfClass
|
||||||
@@ -273,40 +272,37 @@ private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: MatchRe
|
|||||||
val dataFlowInfo = bindingContext.getDataFlowInfo(loop)
|
val dataFlowInfo = bindingContext.getDataFlowInfo(loop)
|
||||||
val newBindingContext = callChain.analyzeInContext(resolutionScope, loop, dataFlowInfo = dataFlowInfo)
|
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)
|
val smartCastType = expression.getCopyableUserData(SMARTCAST_KEY)
|
||||||
if (smartCastType != null) {
|
if (smartCastType != null) {
|
||||||
if (newBindingContext[BindingContext.SMARTCAST, expression] != smartCastType && newBindingContext.getType(expression) != smartCastType) {
|
if (newBindingContext[BindingContext.SMARTCAST, expression] == smartCastType || newBindingContext.getType(expression) == smartCastType) {
|
||||||
return@anyDescendantOfType true
|
preservedSmartCastCount++
|
||||||
}
|
}
|
||||||
smartCastCount--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val implicitReceiverSmartCastType = expression.getCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY)
|
val implicitReceiverSmartCastType = expression.getCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY)
|
||||||
if (implicitReceiverSmartCastType != null) {
|
if (implicitReceiverSmartCastType != null) {
|
||||||
if (newBindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression] != implicitReceiverSmartCastType) {
|
if (newBindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression] == implicitReceiverSmartCastType) {
|
||||||
return@anyDescendantOfType true
|
preservedSmartCastCount++
|
||||||
}
|
}
|
||||||
smartCastCount--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smartCastBroken) return false
|
if (preservedSmartCastCount == smartCastCount) return true
|
||||||
|
|
||||||
assert(smartCastCount >= 0)
|
// not all smart cast expressions has been found in the result or have the same type after conversion, perform more expensive check
|
||||||
if (smartCastCount > 0) { // not all smart cast expressions has been found in the result, perform more expensive check
|
val expressionToBeReplaced = matchResult.transformationMatch.resultTransformation.expressionToBeReplacedByResultCallChain
|
||||||
val expressionToBeReplaced = matchResult.transformationMatch.resultTransformation.expressionToBeReplacedByResultCallChain
|
if (!tryChangeAndCheckErrors(expressionToBeReplaced, loop) { it.replace(callChain) }) return false
|
||||||
if (!tryChangeAndCheckErrors(expressionToBeReplaced, loop) { it.replace(callChain) }) return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loop.forEachDescendantOfType<KtExpression> {
|
if (smartCastCount > 0) {
|
||||||
it.putCopyableUserData(SMARTCAST_KEY, null)
|
loop.forEachDescendantOfType<KtExpression> {
|
||||||
it.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, null)
|
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);
|
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")
|
@TestMetadata("smartCastNotNullRequired.kt")
|
||||||
public void testSmartCastNotNullRequired() throws Exception {
|
public void testSmartCastNotNullRequired() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCastNotNullRequired.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCastNotNullRequired.kt");
|
||||||
|
|||||||
@@ -8095,6 +8095,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("smartCastNotNullRequired.kt")
|
||||||
public void testSmartCastNotNullRequired() throws Exception {
|
public void testSmartCastNotNullRequired() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCastNotNullRequired.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCastNotNullRequired.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user