diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDiagnosticFactory.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDiagnosticFactory.kt index 7a9280bbdc7..315549792bc 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDiagnosticFactory.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDiagnosticFactory.kt @@ -23,10 +23,6 @@ sealed class AbstractFirDiagnosticFactory( override fun toString(): String { return name } - - protected fun checkMyDiagnostic(diagnostic: FirDiagnostic) { - require(diagnostic.factory === this) { "$diagnostic should be of this factory" } - } } class FirDiagnosticFactory0( @@ -83,11 +79,6 @@ class FirDiagnosticFactory1( else -> incorrectElement(element) } } - - fun extractArgument(diagnostic: FirDiagnostic): A { - checkMyDiagnostic(diagnostic) - return (diagnostic as FirDiagnosticWithParameters1).a - } } class FirDiagnosticFactory2( @@ -124,12 +115,6 @@ class FirDiagnosticFactory2( else -> incorrectElement(element) } } - - fun extractArguments(diagnostic: FirDiagnostic): Pair { - checkMyDiagnostic(diagnostic) - val typed = diagnostic as FirDiagnosticWithParameters2 - return Pair(typed.a, typed.b) - } } class FirDiagnosticFactory3( diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt index 3efdda344d2..b15d769d630 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt @@ -833,6 +833,33 @@ object LightTreePositioningStrategies { private fun lastSymbol(range: TextRange): TextRange = if (range.isEmpty) range else TextRange.create(range.endOffset - 1, range.endOffset) } + + val UNREACHABLE_CODE: LightTreePositioningStrategy = object : LightTreePositioningStrategy() { + override fun markFirDiagnostic(element: FirSourceElement, diagnostic: FirDiagnostic): List { + @Suppress("UNCHECKED_CAST") + val typed = diagnostic as FirDiagnosticWithParameters2, Set> + with(UnreachableCodeLightTreeHelper(element.treeStructure)) { + val reachable = typed.a.map { it.lighterASTNode }.toSet() + val unreachable = typed.b.map { it.lighterASTNode }.toSet() + if (!element.lighterASTNode.hasChildrenInSet(reachable)) { + return super.markFirDiagnostic(element, diagnostic) + } + + val nodesToMark = element.lighterASTNode.getLeavesOrReachableChildren(reachable, unreachable) + .removeReachableElementsWithMeaninglessSiblings(reachable) + + if (nodesToMark.isEmpty()) { + return super.markFirDiagnostic(element, diagnostic) + } + + val ranges = nodesToMark.flatMap { + markElement(it, element.startOffset, element.endOffset, element.treeStructure, element.lighterASTNode) + } + + return ranges.mergeAdjacentTextRanges() + } + } + } } fun FirSourceElement.hasValOrVar(): Boolean = @@ -1100,6 +1127,29 @@ fun FlyweightCapableTreeStructure.collectDescendantsOfType( return result } +fun FlyweightCapableTreeStructure.traverseDescendants( + node: LighterASTNode, + acceptor: (LighterASTNode) -> Boolean +) { + fun FlyweightCapableTreeStructure.traverse(node: LighterASTNode) { + val childrenRef = Ref>() + getChildren(node, childrenRef) + + val childrenRefGet = childrenRef.get() + if (childrenRefGet != null) { + for (child in childrenRefGet) { + if (child != null) { + if (acceptor(child)) { + traverse(child) + } + } + } + } + } + + traverse(node) +} + fun FlyweightCapableTreeStructure.findChildByType(node: LighterASTNode, type: TokenSet): LighterASTNode? { val childrenRef = Ref>() getChildren(node, childrenRef) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategy.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategy.kt index fefc38681ec..4b8ab630c0f 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategy.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategy.kt @@ -88,11 +88,12 @@ private val FILLER_TOKENS = setOf( ) private fun LighterASTNode.nonFillerFirstChildOrSelf(tree: FlyweightCapableTreeStructure): LighterASTNode = - getChildren(tree).firstOrNull { it != null && it.tokenType !in FILLER_TOKENS } ?: this + getChildren(tree).firstOrNull { it != null && !it.isFiller() } ?: this internal fun LighterASTNode.nonFillerLastChildOrSelf(tree: FlyweightCapableTreeStructure): LighterASTNode = - getChildren(tree).lastOrNull { it != null && it.tokenType !in FILLER_TOKENS } ?: this + getChildren(tree).lastOrNull { it != null && !it.isFiller() } ?: this +internal fun LighterASTNode.isFiller() = tokenType in FILLER_TOKENS private fun hasSyntaxErrors(node: LighterASTNode, tree: FlyweightCapableTreeStructure): Boolean { if (node.tokenType == TokenType.ERROR_ELEMENT) return true diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt index ddb132f0104..77688447397 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt @@ -274,7 +274,7 @@ object SourceElementPositioningStrategies { ) val UNREACHABLE_CODE = SourceElementPositioningStrategy( - LightTreePositioningStrategies.DEFAULT, //todo + LightTreePositioningStrategies.UNREACHABLE_CODE, FirPsiPositioningStrategies.UNREACHABLE_CODE ) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/UnreachableCodeLightTreeHelper.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/UnreachableCodeLightTreeHelper.kt new file mode 100644 index 00000000000..01e1d0526e9 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/UnreachableCodeLightTreeHelper.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.analysis.diagnostics + +import com.intellij.lang.LighterASTNode +import com.intellij.openapi.util.TextRange +import com.intellij.util.diff.FlyweightCapableTreeStructure +import org.jetbrains.kotlin.fir.analysis.checkers.getChildren +import org.jetbrains.kotlin.lexer.KtTokens + +typealias Node = LighterASTNode + +class UnreachableCodeLightTreeHelper(val tree: FlyweightCapableTreeStructure) { + + fun Node.hasChildrenInSet(set: Set): Boolean { + var result = false + tree.traverseDescendants(this) { + if (!result && it != this && it in set) { + result = true + } + !result + } + return result + } + + fun Node.getLeavesOrReachableChildren(reachable: Set, unreachable: Set): List { + val result = mutableListOf() + tree.traverseDescendants(this) { element -> + val isReachable = element in reachable && !element.hasChildrenInSet(unreachable) + if (isReachable || element.getChildren(tree).isEmpty()) { + result.add(element) + false + } else { + true + } + } + return result + } + + fun List.removeReachableElementsWithMeaninglessSiblings(reachableElements: Set): List { + val childrenToRemove = mutableSetOf() + fun collectSiblingsIfMeaningless(elementIndex: Int, direction: Int) { + val index = elementIndex + direction + if (index !in 0 until size) return + + val element = this[index] + if (element.isFiller() || element.tokenType == KtTokens.COMMA) { + 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 filter { it !in childrenToRemove } + } + + fun List.mergeAdjacentTextRanges(): List { + val result = ArrayList() + val lastRange = fold(null as TextRange?) { currentTextRange, elementRange -> + when { + currentTextRange == null -> { + elementRange + } + currentTextRange.endOffset == elementRange.startOffset -> { + currentTextRange.union(elementRange) + } + else -> { + result.add(currentTextRange) + elementRange + } + } + } + if (lastRange != null) { + result.add(lastRange) + } + return result + } +} \ No newline at end of file