[AA] KT-56617 Build FirClassSymbols for Java classes lazily

- Some completion performance tests (e.g. IntelliJ: "empty place typing
  with library cache") spent a considerable amount of time in
  `KtFirSymbolProviderByJavaPsi.getNamedClassSymbol`.
- This commit introduces `KtFirPsiJavaClassSymbol`, which implements
  many `KtNamedClassOrObjectSymbol` properties with the `PsiClass`
  instead of the FIR class. The `FirClassSymbol` is built only when
  necessary. This improves performance when no "slow" properties need to
  be computed for the symbol.

^KT-56617 fixed
This commit is contained in:
Marco Pennekamp
2023-02-27 19:08:20 +01:00
committed by Space Team
parent e70a3a899f
commit 1c46d37663
7 changed files with 188 additions and 28 deletions
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.fir.expressions.FirAnonymousObjectExpression
import org.jetbrains.kotlin.fir.java.JavaScopeProvider
import org.jetbrains.kotlin.fir.java.declarations.FirJavaClass
import org.jetbrains.kotlin.analysis.api.fir.scopes.JavaClassDeclaredMembersEnhancementScope
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirPsiJavaClassSymbol
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.calls.FirSyntheticPropertiesScope
import org.jetbrains.kotlin.fir.resolve.scope
@@ -68,6 +69,7 @@ internal class KtFirScopeProvider(
private inline fun <T> KtSymbolWithMembers.withFirForScope(crossinline body: (FirClass) -> T): T? {
return when (this) {
is KtFirNamedClassOrObjectSymbol -> body(firSymbol.fir)
is KtFirPsiJavaClassSymbol -> body(firSymbol.fir)
is KtFirAnonymousObjectSymbol -> body(firSymbol.fir)
is KtFirEnumEntrySymbol -> {
firSymbol.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
@@ -9,11 +9,10 @@ import com.intellij.psi.*
import com.intellij.psi.impl.compiled.ClsElementImpl
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirPsiJavaClassSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolProviderByJavaPsi
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.firClassByPsiClassProvider
import org.jetbrains.kotlin.analysis.project.structure.getKtModule
import org.jetbrains.kotlin.asJava.KtLightClassMarker
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.name.Name
@@ -28,10 +27,7 @@ internal class KtFirSymbolProviderByJavaPsi(
if (psiClass is KtLightClassMarker) return null
if (psiClass.isKotlinCompiledClass()) return null
val module = psiClass.getKtModule(analysisSession.project)
val provider = firResolveSession.getSessionFor(module).firClassByPsiClassProvider
val firClass = provider.getFirClass(psiClass) ?: return null
return firSymbolBuilder.classifierBuilder.buildNamedClassOrObjectSymbol(firClass)
return KtFirPsiJavaClassSymbol(psiClass, analysisSession)
}
private fun PsiClass.isKotlinCompiledClass() =
@@ -10,9 +10,9 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.base.KtContextReceiver
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.analysis.api.fir.annotations.KtFirAnnotationListForDeclaration
import org.jetbrains.kotlin.analysis.api.fir.findPsi
import org.jetbrains.kotlin.analysis.api.fir.symbols.pointers.KtFirClassLikeSymbolPointer
import org.jetbrains.kotlin.analysis.api.fir.utils.cached
import org.jetbrains.kotlin.analysis.api.impl.base.symbols.toKtClassKind
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
@@ -20,15 +20,11 @@ import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind
import org.jetbrains.kotlin.analysis.api.symbols.pointers.CanNotCreateSymbolPointerForLocalLibraryDeclarationException
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtPsiBasedSymbolPointer
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.api.symbols.pointers.UnsupportedSymbolKind
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.name.ClassId
@@ -93,12 +89,9 @@ internal class KtFirNamedClassOrObjectSymbol(
}
override val typeParameters = withValidityAssertion {
firSymbol.fir.typeParameters.filterIsInstance<FirTypeParameter>().map { typeParameter ->
builder.classifierBuilder.buildTypeParameterSymbol(typeParameter.symbol)
}
firSymbol.createRegularKtTypeParameters(builder)
}
@OptIn(KtAnalysisApiInternals::class)
override val classKind: KtClassKind
get() = withValidityAssertion {
@@ -109,17 +102,7 @@ internal class KtFirNamedClassOrObjectSymbol(
context(KtAnalysisSession)
override fun createPointer(): KtSymbolPointer<KtNamedClassOrObjectSymbol> = withValidityAssertion {
KtPsiBasedSymbolPointer.createForSymbolFromSource<KtNamedClassOrObjectSymbol>(this)?.let { return it }
when (val symbolKind = symbolKind) {
KtSymbolKind.LOCAL ->
throw CanNotCreateSymbolPointerForLocalLibraryDeclarationException(classIdIfNonLocal?.asString() ?: name.asString())
KtSymbolKind.CLASS_MEMBER, KtSymbolKind.TOP_LEVEL ->
KtFirClassLikeSymbolPointer(classIdIfNonLocal!!, KtNamedClassOrObjectSymbol::class)
else -> throw UnsupportedSymbolKind(this::class, symbolKind)
}
createNamedClassOrObjectSymbolPointer()
}
override fun equals(other: Any?): Boolean = symbolEquals(other)
@@ -0,0 +1,126 @@
/*
* Copyright 2010-2023 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.analysis.api.fir.symbols
import com.intellij.psi.PsiClass
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationsList
import org.jetbrains.kotlin.analysis.api.base.KtContextReceiver
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.annotations.KtFirAnnotationListForDeclaration
import org.jetbrains.kotlin.analysis.api.fir.utils.cached
import org.jetbrains.kotlin.analysis.api.impl.base.symbols.toKtClassKind
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin
import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.analysis.project.structure.getKtModule
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.firClassByPsiClassProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.analysis.utils.classIdIfNonLocal
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.java.classKind
import org.jetbrains.kotlin.fir.java.modality
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
/**
* Implements [KtNamedClassOrObjectSymbol] for a Java class. The underlying [firSymbol] is built lazily and only when needed. Many simple
* properties are computed from the given [PsiClass] instead of [firSymbol]. This improves performance when "slow" properties don't need to
* be accessed.
*/
internal class KtFirPsiJavaClassSymbol(
override val psi: PsiClass,
override val analysisSession: KtFirAnalysisSession,
) : KtNamedClassOrObjectSymbol(), KtFirPsiSymbol<PsiClass, FirRegularClassSymbol> {
/**
* [javaClass] is used to defer some properties to the compiler's view of a Java class.
*/
private val javaClass: JavaClass = JavaClassImpl(psi)
override val name: Name = withValidityAssertion { javaClass.name }
override val classIdIfNonLocal: ClassId? by cached { psi.classIdIfNonLocal }
override val origin: KtSymbolOrigin
get() = withValidityAssertion { KtSymbolOrigin.JAVA }
override val symbolKind: KtSymbolKind
get() = withValidityAssertion {
val classId = classIdIfNonLocal
when {
classId == null -> KtSymbolKind.LOCAL
classId.outerClassId != null -> KtSymbolKind.CLASS_MEMBER
else -> KtSymbolKind.TOP_LEVEL
}
}
@OptIn(KtAnalysisApiInternals::class)
override val classKind: KtClassKind
get() = withValidityAssertion { javaClass.classKind.toKtClassKind(isCompanionObject = false) }
override val modality: Modality
get() = withValidityAssertion { javaClass.modality }
override val visibility: Visibility
get() = withValidityAssertion { javaClass.visibility }
override val isInner: Boolean
get() = withValidityAssertion { classIdIfNonLocal?.outerClassId != null && !javaClass.isStatic }
override val isData: Boolean get() = withValidityAssertion { false }
override val isInline: Boolean get() = withValidityAssertion { false }
override val isFun: Boolean get() = withValidityAssertion { false }
override val isExternal: Boolean get() = withValidityAssertion { false }
override val companionObject: KtNamedClassOrObjectSymbol? get() = withValidityAssertion { null }
override val contextReceivers: List<KtContextReceiver> get() = withValidityAssertion { emptyList() }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Slow Operations (requiring access to the underlying FIR class symbol)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
override val firSymbol: FirRegularClassSymbol by cached {
val module = psi.getKtModule(analysisSession.project)
val provider = analysisSession.firResolveSession.getSessionFor(module).firClassByPsiClassProvider
val firClassSymbol = provider.getFirClass(psi)
require(firClassSymbol != null) {
"A FIR class symbol should be available for ${KtFirPsiJavaClassSymbol::class.simpleName} `$classIdIfNonLocal`."
}
firClassSymbol
}
override val superTypes: List<KtType> by cached {
firSymbol.superTypesAndAnnotationsListForRegularClass(builder)
}
override val typeParameters: List<KtTypeParameterSymbol> by cached {
firSymbol.createRegularKtTypeParameters(builder)
}
override val annotationsList: KtAnnotationsList by cached {
KtFirAnnotationListForDeclaration.create(firSymbol, analysisSession.useSiteSession, token)
}
context(KtAnalysisSession)
override fun createPointer(): KtSymbolPointer<KtNamedClassOrObjectSymbol> = withValidityAssertion {
createNamedClassOrObjectSymbolPointer()
}
// TODO (marco): Can we implement equality and hash code without falling back to the symbol?
override fun equals(other: Any?): Boolean = symbolEquals(other)
override fun hashCode(): Int = symbolHashCode()
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2023 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.analysis.api.fir.symbols
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
/**
* A [KtFirSymbol] that is backed by some [PsiElement] and builds [firSymbol] lazily (by convention), allowing some properties to be
* calculated without the need to build a [firSymbol].
*/
internal interface KtFirPsiSymbol<P : PsiElement, out S : FirBasedSymbol<*>> : KtFirSymbol<S> {
override val psi: P
/**
* The origin should be provided without using [firSymbol], if possible.
*/
abstract override val origin: KtSymbolOrigin
}
@@ -8,12 +8,20 @@
package org.jetbrains.kotlin.analysis.api.fir.symbols
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.KtInitializerValue
import org.jetbrains.kotlin.analysis.api.base.KtContextReceiver
import org.jetbrains.kotlin.analysis.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.analysis.api.fir.symbols.pointers.KtFirClassLikeSymbolPointer
import org.jetbrains.kotlin.analysis.api.fir.utils.asKtInitializerValue
import org.jetbrains.kotlin.analysis.api.impl.base.KtContextReceiverImpl
import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind
import org.jetbrains.kotlin.analysis.api.symbols.pointers.CanNotCreateSymbolPointerForLocalLibraryDeclarationException
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtPsiBasedSymbolPointer
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.api.symbols.pointers.UnsupportedSymbolKind
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.utils.printer.getElementTextInContext
@@ -57,6 +65,13 @@ internal fun <D> FirBasedSymbol<D>.createKtTypeParameters(
}
}
internal fun <D> FirBasedSymbol<D>.createRegularKtTypeParameters(
builder: KtSymbolByFirBuilder,
): List<KtFirTypeParameterSymbol> where D : FirTypeParameterRefsOwner, D : FirDeclaration {
return fir.typeParameters.filterIsInstance<FirTypeParameter>().map { typeParameter ->
builder.classifierBuilder.buildTypeParameterSymbol(typeParameter.symbol)
}
}
internal fun FirCallableSymbol<*>.createContextReceivers(
builder: KtSymbolByFirBuilder
@@ -109,3 +124,18 @@ internal fun FirVariableSymbol<*>.getKtConstantInitializer(resolveSession: LLFir
?.classKind == ClassKind.ANNOTATION_CLASS
return firInitializer.asKtInitializerValue(moduleData.session, parentIsAnnotation)
}
context(KtAnalysisSession)
internal fun KtNamedClassOrObjectSymbol.createNamedClassOrObjectSymbolPointer(): KtSymbolPointer<KtNamedClassOrObjectSymbol> {
KtPsiBasedSymbolPointer.createForSymbolFromSource<KtNamedClassOrObjectSymbol>(this)?.let { return it }
return when (val symbolKind = symbolKind) {
KtSymbolKind.LOCAL ->
throw CanNotCreateSymbolPointerForLocalLibraryDeclarationException(classIdIfNonLocal?.asString() ?: name.asString())
KtSymbolKind.CLASS_MEMBER, KtSymbolKind.TOP_LEVEL ->
KtFirClassLikeSymbolPointer(classIdIfNonLocal!!, KtNamedClassOrObjectSymbol::class)
else -> throw UnsupportedSymbolKind(this::class, symbolKind)
}
}
@@ -34,7 +34,7 @@ internal val JavaModifierListOwner.modality: Modality
else -> Modality.OPEN
}
internal val JavaClass.modality: Modality
val JavaClass.modality: Modality
get() = when {
isAnnotationType || isEnum -> Modality.FINAL
isSealed -> Modality.SEALED
@@ -43,7 +43,7 @@ internal val JavaClass.modality: Modality
else -> Modality.OPEN
}
internal val JavaClass.classKind: ClassKind
val JavaClass.classKind: ClassKind
get() = when {
isAnnotationType -> ClassKind.ANNOTATION_CLASS
isInterface -> ClassKind.INTERFACE