All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.diagnostics.AbstractKotlinSuppressCache.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-RC
Show newest version
/*
 * 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, rootElement: Element, suppressionKey: String, severity: Severity) =
        isSuppressed(StringSuppressRequest(element, rootElement, severity, suppressionKey.lowercase()))

    protected open fun isSuppressed(request: SuppressRequest): Boolean {

        val annotated = getClosestAnnotatedAncestorElement(request.element, request.rootElement, false) ?: return false

        return isSuppressedByAnnotated(request.suppressKey, request.severity, annotated, request.rootElement, 0)

    }

    protected abstract fun getClosestAnnotatedAncestorElement(element: Element, rootElement: Element, excludeSelf: 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,
        rootElement: Element,
        debugDepth: Int
    ): Boolean {
        val suppressor = getOrCreateSuppressor(annotated)
        if (suppressor.isSuppressed(suppressionKey, severity)) return true

        val annotatedAbove = getClosestAnnotatedAncestorElement(suppressor.annotatedElement, rootElement, true) ?: return false

        val suppressed = isSuppressedByAnnotated(suppressionKey, severity, annotatedAbove, rootElement, 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)
            }
        }

    // TODO: consider replacing set with list, assuming that the list of suppresses is usually very small
    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 rootElement: Element
        val severity: Severity
        val suppressKey: String
    }

    private class StringSuppressRequest(
        override val element: Element,
        override val rootElement: Element,
        override val severity: Severity,
        override val suppressKey: String
    ) : SuppressRequest
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy