Checking that smart casts will not be broken by conversion
This commit is contained in:
Generated
+1
@@ -17,6 +17,7 @@
|
||||
<w>renderers</w>
|
||||
<w>rparenth</w>
|
||||
<w>selectioner</w>
|
||||
<w>smartcast</w>
|
||||
<w>summand</w>
|
||||
<w>unpluralize</w>
|
||||
<w>weighers</w>
|
||||
|
||||
-2133
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.intentions.loopToCallChain
|
||||
|
||||
import com.intellij.openapi.util.Key
|
||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndAssignTransformation
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndReturnTransformation
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
||||
@@ -23,7 +27,15 @@ import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FlatMapTran
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapTransformation
|
||||
import org.jetbrains.kotlin.idea.intentions.negate
|
||||
import org.jetbrains.kotlin.idea.util.CommentSaver
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
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.bindingContextUtil.getDataFlowInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||
import java.util.*
|
||||
|
||||
object MatcherRegistrar {
|
||||
@@ -59,7 +71,8 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
|
||||
val match = matcher.match(state)
|
||||
if (match != null) {
|
||||
sequenceTransformations.addAll(match.sequenceTransformations)
|
||||
return ResultTransformationMatch(match.resultTransformation, sequenceTransformations)
|
||||
val resultMatch = ResultTransformationMatch(match.resultTransformation, sequenceTransformations)
|
||||
return resultMatch.check { checkSmartCastsPreserved(loop, it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +102,57 @@ fun convertLoop(loop: KtForExpression, matchResult: ResultTransformationMatch):
|
||||
return result
|
||||
}
|
||||
|
||||
private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: ResultTransformationMatch): Boolean {
|
||||
val bindingContext = loop.analyze(BodyResolveMode.FULL)
|
||||
|
||||
val SMARTCAST_KEY = Key<KotlinType>("SMARTCAST_KEY")
|
||||
val IMPLICIT_RECEIVER_SMARTCAST_KEY = Key<KotlinType>("IMPLICIT_RECEIVER_SMARTCAST")
|
||||
|
||||
var smartCastsFound = false
|
||||
try {
|
||||
loop.forEachDescendantOfType<KtExpression> { expression ->
|
||||
bindingContext[BindingContext.SMARTCAST, expression]?.let {
|
||||
expression.putCopyableUserData(SMARTCAST_KEY, it)
|
||||
smartCastsFound = true
|
||||
}
|
||||
|
||||
bindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression]?.let {
|
||||
expression.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, it)
|
||||
smartCastsFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!smartCastsFound) return true // optimization
|
||||
|
||||
val callChain = matchResult.generateCallChain(loop)
|
||||
|
||||
val resolutionScope = loop.getResolutionScope(bindingContext, loop.getResolutionFacade())
|
||||
val dataFlowInfo = bindingContext.getDataFlowInfo(loop)
|
||||
val newBindingContext = callChain.analyzeInContext(resolutionScope, loop, dataFlowInfo = dataFlowInfo)
|
||||
|
||||
val smartCastBroken = callChain.anyDescendantOfType<KtExpression> { expression ->
|
||||
val smartCastType = expression.getCopyableUserData(SMARTCAST_KEY)
|
||||
if (smartCastType != null && newBindingContext[BindingContext.SMARTCAST, expression] != smartCastType && newBindingContext.getType(expression) != smartCastType) {
|
||||
return@anyDescendantOfType true
|
||||
}
|
||||
|
||||
val implicitReceiverSmartCastType = expression.getCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY)
|
||||
if (implicitReceiverSmartCastType != null && newBindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression] != implicitReceiverSmartCastType) {
|
||||
return@anyDescendantOfType true
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
return !smartCastBroken
|
||||
}
|
||||
finally {
|
||||
if (smartCastsFound) {
|
||||
loop.forEachDescendantOfType<KtExpression> { it.putCopyableUserData(SMARTCAST_KEY, null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResultTransformationMatch.generateCallChain(loop: KtForExpression): KtExpression {
|
||||
var sequenceTransformations = sequenceTransformations
|
||||
val last = sequenceTransformations.lastOrNull()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.intentions.loopToCallChain
|
||||
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.KtNodeTypes
|
||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
@@ -57,7 +59,10 @@ fun generateLambda(workingVariable: KtCallableDeclaration, expression: KtExpress
|
||||
|
||||
val itExpr = psiFactory.createSimpleName("it")
|
||||
for (usage in usages) {
|
||||
usage.replace(itExpr)
|
||||
val replaced = usage.replaced(itExpr)
|
||||
|
||||
// we need to copy user data for checkSmartCastsPreserved() to work
|
||||
(usage.node as UserDataHolderBase).copyCopyableDataTo(replaced.node as UserDataHolderBase)
|
||||
}
|
||||
|
||||
return psiFactory.createExpressionByPattern("{ $0 }", lambdaExpression.bodyExpression!!) as KtLambdaExpression
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>, o: Any): Int? {
|
||||
if (o is Int) {
|
||||
<caret>for (s in list) {
|
||||
val length = s.length + o
|
||||
if (length > 0) {
|
||||
val x = length * o.hashCode()
|
||||
return x
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>, o: Any): Int? {
|
||||
if (o is Int) {
|
||||
return list
|
||||
.map { it.length + o }
|
||||
.filter { it > 0 }
|
||||
.map { it * o.hashCode() }
|
||||
.firstOrNull()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>, o: Int?): Int? {
|
||||
<caret>for (s in list) {
|
||||
val length = s.length + o!!
|
||||
if (length > 0) {
|
||||
val x = length * o
|
||||
return x
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>, o: Any): Int? {
|
||||
<caret>for (s in list) {
|
||||
val length = s.length + (o as Int)
|
||||
if (length > 0) {
|
||||
val x = length * o.hashCode()
|
||||
return x
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>, o: Any): Int? {
|
||||
<caret>return list
|
||||
.map { it.length + (o as Int) }
|
||||
.filter { it > 0 }
|
||||
.map { it * o.hashCode() }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>, o: Any): Int? {
|
||||
<caret>for (s in list) {
|
||||
val length = s.length + (o as Int)
|
||||
if (length > 0) {
|
||||
val x = length * o
|
||||
return x
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<Any>, o: Any): Int? {
|
||||
<caret>for (s in list) {
|
||||
if (s is String && s.length > 0) {
|
||||
val x = s.length * 2
|
||||
return x
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun Any.foo(list: List<String>): Int? {
|
||||
<caret>for (s in list) {
|
||||
if (s.length > 0 && this is String) {
|
||||
val result = s.length + length
|
||||
return result
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user