Checking that smart casts will not be broken by conversion

This commit is contained in:
Valentin Kipyatkov
2016-03-24 12:37:35 +03:00
parent 978fc468fa
commit 8c498af3d8
12 changed files with 162 additions and 2135 deletions
+1
View File
@@ -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>
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
}