Fixed some cases when smart cast problems were not detected
This commit is contained in:
@@ -27,6 +27,9 @@ abstract class ReplaceLoopResultTransformation(override val loop: KtForExpressio
|
|||||||
|
|
||||||
override val commentSavingRange = PsiChildRange.singleElement(loop.unwrapIfLabeled())
|
override val commentSavingRange = PsiChildRange.singleElement(loop.unwrapIfLabeled())
|
||||||
|
|
||||||
|
override val expressionToBeReplacedByResultCallChain: KtExpression
|
||||||
|
get() = loop.unwrapIfLabeled()
|
||||||
|
|
||||||
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
||||||
return loop.unwrapIfLabeled().replaced(resultCallChain)
|
return loop.unwrapIfLabeled().replaced(resultCallChain)
|
||||||
}
|
}
|
||||||
@@ -39,6 +42,9 @@ abstract class AssignToVariableResultTransformation(
|
|||||||
|
|
||||||
override val commentSavingRange = PsiChildRange(initialization.initializationStatement, loop.unwrapIfLabeled())
|
override val commentSavingRange = PsiChildRange(initialization.initializationStatement, loop.unwrapIfLabeled())
|
||||||
|
|
||||||
|
override val expressionToBeReplacedByResultCallChain: KtExpression
|
||||||
|
get() = initialization.initializer
|
||||||
|
|
||||||
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
||||||
initialization.initializer.replace(resultCallChain)
|
initialization.initializer.replace(resultCallChain)
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ interface ResultTransformation : Transformation {
|
|||||||
|
|
||||||
val commentSavingRange: PsiChildRange
|
val commentSavingRange: PsiChildRange
|
||||||
|
|
||||||
|
val expressionToBeReplacedByResultCallChain: KtExpression
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementations of this method are obliged to update [commentSavingRangeHolder] when deleting or adding any element into the tree
|
* Implementations of this method are obliged to update [commentSavingRangeHolder] when deleting or adding any element into the tree
|
||||||
* except for the loop itself and the result element returned from this method
|
* except for the loop itself and the result element returned from this method
|
||||||
|
|||||||
@@ -190,21 +190,21 @@ private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: MatchRe
|
|||||||
val SMARTCAST_KEY = Key<KotlinType>("SMARTCAST_KEY")
|
val SMARTCAST_KEY = Key<KotlinType>("SMARTCAST_KEY")
|
||||||
val IMPLICIT_RECEIVER_SMARTCAST_KEY = Key<KotlinType>("IMPLICIT_RECEIVER_SMARTCAST")
|
val IMPLICIT_RECEIVER_SMARTCAST_KEY = Key<KotlinType>("IMPLICIT_RECEIVER_SMARTCAST")
|
||||||
|
|
||||||
var smartCastsFound = false
|
var smartCastCount = 0
|
||||||
try {
|
try {
|
||||||
loop.forEachDescendantOfType<KtExpression> { expression ->
|
loop.forEachDescendantOfType<KtExpression> { expression ->
|
||||||
bindingContext[BindingContext.SMARTCAST, expression]?.let {
|
bindingContext[BindingContext.SMARTCAST, expression]?.let {
|
||||||
expression.putCopyableUserData(SMARTCAST_KEY, it)
|
expression.putCopyableUserData(SMARTCAST_KEY, it)
|
||||||
smartCastsFound = true
|
smartCastCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression]?.let {
|
bindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression]?.let {
|
||||||
expression.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, it)
|
expression.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, it)
|
||||||
smartCastsFound = true
|
smartCastCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!smartCastsFound) return true // optimization
|
if (smartCastCount == 0) return true // optimization
|
||||||
|
|
||||||
val callChain = matchResult.generateCallChain(loop)
|
val callChain = matchResult.generateCallChain(loop)
|
||||||
|
|
||||||
@@ -214,23 +214,40 @@ private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: MatchRe
|
|||||||
|
|
||||||
val smartCastBroken = callChain.anyDescendantOfType<KtExpression> { expression ->
|
val smartCastBroken = callChain.anyDescendantOfType<KtExpression> { expression ->
|
||||||
val smartCastType = expression.getCopyableUserData(SMARTCAST_KEY)
|
val smartCastType = expression.getCopyableUserData(SMARTCAST_KEY)
|
||||||
if (smartCastType != null && newBindingContext[BindingContext.SMARTCAST, expression] != smartCastType && newBindingContext.getType(expression) != smartCastType) {
|
if (smartCastType != null) {
|
||||||
return@anyDescendantOfType true
|
if (newBindingContext[BindingContext.SMARTCAST, expression] != smartCastType && newBindingContext.getType(expression) != smartCastType) {
|
||||||
|
return@anyDescendantOfType true
|
||||||
|
}
|
||||||
|
smartCastCount--
|
||||||
}
|
}
|
||||||
|
|
||||||
val implicitReceiverSmartCastType = expression.getCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY)
|
val implicitReceiverSmartCastType = expression.getCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY)
|
||||||
if (implicitReceiverSmartCastType != null && newBindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression] != implicitReceiverSmartCastType) {
|
if (implicitReceiverSmartCastType != null) {
|
||||||
return@anyDescendantOfType true
|
if (newBindingContext[BindingContext.IMPLICIT_RECEIVER_SMARTCAST, expression] != implicitReceiverSmartCastType) {
|
||||||
|
return@anyDescendantOfType true
|
||||||
|
}
|
||||||
|
smartCastCount--
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
return !smartCastBroken
|
if (smartCastBroken) return false
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (smartCastsFound) {
|
if (smartCastCount > 0) {
|
||||||
loop.forEachDescendantOfType<KtExpression> { it.putCopyableUserData(SMARTCAST_KEY, null) }
|
loop.forEachDescendantOfType<KtExpression> {
|
||||||
|
it.putCopyableUserData(SMARTCAST_KEY, null)
|
||||||
|
it.putCopyableUserData(IMPLICIT_RECEIVER_SMARTCAST_KEY, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -127,6 +127,9 @@ object FindTransformationMatcher : TransformationMatcher {
|
|||||||
return generator.generate(chainedCallGenerator)
|
return generator.generate(chainedCallGenerator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val expressionToBeReplacedByResultCallChain: KtExpression
|
||||||
|
get() = endReturn.returnedExpression!!
|
||||||
|
|
||||||
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
||||||
endReturn.returnedExpression!!.replace(resultCallChain)
|
endReturn.returnedExpression!!.replace(resultCallChain)
|
||||||
loop.deleteWithLabels()
|
loop.deleteWithLabels()
|
||||||
|
|||||||
@@ -162,35 +162,43 @@ fun KtExpression.isSimpleCollectionInstantiation(): CollectionKind? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun canChangeLocalVariableType(variable: KtProperty, newTypeText: String, loop: KtForExpression): Boolean {
|
fun canChangeLocalVariableType(variable: KtProperty, newTypeText: String, loop: KtForExpression): Boolean {
|
||||||
val bindingContext = variable.analyze(BodyResolveMode.FULL)
|
return tryChangeAndCheckErrors(variable, loop) {
|
||||||
|
it.typeReference = KtPsiFactory(it).createType(newTypeText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <TExpression : KtExpression> tryChangeAndCheckErrors(
|
||||||
|
expressionToChange: TExpression,
|
||||||
|
scopeToExclude: KtElement,
|
||||||
|
performChange: (TExpression) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
val bindingContext = expressionToChange.analyze(BodyResolveMode.FULL)
|
||||||
|
|
||||||
// analyze the closest block which is not used as expression
|
// analyze the closest block which is not used as expression
|
||||||
val block = variable.parents
|
val block = expressionToChange.parents
|
||||||
.filterIsInstance<KtBlockExpression>()
|
.filterIsInstance<KtBlockExpression>()
|
||||||
.firstOrNull { bindingContext[BindingContext.USED_AS_EXPRESSION, it] != true }
|
.firstOrNull { bindingContext[BindingContext.USED_AS_EXPRESSION, it] != true }
|
||||||
?: return false
|
?: return true
|
||||||
|
|
||||||
val KEY = Key<Unit>("KEY")
|
val EXPRESSION = Key<Unit>("EXPRESSION")
|
||||||
block.putCopyableUserData(KEY, Unit)
|
val SCOPE_TO_EXCLUDE = Key<Unit>("SCOPE_TO_EXCLUDE")
|
||||||
variable.putCopyableUserData(KEY, Unit)
|
expressionToChange.putCopyableUserData(EXPRESSION, Unit)
|
||||||
loop.putCopyableUserData(KEY, Unit)
|
scopeToExclude.putCopyableUserData(SCOPE_TO_EXCLUDE, Unit)
|
||||||
|
|
||||||
val fileCopy = block.containingFile.copied()
|
val blockCopy = block.copied()
|
||||||
val blockCopy: KtBlockExpression
|
val expressionCopy: TExpression
|
||||||
val variableCopy: KtProperty
|
val scopeToExcludeCopy: KtElement
|
||||||
val loopCopy: KtForExpression
|
@Suppress("UNCHECKED_CAST")
|
||||||
try {
|
try {
|
||||||
blockCopy = fileCopy.findDescendantOfType<KtBlockExpression> { it.getCopyableUserData(KEY) != null }!!
|
expressionCopy = blockCopy.findDescendantOfType<KtExpression> { it.getCopyableUserData(EXPRESSION) != null } as TExpression
|
||||||
variableCopy = blockCopy.findDescendantOfType<KtProperty> { it.getCopyableUserData(KEY) != null }!!
|
scopeToExcludeCopy = blockCopy.findDescendantOfType<KtElement> { it.getCopyableUserData(SCOPE_TO_EXCLUDE) != null }!!
|
||||||
loopCopy = blockCopy.findDescendantOfType<KtForExpression> { it.getCopyableUserData(KEY) != null }!!
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
block.putCopyableUserData(KEY, null)
|
expressionToChange.putCopyableUserData(EXPRESSION, null)
|
||||||
variable.putCopyableUserData(KEY, null)
|
scopeToExclude.putCopyableUserData(SCOPE_TO_EXCLUDE, null)
|
||||||
loop.putCopyableUserData(KEY, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
variableCopy.typeReference = KtPsiFactory(block).createType(newTypeText)
|
performChange(expressionCopy)
|
||||||
|
|
||||||
val resolutionScope = block.getResolutionScope(bindingContext, block.getResolutionFacade())
|
val resolutionScope = block.getResolutionScope(bindingContext, block.getResolutionFacade())
|
||||||
val newBindingContext = blockCopy.analyzeInContext(scope = resolutionScope,
|
val newBindingContext = blockCopy.analyzeInContext(scope = resolutionScope,
|
||||||
@@ -198,7 +206,7 @@ fun canChangeLocalVariableType(variable: KtProperty, newTypeText: String, loop:
|
|||||||
dataFlowInfo = bindingContext.getDataFlowInfo(block),
|
dataFlowInfo = bindingContext.getDataFlowInfo(block),
|
||||||
trace = DelegatingBindingTrace(bindingContext, "Temporary trace"))
|
trace = DelegatingBindingTrace(bindingContext, "Temporary trace"))
|
||||||
//TODO: what if there were errors before?
|
//TODO: what if there were errors before?
|
||||||
return newBindingContext.diagnostics.none { it.severity == Severity.ERROR && !loopCopy.isAncestor(it.psiElement) }
|
return newBindingContext.diagnostics.none { it.severity == Severity.ERROR && !scopeToExcludeCopy.isAncestor(it.psiElement) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val NO_SIDE_EFFECT_STANDARD_CLASSES = setOf(
|
private val NO_SIDE_EFFECT_STANDARD_CLASSES = setOf(
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// INTENTION_TEXT: "Replace with 'map{}.map{}.firstOrNull{}'"
|
||||||
|
fun foo(list: List<String>, o: Any): Int? {
|
||||||
|
if (o is CharSequence) {
|
||||||
|
<caret>return list
|
||||||
|
.map { it.length + (o as String).capitalize().hashCode() }
|
||||||
|
.map { it * o.length }
|
||||||
|
.firstOrNull { it > 1000 }
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// INTENTION_TEXT: "Replace with 'map{}.map{}.firstOrNull{}'"
|
||||||
|
fun foo(list: List<String>, o: Any): Int? {
|
||||||
|
if (o is CharSequence) {
|
||||||
|
<caret>for (s in list) {
|
||||||
|
val a = s.length + (o as String).capitalize().hashCode()
|
||||||
|
val x = a * o.length
|
||||||
|
if (x > 1000) return x
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IS_APPLICABLE: false
|
||||||
|
fun foo(list: List<Any>): String? {
|
||||||
|
<caret>for (o in list) {
|
||||||
|
if (bar(o as String)) {
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(s: String): Boolean = true
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IS_APPLICABLE: false
|
||||||
|
fun foo(list: List<Any>): String? {
|
||||||
|
var v: String? = null
|
||||||
|
<caret>for (o in list) {
|
||||||
|
if (bar(o as String)) {
|
||||||
|
v = o
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(s: String): Boolean = true
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IS_APPLICABLE: false
|
||||||
|
fun foo(list: List<Any>, target: MutableCollection<String>) {
|
||||||
|
<caret>for (o in list) {
|
||||||
|
if (bar(o as String)) {
|
||||||
|
target.add(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(s: String): Boolean = true
|
||||||
Reference in New Issue
Block a user