Partial body resolve: rewritten algorithm of filter building to ensure correct resolve of smart cast points (+ prepared to support more strict mode for resolve only)

This commit is contained in:
Valentin Kipyatkov
2014-11-21 01:50:45 +03:00
parent f8f07dc824
commit 2f19713cc2
10 changed files with 315 additions and 63 deletions
@@ -37,7 +37,7 @@ class PartialBodyResolveFilter(
probablyNothingCallableNamesService: ProbablyNothingCallableNamesService
) : (JetElement) -> Boolean {
private val statementsToResolve = HashSet<JetExpression>()
private val statementTree = StatementTree()
private val nothingFunctionNames = HashSet(probablyNothingCallableNamesService.functionNames())
private val nothingPropertyNames = probablyNothingCallableNamesService.propertyNames()
@@ -59,76 +59,95 @@ class PartialBodyResolveFilter(
}
})
addStatementsToResolve(elementToResolve)
statementTree.mark(elementToResolve, MarkLevel.NEED_COMPLETION)
statementTree.blocks(body).forEach {
processBlock(it)
}
}
override fun invoke(statement: JetElement) = statement in statementsToResolve
//TODO: do..while is special case
tailRecursive
private fun addStatementsToResolve(element: JetElement) {
if (element == body) return
val parent = element.getParent() as? JetElement ?: return
private fun processBlock(block: JetBlockExpression): NameFilter {
val nameFilter = NameFilter()
if (!statementTree.hasMarks(block, MarkLevel.NEED_REFERENCE_RESOLVE)) return nameFilter
if (parent is JetBlockExpression) {
if (element in statementsToResolve) return // already processed
val startStatement = block.getLastChild().siblings(forward = false)
.filterIsInstance<JetExpression>()
.first { statementTree.statementMark(it) > MarkLevel.RESOLVE_STATEMENT }
if (element is JetExpression) {
addStatementToResolve(element)
for (statement in startStatement.siblings(forward = false)) {
if (statement !is JetExpression) continue
if (statement is JetNamedDeclaration) {
val name = statement.getName()
if (name != null && nameFilter.accepts(name)) {
statementTree.mark(statement, MarkLevel.NEED_REFERENCE_RESOLVE)
}
}
else if (statement is JetMultiDeclaration) {
if (statement.getEntries().any {
val name = it.getName()
name != null && nameFilter.accepts(name)
}) {
statementTree.mark(statement, MarkLevel.NEED_REFERENCE_RESOLVE)
}
}
for (statement in element.siblings(forward = false, withItself = false)) {
if (statement !is JetExpression) continue
if (statement is JetClassBody) continue
val smartCastPlaces = potentialSmartCastPlaces(statement)
if (!smartCastPlaces.isEmpty()) {
addStatementToResolve(statement)
//TODO: do we really need correct resolve for ALL smart cast places?
addStatementsToResolveForPlaces(smartCastPlaces.values().flatMap { it })
fun updateNameFilter() {
val level = statementTree.statementMark(statement)
when (level) {
MarkLevel.NEED_REFERENCE_RESOLVE -> nameFilter.addUsedNames(statement)
MarkLevel.NEED_COMPLETION -> nameFilter.addAllNames()
}
else if (statement is JetDeclaration) {
addStatementToResolve(statement)
}
updateNameFilter()
if (!nameFilter.isEmpty) {
val smartCastPlaces = potentialSmartCastPlaces(statement, { it.identifiers().all { nameFilter.accepts(it) } })
if (!smartCastPlaces.isEmpty()) {
//TODO: do we really need correct resolve for ALL smart cast places?
smartCastPlaces.values()
.flatMap { it }
.forEach { statementTree.mark(it, MarkLevel.NEED_REFERENCE_RESOLVE) }
updateNameFilter()
}
}
val level = statementTree.statementMark(statement)
if (level > MarkLevel.RESOLVE_STATEMENT) {
for (nestedBlock in statementTree.blocks(statement)) {
if (isValueNeeded(nestedBlock)) {
nestedBlock.lastStatement()?.let { statementTree.mark(it, MarkLevel.NEED_REFERENCE_RESOLVE) }
}
val childFilter = processBlock(nestedBlock)
nameFilter.addNames(childFilter)
}
}
}
addStatementsToResolve(parent)
return nameFilter
}
private fun addStatementToResolve(statement: JetExpression): Boolean {
assert(statement.getParent() is JetBlockExpression)
if (!statementsToResolve.add(statement)) return false
// search for code blocks inside statement and make sure its values will be resolved
statement.accept(object : JetVisitorVoid(){
override fun visitBlockExpression(expression: JetBlockExpression) {
if (isValueNeeded(expression)) {
val value = expression.lastStatement()
if (value != null) {
addStatementsToResolve(value)
}
}
}
override fun visitJetElement(element: JetElement) {
element.acceptChildren(this)
}
})
return true
}
override fun invoke(statement: JetElement)
= statement is JetExpression && statementTree.statementMark(statement) != MarkLevel.NONE
/**
* Finds places within the given statement that may affect smart-casts after it.
* That is, such places whose containing statements must be left in code to keep the smart-casts.
* Returns map from smart-cast expression names (variable name or qualified variable name) to places.
*/
private fun potentialSmartCastPlaces(statement: JetExpression, filter: (String) -> Boolean = { true }): Map<String, List<JetExpression>> {
val map = HashMap<String, ArrayList<JetExpression>>(0)
private fun potentialSmartCastPlaces(
statement: JetExpression,
filter: (SmartCastName) -> Boolean = { true }
): Map<SmartCastName, List<JetExpression>> {
val map = HashMap<SmartCastName, ArrayList<JetExpression>>(0)
fun addPlace(name: String, place: JetExpression) {
fun addPlace(name: SmartCastName, place: JetExpression) {
map.getOrPut(name, { ArrayList(1) }).add(place)
}
fun addPlaces(name: String, places: Collection<JetExpression>) {
fun addPlaces(name: SmartCastName, places: Collection<JetExpression>) {
assert(!places.isEmpty())
map.getOrPut(name, { ArrayList(places.size) }).addAll(places)
}
@@ -219,8 +238,8 @@ class PartialBodyResolveFilter(
* Returns names of expressions that would possibly be smart cast after
* either a statement "if (condition) return" or "if (!condition) return"
*/
private fun collectPossiblySmartCastInCondition(condition: JetExpression?): Set<String> {
val result = HashSet<String>()
private fun collectPossiblySmartCastInCondition(condition: JetExpression?): Set<SmartCastName> {
val result = HashSet<SmartCastName>()
condition?.accept(object : ControlFlowVisitor() {
override fun visitBinaryExpression(expression: JetBinaryExpression) {
expression.acceptChildren(this)
@@ -352,28 +371,33 @@ class PartialBodyResolveFilter(
private fun JetElement.noControlFlowInside() = this is JetFunction || this is JetClass || this is JetClassBody
}
private fun addStatementsToResolveForPlaces(places: Collection<JetExpression>) {
for (place in places) {
var parent: PsiElement = place
while (true) {
if (parent.isStatement() && !addStatementToResolve(parent as JetExpression)) break
parent = parent.getParent()
}
private fun PsiElement.isStatement() = this is JetExpression && getParent() is JetBlockExpression
private data class SmartCastName(val receiverName: SmartCastName?, val selectorName: String) {
override fun toString(): String = if (receiverName != null) receiverName.toString() + "." + selectorName else selectorName
fun identifiers(): Collection<String> {
return if (receiverName != null)
receiverName.identifiers() + listOf(selectorName)
else
listOf(selectorName)
}
}
private fun PsiElement.isStatement() = this is JetExpression && getParent() is JetBlockExpression
private fun JetExpression.smartCastExpressionName(): String? {
//TODO: this can be smart-cast too!
private fun JetExpression.smartCastExpressionName(): SmartCastName? {
return when (this) {
is JetSimpleNameExpression -> this.getReferencedName()
is JetSimpleNameExpression -> SmartCastName(null, this.getReferencedName())
is JetQualifiedExpression -> {
val selectorName = getSelectorExpression().smartCastExpressionName() ?: return null
val selector = getSelectorExpression() as? JetSimpleNameExpression ?: return null
val selectorName = selector.getReferencedName()
val receiver = getReceiverExpression()
if (receiver is JetThisExpression) return selectorName
if (receiver is JetThisExpression) {
return SmartCastName(null, selectorName)
}
val receiverName = receiver.smartCastExpressionName() ?: return null
return selectorName + "." + receiverName
return SmartCastName(receiverName, selectorName)
}
else -> null
@@ -409,5 +433,110 @@ class PartialBodyResolveFilter(
private fun JetBlockExpression.lastStatement(): JetExpression?
= getLastChild()?.siblings(forward = false)?.firstOrNullIsInstance<JetExpression>()
//TODO: declarations with special names (e.g. "get")
private class NameFilter {
private var names: MutableSet<String>? = HashSet()
fun accepts(name: String) = names == null || name in names!!
val isEmpty: Boolean
get() = names?.isEmpty() ?: false
private val addUsedNamesVisitor = object : JetVisitorVoid(){
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
names!!.add(expression.getReferencedName())
}
override fun visitJetElement(element: JetElement) {
element.acceptChildren(this)
}
override fun visitBlockExpression(expression: JetBlockExpression) {
}
}
fun addUsedNames(statement: JetExpression) {
if (names != null) {
statement.accept(addUsedNamesVisitor)
}
}
fun addNames(filter: NameFilter) {
if (names == null) return
if (filter.names == null) {
names = null
}
else {
names!!.addAll(filter.names!!)
}
}
fun addAllNames() {
names = null
}
}
private enum class MarkLevel {
NONE
RESOLVE_STATEMENT
NEED_REFERENCE_RESOLVE
NEED_COMPLETION
}
private inner class StatementTree {
private val statementMarks = HashMap<JetExpression, MarkLevel>()
private val blockLevels = HashMap<JetBlockExpression, MarkLevel>()
val topLevelBlocks: Collection<JetBlockExpression> = blocks(body)
fun blocks(statement: JetExpression): Collection<JetBlockExpression>
= blocks(statement : JetElement)
private fun blocks(element: JetElement): Collection<JetBlockExpression> {
val result = ArrayList<JetBlockExpression>(1)
element.accept(object : JetVisitorVoid() {
override fun visitBlockExpression(expression: JetBlockExpression) {
result.add(expression)
}
override fun visitElement(element: PsiElement) {
element.acceptChildren(this)
}
})
return result
}
fun mark(element: PsiElement, level: MarkLevel) {
var e = element
while (e != body) {
if (e.isStatement()) {
markStatement(e as JetExpression, level)
}
e = e.getParent()!!
}
}
private fun markStatement(statement: JetExpression, level: MarkLevel) {
val currentLevel = statementMark(statement)
if (currentLevel < level) {
statementMarks[statement] = level
val block = statement.getParent() as JetBlockExpression
val currentBlockLevel = blockLevels[block] ?: MarkLevel.NONE
if (currentBlockLevel < level) {
blockLevels[block] = level
}
}
}
fun statementMark(statement: JetExpression): MarkLevel
= statementMarks[statement] ?: MarkLevel.NONE
fun hasMarks(block: JetBlockExpression, minLevel: MarkLevel): Boolean {
val level = blockLevels[block] ?: return minLevel == MarkLevel.NONE
return level >= minLevel
}
}
}
@@ -0,0 +1,16 @@
Resolve target: value-parameter val b: kotlin.String?
----------------------------------------------
data class TestData(val first: Int, val second: String?)
fun f() : TestData {}
fun test(b: String?, p: Int) {
if (p > 0) {
val (a, b) = f()
print(b!!.size)
}
else {
print(b!!.size)
}
<caret>b
}
@@ -0,0 +1,14 @@
data class TestData(val first: Int, val second: String?)
fun f() : TestData {}
fun test(b: String?, p: Int) {
if (p > 0) {
val (a, b) = f()
print(b!!.size)
}
else {
print(b!!.size)
}
<caret>b
}
@@ -0,0 +1,21 @@
Resolve target: value-parameter val s: kotlin.String?
----------------------------------------------
fun foo(s: String?, b1: Boolean, b2: Boolean) {
x(
if (b1) {
print(s!!.length)
1
} else {
if (b2) {
print(s!!.length + 1)
}
else {
print(s!!.length - 1)
}
2
},
<caret>s
)
}
fun x(p1: Int, p2: String) { }
@@ -0,0 +1,19 @@
fun foo(s: String?, b1: Boolean, b2: Boolean) {
x(
if (b1) {
print(s!!.length)
1
} else {
if (b2) {
print(s!!.length + 1)
}
else {
print(s!!.length - 1)
}
2
},
<caret>s
)
}
fun x(p1: Int, p2: String) { }
@@ -0,0 +1,17 @@
Resolve target: value-parameter val x: kotlin.Any?
----------------------------------------------
fun foo(x: Any?, p: Int) {
if (p > 0) {
val x = f()
print(x!!)
}
else {
val x = g()
print(x!!)
}
<caret>x.hashCode()
}
fun f(): Any? = null
fun g(): Any? = null
@@ -0,0 +1,12 @@
Resolve target: value-parameter val x: kotlin.Any?
----------------------------------------------
fun foo(x: Any?) {
if (x == null) {
val error = f()
error("error")
}
<caret>x.hashCode()
}
fun f(): (String) -> Unit = {}
@@ -264,6 +264,12 @@ public class PartialBodyResolveTestGenerated extends AbstractPartialBodyResolveT
doTest(fileName);
}
@TestMetadata("MutliDeclaration.kt")
public void testMutliDeclaration() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/MutliDeclaration.kt");
doTest(fileName);
}
@TestMetadata("ReturnLambda.kt")
public void testReturnLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/ReturnLambda.kt");
@@ -276,6 +282,24 @@ public class PartialBodyResolveTestGenerated extends AbstractPartialBodyResolveT
doTest(fileName);
}
@TestMetadata("SmartCastInTheSameStatement.kt")
public void testSmartCastInTheSameStatement() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/SmartCastInTheSameStatement.kt");
doTest(fileName);
}
@TestMetadata("SmartCastPointsResolveRequired1.kt")
public void testSmartCastPointsResolveRequired1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/SmartCastPointsResolveRequired1.kt");
doTest(fileName);
}
@TestMetadata("SmartCastPointsResolveRequired2.kt")
public void testSmartCastPointsResolveRequired2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/SmartCastPointsResolveRequired2.kt");
doTest(fileName);
}
@TestMetadata("ThisQualifiedAutoCast.kt")
public void testThisQualifiedAutoCast() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/resolve/partialBodyResolve/ThisQualifiedAutoCast.kt");