Drop UNREACHABLE_CODE eager text range calculation

#KT-35242 Fixed
This commit is contained in:
Vladimir Dolzhenko
2019-12-04 17:47:21 +01:00
parent 9e4d7df86e
commit 272ca002d7
19 changed files with 321 additions and 126 deletions
@@ -281,7 +281,7 @@ class ControlFlowInformationProvider private constructor(
private fun reportUnreachableCode(unreachableCode: UnreachableCode) {
for (element in unreachableCode.elements) {
trace.report(Errors.UNREACHABLE_CODE.on(element, unreachableCode.getUnreachableTextRanges(element)))
trace.report(Errors.UNREACHABLE_CODE.on(element, unreachableCode.reachableElements, unreachableCode.unreachableElements))
}
}
@@ -29,97 +29,112 @@ import java.util.*
interface UnreachableCode {
val elements: Set<KtElement>
fun getUnreachableTextRanges(element: KtElement): List<TextRange>
val reachableElements: Set<KtElement>
val unreachableElements: Set<KtElement>
companion object {
fun getUnreachableTextRanges(
element: KtElement,
reachableElements: Set<KtElement>,
unreachableElements: Set<KtElement>
): List<TextRange> {
return if (element.hasChildrenInSet(reachableElements)) {
with(
element.getLeavesOrReachableChildren(reachableElements, unreachableElements)
.removeReachableElementsWithMeaninglessSiblings(reachableElements).mergeAdjacentTextRanges()
) {
if (isNotEmpty()) this
// Specific case like condition in when:
// element is dead but its only child is alive and has the same text range
else listOf(element.textRange.endOffset.let { TextRange(it, it) })
}
} else {
listOf(element.textRange!!)
}
}
private fun KtElement.hasChildrenInSet(set: Set<KtElement>): Boolean =
PsiTreeUtil.collectElements(this) { it != this }.any { it in set }
private fun KtElement.getLeavesOrReachableChildren(
reachableElements: Set<KtElement>,
unreachableElements: Set<KtElement>
): List<PsiElement> {
val children = ArrayList<PsiElement>()
acceptChildren(object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
val isReachable =
element is KtElement && reachableElements.contains(element) && !element.hasChildrenInSet(unreachableElements)
if (isReachable || element.children.isEmpty()) {
children.add(element)
} else {
element.acceptChildren(this)
}
}
})
return children
}
private fun List<PsiElement>.removeReachableElementsWithMeaninglessSiblings(reachableElements: Set<KtElement>): List<PsiElement> {
fun PsiElement.isMeaningless() = this is PsiWhiteSpace
|| this.node?.elementType == KtTokens.COMMA
|| this is PsiComment
val childrenToRemove = HashSet<PsiElement>()
fun collectSiblingsIfMeaningless(elementIndex: Int, direction: Int) {
val index = elementIndex + direction
if (index !in 0 until size) return
val element = this[index]
if (element.isMeaningless()) {
childrenToRemove.add(element)
collectSiblingsIfMeaningless(index, direction)
}
}
for ((index, element) in this.withIndex()) {
if (reachableElements.contains(element)) {
childrenToRemove.add(element)
collectSiblingsIfMeaningless(index, -1)
collectSiblingsIfMeaningless(index, 1)
}
}
return this.filter { it !in childrenToRemove }
}
private fun List<PsiElement>.mergeAdjacentTextRanges(): List<TextRange> {
val result = ArrayList<TextRange>()
val lastRange = fold(null as TextRange?) { currentTextRange, element ->
val elementRange = element.textRange!!
when {
currentTextRange == null -> {
elementRange
}
currentTextRange.endOffset == elementRange.startOffset -> {
currentTextRange.union(elementRange)
}
else -> {
result.add(currentTextRange)
elementRange
}
}
}
if (lastRange != null) {
result.add(lastRange)
}
return result
}
}
}
class UnreachableCodeImpl(
private val reachableElements: Set<KtElement>,
private val unreachableElements: Set<KtElement>
override val reachableElements: Set<KtElement>,
override val unreachableElements: Set<KtElement>
) : UnreachableCode {
// This is needed in order to highlight only '1 < 2' and not '1', '<' and '2' as well
override val elements: Set<KtElement> = KtPsiUtil.findRootExpressions(unreachableElements)
override fun getUnreachableTextRanges(element: KtElement): List<TextRange> {
return if (element.hasChildrenInSet(reachableElements)) {
with(element.getLeavesOrReachableChildren().removeReachableElementsWithMeaninglessSiblings().mergeAdjacentTextRanges()) {
if (isNotEmpty()) this
// Specific case like condition in when:
// element is dead but its only child is alive and has the same text range
else listOf(element.textRange.endOffset.let { TextRange(it, it) })
}
} else {
listOf(element.textRange!!)
}
}
private fun KtElement.hasChildrenInSet(set: Set<KtElement>): Boolean =
PsiTreeUtil.collectElements(this) { it != this }.any { it in set }
private fun KtElement.getLeavesOrReachableChildren(): List<PsiElement> {
val children = ArrayList<PsiElement>()
acceptChildren(object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
val isReachable =
element is KtElement && reachableElements.contains(element) && !element.hasChildrenInSet(unreachableElements)
if (isReachable || element.children.isEmpty()) {
children.add(element)
} else {
element.acceptChildren(this)
}
}
})
return children
}
private fun List<PsiElement>.removeReachableElementsWithMeaninglessSiblings(): List<PsiElement> {
fun PsiElement.isMeaningless() = this is PsiWhiteSpace
|| this.node?.elementType == KtTokens.COMMA
|| this is PsiComment
val childrenToRemove = HashSet<PsiElement>()
fun collectSiblingsIfMeaningless(elementIndex: Int, direction: Int) {
val index = elementIndex + direction
if (index !in 0..(size - 1)) return
val element = this[index]
if (element.isMeaningless()) {
childrenToRemove.add(element)
collectSiblingsIfMeaningless(index, direction)
}
}
for ((index, element) in this.withIndex()) {
if (reachableElements.contains(element)) {
childrenToRemove.add(element)
collectSiblingsIfMeaningless(index, -1)
collectSiblingsIfMeaningless(index, 1)
}
}
return this.filter { it !in childrenToRemove }
}
private fun List<PsiElement>.mergeAdjacentTextRanges(): List<TextRange> {
val result = ArrayList<TextRange>()
val lastRange = fold(null as TextRange?) { currentTextRange, element ->
val elementRange = element.textRange!!
when {
currentTextRange == null -> {
elementRange
}
currentTextRange.endOffset == elementRange.startOffset -> {
currentTextRange.union(elementRange)
}
else -> {
result.add(currentTextRange)
elementRange
}
}
}
if (lastRange != null) {
result.add(lastRange)
}
return result
}
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.diagnostics;
import com.google.common.collect.ImmutableSet;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import kotlin.Pair;
@@ -37,6 +36,7 @@ import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.jetbrains.kotlin.diagnostics.PositioningStrategies.*;
import static org.jetbrains.kotlin.diagnostics.Severity.*;
@@ -886,7 +886,7 @@ public interface Errors {
// Control flow / Data flow
DiagnosticFactory1<KtElement, List<TextRange>> UNREACHABLE_CODE = DiagnosticFactory1.create(
DiagnosticFactory2<KtElement, Set<KtElement>, Set<KtElement>> UNREACHABLE_CODE = DiagnosticFactory2.create(
WARNING, PositioningStrategies.UNREACHABLE_CODE);
DiagnosticFactory0<KtVariableDeclaration> VARIABLE_WITH_NO_TYPE_NO_INITIALIZER = DiagnosticFactory0.create(ERROR, DECLARATION_NAME);
@@ -21,6 +21,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.cfg.UnreachableCode
import org.jetbrains.kotlin.diagnostics.Errors.ACTUAL_WITHOUT_EXPECT
import org.jetbrains.kotlin.diagnostics.Errors.NO_ACTUAL_FOR_EXPECT
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
@@ -579,7 +580,8 @@ object PositioningStrategies {
@JvmField
val UNREACHABLE_CODE: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
override fun markDiagnostic(diagnostic: ParametrizedDiagnostic<out PsiElement>): List<TextRange> {
return Errors.UNREACHABLE_CODE.cast(diagnostic).a
val unreachableCode = Errors.UNREACHABLE_CODE.cast(diagnostic)
return UnreachableCode.getUnreachableTextRanges(unreachableCode.psiElement, unreachableCode.a, unreachableCode.b)
}
}
@@ -351,7 +351,7 @@ public class DefaultErrorMessages {
MAP.put(VAL_OR_VAR_ON_CATCH_PARAMETER, "''{0}'' on catch parameter is not allowed", TO_STRING);
MAP.put(VAL_OR_VAR_ON_SECONDARY_CONSTRUCTOR_PARAMETER, "''{0}'' on secondary constructor parameter is not allowed", TO_STRING);
MAP.put(UNREACHABLE_CODE, "Unreachable code", TO_STRING);
MAP.put(UNREACHABLE_CODE, "Unreachable code", EMPTY, EMPTY);
MAP.put(MANY_COMPANION_OBJECTS, "Only one companion object is allowed per class");
@@ -67,6 +67,9 @@ object Renderers {
element.toString()
}
@JvmField
val EMPTY = Renderer<Any> { "" }
@JvmField
val STRING = Renderer<String> { it }