From 176ba937baf33a1c281facf13d5663ece29bdbe4 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Tue, 24 Mar 2015 22:24:13 +0300 Subject: [PATCH] Partial body resolve filter works more precisely for if-statements --- .../org/jetbrains/kotlin/utils/addToStdlib.kt | 4 +- .../resolve/lazy/PartialBodyResolveFilter.kt | 90 +++++++++++++------ .../IfCallWithConditionReturn.dump | 6 ++ .../IfCallWithConditionReturn.kt | 7 ++ .../partialBodyResolve/IfIsReturn.dump | 6 ++ .../resolve/partialBodyResolve/IfIsReturn.kt | 4 + .../IfNullAndNullReturn.dump | 6 ++ .../partialBodyResolve/IfNullAndNullReturn.kt | 7 ++ .../IfNullOrNullReturn.dump | 9 ++ .../partialBodyResolve/IfNullOrNullReturn.kt | 7 ++ .../PartialBodyResolveTestGenerated.java | 31 ++++++- 11 files changed, 150 insertions(+), 27 deletions(-) create mode 100644 idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.dump create mode 100644 idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.kt create mode 100644 idea/testData/resolve/partialBodyResolve/IfIsReturn.dump create mode 100644 idea/testData/resolve/partialBodyResolve/IfIsReturn.kt create mode 100644 idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.dump create mode 100644 idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.kt create mode 100644 idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.dump create mode 100644 idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.kt diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/addToStdlib.kt b/compiler/util/src/org/jetbrains/kotlin/utils/addToStdlib.kt index 1151a39fcf6..58023efae68 100644 --- a/compiler/util/src/org/jetbrains/kotlin/utils/addToStdlib.kt +++ b/compiler/util/src/org/jetbrains/kotlin/utils/addToStdlib.kt @@ -53,4 +53,6 @@ public inline fun Array<*>.firstIsInstance(): T { throw NoSuchElementException("No element of given type found") } -public fun streamOfLazyValues(vararg elements: () -> T): Stream = elements.stream().map { it() } \ No newline at end of file +public fun streamOfLazyValues(vararg elements: () -> T): Stream = elements.stream().map { it() } + +public fun Pair.swap(): Pair = Pair(second, first) \ No newline at end of file diff --git a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt index 5378caece94..24244c9ba0e 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.resolve.StatementFilter import org.jetbrains.kotlin.psi.psiUtil.isProbablyNothing +import org.jetbrains.kotlin.utils.addToStdlib.swap //TODO: do resolve anonymous object's body @@ -189,16 +190,24 @@ class PartialBodyResolveFilter( val thenBranch = expression.getThen() val elseBranch = expression.getElse() - val smartCastNames = collectPossiblySmartCastInCondition(condition).filter(filter) - if (smartCastNames.isNotEmpty()) { - val exits = collectAlwaysExitPoints(thenBranch) + collectAlwaysExitPoints(elseBranch) - if (exits.isNotEmpty()) { - for (name in smartCastNames) { - addPlaces(name, exits) + val (thenSmartCastNames, elseSmartCastNames) = possiblySmartCastInCondition(condition) + + fun processBranchExits(smartCastNames: Collection, branch: JetExpression?) { + if (branch == null) return + val filteredNames = smartCastNames.filter(filter) + if (filteredNames.isNotEmpty()) { + val exits = collectAlwaysExitPoints(branch) + if (exits.isNotEmpty()) { + for (name in filteredNames) { + addPlaces(name, exits) + } } } } + processBranchExits(thenSmartCastNames, elseBranch) + processBranchExits(elseSmartCastNames, thenBranch) + condition?.accept(this) if (thenBranch != null && elseBranch != null) { @@ -243,29 +252,59 @@ class PartialBodyResolveFilter( } /** - * Returns names of expressions that would possibly be smart cast after - * either a statement "if (condition) return" or "if (!condition) return" + * Returns names of expressions that would possibly be smart cast + * in then (first component) and else (second component) + * branches of an if-statement with such condition */ - private fun collectPossiblySmartCastInCondition(condition: JetExpression?): Set { - val result = HashSet() - condition?.accept(object : ControlFlowVisitor() { - override fun visitBinaryExpression(expression: JetBinaryExpression) { - expression.acceptChildren(this) + private fun possiblySmartCastInCondition(condition: JetExpression?): Pair, Set> { + val emptyResult = Pair(setOf(), setOf()) + when (condition) { + is JetBinaryExpression -> { + val operation = condition.getOperationToken() + val left = condition.getLeft() ?: return emptyResult + val right = condition.getRight() ?: return emptyResult - val operation = expression.getOperationToken() - if (operation == JetTokens.EQEQ || operation == JetTokens.EXCLEQ || operation == JetTokens.EQEQEQ || operation == JetTokens.EXCLEQEQEQ) { - result.addIfNotNull(expression.getLeft()?.smartCastExpressionName()) - result.addIfNotNull(expression.getRight()?.smartCastExpressionName()) + fun smartCastInEq(): Pair, Set> { + if (left.isNullLiteral()) { + return Pair(setOf(), right.smartCastExpressionName().singletonOrEmptySet()) + } + else if (right.isNullLiteral()) { + return Pair(setOf(), left.smartCastExpressionName().singletonOrEmptySet()) + } + else { + val leftName = left.smartCastExpressionName() + val rightName = right.smartCastExpressionName() + val names = listOf(leftName, rightName).filterNotNull().toSet() + return Pair(names, setOf()) + } + } + + when (operation) { + JetTokens.EQEQ, JetTokens.EQEQEQ -> return smartCastInEq() + + JetTokens.EXCLEQ, JetTokens.EXCLEQEQEQ -> return smartCastInEq().swap() + + JetTokens.ANDAND -> { + val casts1 = possiblySmartCastInCondition(left) + val casts2 = possiblySmartCastInCondition(right) + return Pair(casts1.first.union(casts2.first), casts1.second.intersect(casts2.second)) + } + + JetTokens.OROR -> { + val casts1 = possiblySmartCastInCondition(left) + val casts2 = possiblySmartCastInCondition(right) + return Pair(casts1.first.intersect(casts2.first), casts1.second.union(casts2.second)) + } } } - override fun visitIsExpression(expression: JetIsExpression) { - expression.acceptChildren(this) - - result.addIfNotNull(expression.getLeftHandSide().smartCastExpressionName()) + is JetIsExpression -> { + val cast = condition.getLeftHandSide().smartCastExpressionName().singletonOrEmptySet() + return if (condition.isNegated()) Pair(setOf(), cast) else Pair(cast, setOf()) } - }) - return result + } + + return emptyResult } /** @@ -484,11 +523,13 @@ class PartialBodyResolveFilter( return result } - // private fun JetExpression?.isNullLiteral() = this?.getNode()?.getElementType() == JetNodeTypes.NULL + private fun JetExpression?.isNullLiteral() = this?.getNode()?.getElementType() == JetNodeTypes.NULL private fun JetExpression?.isTrueConstant() = this != null && getNode()?.getElementType() == JetNodeTypes.BOOLEAN_CONSTANT && getText() == "true" + private fun T?.singletonOrEmptySet(): Set = if (this != null) setOf(this) else setOf() + //TODO: review logic private fun isValueNeeded(expression: JetExpression): Boolean { val parent = expression.getParent() @@ -571,6 +612,5 @@ class PartialBodyResolveFilter( .first { statementMark(it) >= minLevel } } } - } diff --git a/idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.dump b/idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.dump new file mode 100644 index 00000000000..5a1c857d0f6 --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.dump @@ -0,0 +1,6 @@ +Resolve target: value-parameter val p: kotlin.Any? +---------------------------------------------- +fun foo(p: Any?) { + /* STATEMENT DELETED: if (x(p == null)) { print("returned true") return } */ + p?.hashCode() +} diff --git a/idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.kt b/idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.kt new file mode 100644 index 00000000000..b9a48125a01 --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.kt @@ -0,0 +1,7 @@ +fun foo(p: Any?) { + if (x(p == null)) { + print("returned true") + return + } + p?.hashCode() +} \ No newline at end of file diff --git a/idea/testData/resolve/partialBodyResolve/IfIsReturn.dump b/idea/testData/resolve/partialBodyResolve/IfIsReturn.dump new file mode 100644 index 00000000000..994ea86108d --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/IfIsReturn.dump @@ -0,0 +1,6 @@ +Resolve target: value-parameter val p: kotlin.Any? +---------------------------------------------- +fun foo(p: Any?) { + /* STATEMENT DELETED: if (p is String) return */ + println(p.hashCode()) +} diff --git a/idea/testData/resolve/partialBodyResolve/IfIsReturn.kt b/idea/testData/resolve/partialBodyResolve/IfIsReturn.kt new file mode 100644 index 00000000000..8e942da40f2 --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/IfIsReturn.kt @@ -0,0 +1,4 @@ +fun foo(p: Any?) { + if (p is String) return + println(p.hashCode()) +} \ No newline at end of file diff --git a/idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.dump b/idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.dump new file mode 100644 index 00000000000..98a1893de2a --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.dump @@ -0,0 +1,6 @@ +Resolve target: value-parameter val p1: kotlin.Any? +---------------------------------------------- +fun foo(p1: Any?, p2: Any?) { + /* STATEMENT DELETED: if (p1 == null && p2 == null) { print("2 null's") return } */ + p1?.hashCode() +} diff --git a/idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.kt b/idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.kt new file mode 100644 index 00000000000..164243ce59d --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.kt @@ -0,0 +1,7 @@ +fun foo(p1: Any?, p2: Any?) { + if (p1 == null && p2 == null) { + print("2 null's") + return + } + p1?.hashCode() +} \ No newline at end of file diff --git a/idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.dump b/idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.dump new file mode 100644 index 00000000000..e30433d5836 --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.dump @@ -0,0 +1,9 @@ +Resolve target: value-parameter val p1: kotlin.Any? smart-cast to kotlin.Any +---------------------------------------------- +fun foo(p1: Any?, p2: Any?) { + if (p1 == null || p2 == null) { + /* STATEMENT DELETED: print("null") */ + return + } + p1.hashCode() +} diff --git a/idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.kt b/idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.kt new file mode 100644 index 00000000000..6a68de126cd --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.kt @@ -0,0 +1,7 @@ +fun foo(p1: Any?, p2: Any?) { + if (p1 == null || p2 == null) { + print("null") + return + } + p1.hashCode() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/resolve/PartialBodyResolveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/resolve/PartialBodyResolveTestGenerated.java index b46287dab60..c2f05aed2cf 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/resolve/PartialBodyResolveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/resolve/PartialBodyResolveTestGenerated.java @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.idea.resolve; import com.intellij.testFramework.TestDataPath; -import org.jetbrains.kotlin.test.InnerTestClasses; import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; import org.jetbrains.kotlin.test.JetTestUtils; import org.jetbrains.kotlin.test.TestMetadata; @@ -78,6 +77,12 @@ public class PartialBodyResolveTestGenerated extends AbstractPartialBodyResolveT doTest(fileName); } + @TestMetadata("ElvisReturn.kt") + public void testElvisReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/ElvisReturn.kt"); + doTest(fileName); + } + @TestMetadata("ExpressionBodyExplicitType.kt") public void testExpressionBodyExplicitType() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/ExpressionBodyExplicitType.kt"); @@ -114,12 +119,24 @@ public class PartialBodyResolveTestGenerated extends AbstractPartialBodyResolveT doTest(fileName); } + @TestMetadata("IfCallWithConditionReturn.kt") + public void testIfCallWithConditionReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/IfCallWithConditionReturn.kt"); + doTest(fileName); + } + @TestMetadata("IfEqAutoCast.kt") public void testIfEqAutoCast() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/IfEqAutoCast.kt"); doTest(fileName); } + @TestMetadata("IfIsReturn.kt") + public void testIfIsReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/IfIsReturn.kt"); + doTest(fileName); + } + @TestMetadata("IfNotIsError.kt") public void testIfNotIsError() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/IfNotIsError.kt"); @@ -180,6 +197,12 @@ public class PartialBodyResolveTestGenerated extends AbstractPartialBodyResolveT doTest(fileName); } + @TestMetadata("IfNullAndNullReturn.kt") + public void testIfNullAndNullReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/IfNullAndNullReturn.kt"); + doTest(fileName); + } + @TestMetadata("IfNullBreak.kt") public void testIfNullBreak() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/IfNullBreak.kt"); @@ -222,6 +245,12 @@ public class PartialBodyResolveTestGenerated extends AbstractPartialBodyResolveT doTest(fileName); } + @TestMetadata("IfNullOrNullReturn.kt") + public void testIfNullOrNullReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/IfNullOrNullReturn.kt"); + doTest(fileName); + } + @TestMetadata("IfNullPrint.kt") public void testIfNullPrint() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/IfNullPrint.kt");