diff --git a/compiler/frontend.common/build.gradle.kts b/compiler/frontend.common/build.gradle.kts index 7c08e8187e6..9c98340769e 100644 --- a/compiler/frontend.common/build.gradle.kts +++ b/compiler/frontend.common/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { api(project(":compiler:config")) api(project(":compiler:container")) compileOnly(intellijCoreDep()) { includeJars("intellij-core") } + compileOnly(intellijDep()) { includeJars("guava", rootProject = rootProject) } } sourceSets { diff --git a/compiler/frontend.common/src/org/jetbrains/kotlin/diagnostics/AbstractKotlinSuppressCache.kt b/compiler/frontend.common/src/org/jetbrains/kotlin/diagnostics/AbstractKotlinSuppressCache.kt new file mode 100644 index 00000000000..a92ebc7a5f3 --- /dev/null +++ b/compiler/frontend.common/src/org/jetbrains/kotlin/diagnostics/AbstractKotlinSuppressCache.kt @@ -0,0 +1,138 @@ +/* + * 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.diagnostics + +import com.google.common.collect.ImmutableSet +import com.intellij.util.containers.ContainerUtil + +abstract class AbstractKotlinSuppressCache { + // The cache is weak: we're OK with losing it + protected val suppressors = ContainerUtil.createConcurrentWeakValueMap>() + + fun isSuppressed(element: Element, suppressionKey: String, severity: Severity) = + isSuppressed(StringSuppressRequest(element, severity, suppressionKey.lowercase())) + + protected open fun isSuppressed(request: SuppressRequest): Boolean { + + val annotated = getParentAnnotatedElement(request.element, false) ?: return false + + return isSuppressedByAnnotated(request.suppressKey, request.severity, annotated, 0) + + } + + protected abstract fun getParentAnnotatedElement(element: Element, strict: Boolean): Element? + + /* + The cache is optimized for the case where no warnings are suppressed (most frequent one) + + trait Root { + suppress("X") + trait A { + trait B { + suppress("Y") + trait C { + fun foo() = warning + } + } + } + } + + Nothing is suppressed at foo, so we look above. While looking above we went up to the root (once) and propagated + all the suppressors down, so now we have: + + foo - suppress(Y) from C + C - suppress(Y) from C + B - suppress(X) from A + A - suppress(X) from A + Root - suppress() from Root + + Next time we look up anything under foo, we try the Y-suppressor and then immediately the X-suppressor, then to the empty + suppressor at the root. All the intermediate empty nodes are skipped, because every suppressor remembers its definition point. + + This way we need no more lookups than the number of suppress() annotations from here to the root. + */ + protected open fun isSuppressedByAnnotated( + suppressionKey: String, + severity: Severity, + annotated: Element, + debugDepth: Int + ): Boolean { + val suppressor = getOrCreateSuppressor(annotated) + if (suppressor.isSuppressed(suppressionKey, severity)) return true + + val annotatedAbove = getParentAnnotatedElement(suppressor.annotatedElement, true) ?: return false + + val suppressed = isSuppressedByAnnotated(suppressionKey, severity, annotatedAbove, debugDepth + 1) + val suppressorAbove = suppressors[annotatedAbove] + if (suppressorAbove != null && suppressorAbove.dominates(suppressor)) { + suppressors[annotated] = suppressorAbove + } + + return suppressed + } + + protected fun getOrCreateSuppressor(annotated: Element): Suppressor = + suppressors.getOrPut(annotated) { + val strings = getSuppressingStrings(annotated) + when (strings.size) { + 0 -> EmptySuppressor(annotated) + 1 -> SingularSuppressor(annotated, strings.first()) + else -> MultiSuppressor(annotated, strings) + } + } + + protected abstract fun getSuppressingStrings(annotated: Element): Set + + companion object { + private fun isSuppressedByStrings(key: String, strings: Set, severity: Severity): Boolean = + severity == Severity.WARNING && "warnings" in strings || key.lowercase() in strings + } + + protected abstract class Suppressor(val annotatedElement: Element) { + abstract fun isSuppressed(suppressionKey: String, severity: Severity): Boolean + + // true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x) + abstract fun dominates(other: Suppressor): Boolean + } + + private class EmptySuppressor(annotated: Element) : Suppressor(annotated) { + override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean = false + override fun dominates(other: Suppressor): Boolean = other is EmptySuppressor + } + + private class SingularSuppressor(annotated: Element, private val string: String) : Suppressor(annotated) { + override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean { + return isSuppressedByStrings(suppressionKey, ImmutableSet.of(string), severity) + } + + override fun dominates(other: Suppressor): Boolean { + return other is EmptySuppressor || (other is SingularSuppressor && other.string == string) + } + } + + private class MultiSuppressor(annotated: Element, private val strings: Set) : Suppressor(annotated) { + override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean { + return isSuppressedByStrings(suppressionKey, strings, severity) + } + + override fun dominates(other: Suppressor): Boolean { + // it's too costly to check set inclusion + return other is EmptySuppressor + } + } + + protected interface SuppressRequest { + val element: Element + val severity: Severity + val suppressKey: String + } + + private class StringSuppressRequest( + override val element: Element, + override val severity: Severity, + override val suppressKey: String + ) : SuppressRequest +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/diagnostics/KotlinSuppressCache.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/diagnostics/KotlinSuppressCache.kt index eafe6e8113b..5d13ec61261 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/diagnostics/KotlinSuppressCache.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/diagnostics/KotlinSuppressCache.kt @@ -19,9 +19,9 @@ package org.jetbrains.kotlin.resolve.diagnostics import com.google.common.collect.ImmutableSet import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.psi.PsiElement -import com.intellij.util.containers.ContainerUtil import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.diagnostics.AbstractKotlinSuppressCache import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.psi.KtAnnotated @@ -41,135 +41,6 @@ interface DiagnosticSuppressor { } } -abstract class AbstractKotlinSuppressCache { - // The cache is weak: we're OK with losing it - protected val suppressors = ContainerUtil.createConcurrentWeakValueMap>() - - fun isSuppressed(element: Element, suppressionKey: String, severity: Severity) = - isSuppressed(StringSuppressRequest(element, severity, suppressionKey.lowercase())) - - protected open fun isSuppressed(request: SuppressRequest): Boolean { - - val annotated = getParentAnnotatedElement(request.element, false) ?: return false - - return isSuppressedByAnnotated(request.suppressKey, request.severity, annotated, 0) - - } - - protected abstract fun getParentAnnotatedElement(element: Element, strict: Boolean): Element? - - /* - The cache is optimized for the case where no warnings are suppressed (most frequent one) - - trait Root { - suppress("X") - trait A { - trait B { - suppress("Y") - trait C { - fun foo() = warning - } - } - } - } - - Nothing is suppressed at foo, so we look above. While looking above we went up to the root (once) and propagated - all the suppressors down, so now we have: - - foo - suppress(Y) from C - C - suppress(Y) from C - B - suppress(X) from A - A - suppress(X) from A - Root - suppress() from Root - - Next time we look up anything under foo, we try the Y-suppressor and then immediately the X-suppressor, then to the empty - suppressor at the root. All the intermediate empty nodes are skipped, because every suppressor remembers its definition point. - - This way we need no more lookups than the number of suppress() annotations from here to the root. - */ - protected open fun isSuppressedByAnnotated( - suppressionKey: String, - severity: Severity, - annotated: Element, - debugDepth: Int - ): Boolean { - val suppressor = getOrCreateSuppressor(annotated) - if (suppressor.isSuppressed(suppressionKey, severity)) return true - - val annotatedAbove = getParentAnnotatedElement(suppressor.annotatedElement, true) ?: return false - - val suppressed = isSuppressedByAnnotated(suppressionKey, severity, annotatedAbove, debugDepth + 1) - val suppressorAbove = suppressors[annotatedAbove] - if (suppressorAbove != null && suppressorAbove.dominates(suppressor)) { - suppressors[annotated] = suppressorAbove - } - - return suppressed - } - - protected fun getOrCreateSuppressor(annotated: Element): Suppressor = - suppressors.getOrPut(annotated) { - val strings = getSuppressingStrings(annotated) - when (strings.size) { - 0 -> EmptySuppressor(annotated) - 1 -> SingularSuppressor(annotated, strings.first()) - else -> MultiSuppressor(annotated, strings) - } - } - - protected abstract fun getSuppressingStrings(annotated: Element): Set - - companion object { - private fun isSuppressedByStrings(key: String, strings: Set, severity: Severity): Boolean = - severity == Severity.WARNING && "warnings" in strings || key.lowercase() in strings - } - - protected abstract class Suppressor(val annotatedElement: Element) { - abstract fun isSuppressed(suppressionKey: String, severity: Severity): Boolean - - // true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x) - abstract fun dominates(other: Suppressor): Boolean - } - - private class EmptySuppressor(annotated: Element) : Suppressor(annotated) { - override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean = false - override fun dominates(other: Suppressor): Boolean = other is EmptySuppressor - } - - private class SingularSuppressor(annotated: Element, private val string: String) : Suppressor(annotated) { - override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean { - return isSuppressedByStrings(suppressionKey, ImmutableSet.of(string), severity) - } - - override fun dominates(other: Suppressor): Boolean { - return other is EmptySuppressor || (other is SingularSuppressor && other.string == string) - } - } - - private class MultiSuppressor(annotated: Element, private val strings: Set) : Suppressor(annotated) { - override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean { - return isSuppressedByStrings(suppressionKey, strings, severity) - } - - override fun dominates(other: Suppressor): Boolean { - // it's too costly to check set inclusion - return other is EmptySuppressor - } - } - - protected interface SuppressRequest { - val element: Element - val severity: Severity - val suppressKey: String - } - - private class StringSuppressRequest( - override val element: Element, - override val severity: Severity, - override val suppressKey: String - ) : SuppressRequest -} - abstract class KotlinSuppressCache : AbstractKotlinSuppressCache() { private val diagnosticSuppressors = ExtensionProvider.create(DiagnosticSuppressor.EP_NAME)