[AA] KT-56617 Optimize KtFirVisibilityChecker for Java class symbols

- `KtFirVisibilityChecker` accessed `KtFirPsiJavaClassSymbol.firSymbol`
  a significant number of times during completion, which led to many
  instances of FIR class construction.
- This commit introduces a heuristic to `KtFirVisibilityChecker`
  specifically for `KtFirPsiJavaClassSymbol` to bypass the compiler's
  visibility checker. The fact that we're dealing with Java class
  visibility from Kotlin makes this feasible without reimplementing the
  full logic of `FirVisibilityChecker`.
This commit is contained in:
Marco Pennekamp
2023-03-01 13:36:40 +01:00
committed by Space Team
parent 26e2c48a46
commit 77e374db77
2 changed files with 54 additions and 0 deletions
@@ -9,6 +9,7 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.components.KtVisibilityChecker
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirFileSymbol
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirPsiJavaClassSymbol
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirSymbol
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
@@ -17,11 +18,14 @@ import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDesignation
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirSafe
import org.jetbrains.kotlin.analysis.utils.printer.parentsOfType
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.utils.effectiveVisibility
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.packageFqName
import org.jetbrains.kotlin.fir.resolve.calls.ExpressionReceiverValue
import org.jetbrains.kotlin.fir.resolve.transformers.publishedApiEffectiveVisibility
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
@@ -44,6 +48,10 @@ internal class KtFirVisibilityChecker(
require(candidateSymbol is KtFirSymbol<*>)
require(useSiteFile is KtFirFileSymbol)
if (candidateSymbol is KtFirPsiJavaClassSymbol) {
candidateSymbol.isVisibleByPsi(useSiteFile)?.let { return it }
}
val useSiteFirFile = useSiteFile.firSymbol.fir
val containers = collectContainingDeclarations(position)
@@ -65,6 +73,47 @@ internal class KtFirVisibilityChecker(
)
}
/**
* [isVisibleByPsi] is a heuristic that decides visibility for most [KtFirPsiJavaClassSymbol]s without deferring to its FIR symbol,
* thereby avoiding lazy construction of the FIR class. The visibility rules are tailored specifically for Java classes accessed from
* Kotlin. They cover the most popular visibilities `private`, `public`, and default (package) visibility for top-level and nested
* classes.
*
* Returns `null` if visibility cannot be decided by the heuristic.
*/
private fun KtFirPsiJavaClassSymbol.isVisibleByPsi(useSiteFile: KtFirFileSymbol): Boolean? {
val classId = classIdIfNonLocal
if (classId == null) {
// Local classes are only visible in their containing method. Since we're checking the visibility for a Kotlin file, a local
// class from Java can never be accessible.
return false
}
when (visibility) {
Visibilities.Private ->
// Private classes from Java cannot be accessed from Kotlin.
return false
Visibilities.Public ->
return when (val outerClass = this.outerClass) {
null -> true
else -> outerClass.isVisibleByPsi(useSiteFile)
}
JavaVisibilities.PackageVisibility -> {
val isSamePackage = classId.packageFqName == useSiteFile.firSymbol.fir.packageFqName
if (!isSamePackage) return false
return when (val outerClass = this.outerClass) {
null -> true
else -> outerClass.isVisibleByPsi(useSiteFile)
}
}
}
return null
}
override fun isPublicApi(symbol: KtSymbolWithVisibility): Boolean {
require(symbol is KtFirSymbol<*>)
val declaration = symbol.firSymbol.fir as? FirMemberDeclaration ?: return false
@@ -92,6 +92,11 @@ internal class KtFirPsiJavaClassSymbol(
override val isInner: Boolean
get() = withValidityAssertion { classIdIfNonLocal?.outerClassId != null && !javaClass.isStatic }
val outerClass: KtFirPsiJavaClassSymbol?
get() = withValidityAssertion {
psi.containingClass?.let { KtFirPsiJavaClassSymbol(it, analysisSession) }
}
override val isData: Boolean get() = withValidityAssertion { false }
override val isInline: Boolean get() = withValidityAssertion { false }
override val isFun: Boolean get() = withValidityAssertion { false }