Partial body resolve filter works more precisely for if-statements

This commit is contained in:
Valentin Kipyatkov
2015-03-24 22:24:13 +03:00
parent 7a414336c1
commit 176ba937ba
11 changed files with 150 additions and 27 deletions
@@ -53,4 +53,6 @@ public inline fun <reified T> Array<*>.firstIsInstance(): T {
throw NoSuchElementException("No element of given type found")
}
public fun <T> streamOfLazyValues(vararg elements: () -> T): Stream<T> = elements.stream().map { it() }
public fun <T> streamOfLazyValues(vararg elements: () -> T): Stream<T> = elements.stream().map { it() }
public fun <T1, T2> Pair<T1, T2>.swap(): Pair<T2, T1> = Pair(second, first)
@@ -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<SmartCastName>, 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<SmartCastName> {
val result = HashSet<SmartCastName>()
condition?.accept(object : ControlFlowVisitor() {
override fun visitBinaryExpression(expression: JetBinaryExpression) {
expression.acceptChildren(this)
private fun possiblySmartCastInCondition(condition: JetExpression?): Pair<Set<SmartCastName>, Set<SmartCastName>> {
val emptyResult = Pair(setOf<SmartCastName>(), setOf<SmartCastName>())
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<SmartCastName>, Set<SmartCastName>> {
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 : Any> T?.singletonOrEmptySet(): Set<T> = 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 }
}
}
}
@@ -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 } */
<caret>p?.hashCode()
}
@@ -0,0 +1,7 @@
fun foo(p: Any?) {
if (x(p == null)) {
print("returned true")
return
}
<caret>p?.hashCode()
}
@@ -0,0 +1,6 @@
Resolve target: value-parameter val p: kotlin.Any?
----------------------------------------------
fun foo(p: Any?) {
/* STATEMENT DELETED: if (p is String) return */
println(<caret>p.hashCode())
}
@@ -0,0 +1,4 @@
fun foo(p: Any?) {
if (p is String) return
println(<caret>p.hashCode())
}
@@ -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 } */
<caret>p1?.hashCode()
}
@@ -0,0 +1,7 @@
fun foo(p1: Any?, p2: Any?) {
if (p1 == null && p2 == null) {
print("2 null's")
return
}
<caret>p1?.hashCode()
}
@@ -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
}
<caret>p1.hashCode()
}
@@ -0,0 +1,7 @@
fun foo(p1: Any?, p2: Any?) {
if (p1 == null || p2 == null) {
print("null")
return
}
<caret>p1.hashCode()
}
@@ -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");