Partial body resolve filter works more precisely for if-statements
This commit is contained in:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user