[AA] KT-56617 Optimize deprecation calculation for Java class symbols

- Most Java classes aren't deprecated. To avoid building `firSymbol`
  in such cases, this commit adds a simple heuristic which checks the
  class's annotations for the presence of one of the deprecation
  annotations recognized by the Kotlin compiler.
- Note that annotations are compared via simple names, so there is a
  slight margin of error. However, comparing class IDs is more costly in
  my tests, because getting an annotation's class ID is not as cheap as
  getting its simple name.
This commit is contained in:
Marco Pennekamp
2023-03-01 20:52:40 +01:00
committed by Space Team
parent d5933f28ab
commit f9fb718b37
3 changed files with 39 additions and 20 deletions
@@ -8,23 +8,17 @@ package org.jetbrains.kotlin.analysis.api.fir.components
import org.jetbrains.kotlin.analysis.api.components.KtSymbolInfoProvider import org.jetbrains.kotlin.analysis.api.components.KtSymbolInfoProvider
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.symbols.* import org.jetbrains.kotlin.analysis.api.fir.symbols.*
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirBackingFieldSymbol
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirSymbol
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirSyntheticJavaPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtReceiverParameterSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtReceiverParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.utils.errors.requireIsInstance import org.jetbrains.kotlin.analysis.utils.errors.requireIsInstance
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets
import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.getDeprecationForCallSite
import org.jetbrains.kotlin.fir.declarations.getJvmNameFromAnnotation
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.languageVersionSettings import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.JvmAbi
@@ -41,6 +35,12 @@ internal class KtFirSymbolInfoProvider(
override fun getDeprecation(symbol: KtSymbol): DeprecationInfo? { override fun getDeprecation(symbol: KtSymbol): DeprecationInfo? {
if (symbol is KtFirBackingFieldSymbol || symbol is KtFirPackageSymbol || symbol is KtReceiverParameterSymbol) return null if (symbol is KtFirBackingFieldSymbol || symbol is KtFirPackageSymbol || symbol is KtReceiverParameterSymbol) return null
require(symbol is KtFirSymbol<*>) { "${this::class}" } require(symbol is KtFirSymbol<*>) { "${this::class}" }
// Optimization: Avoid building `firSymbol` of `KtFirPsiJavaClassSymbol` if it definitely isn't deprecated.
if (symbol is KtFirPsiJavaClassSymbol && !symbol.mayHaveDeprecation()) {
return null
}
return when (val firSymbol = symbol.firSymbol) { return when (val firSymbol = symbol.firSymbol) {
is FirPropertySymbol -> { is FirPropertySymbol -> {
firSymbol.getDeprecationForCallSite(apiVersion, AnnotationUseSiteTarget.PROPERTY) firSymbol.getDeprecationForCallSite(apiVersion, AnnotationUseSiteTarget.PROPERTY)
@@ -51,6 +51,15 @@ internal class KtFirSymbolInfoProvider(
} }
} }
private fun KtFirPsiJavaClassSymbol.mayHaveDeprecation(): Boolean {
if (!hasAnnotations) return false
// Check the simple names of the Java annotations. While presence of such an annotation name does not prove deprecation, it is a
// necessary condition for it. Type aliases are not a problem here: Java code cannot access Kotlin type aliases. (Currently,
// deprecation annotation type aliases do not work in Kotlin, either, but this might change in the future.)
return annotationSimpleNames.any { it != null && it in deprecationAnnotationSimpleNames }
}
override fun getDeprecation(symbol: KtSymbol, annotationUseSiteTarget: AnnotationUseSiteTarget?): DeprecationInfo? { override fun getDeprecation(symbol: KtSymbol, annotationUseSiteTarget: AnnotationUseSiteTarget?): DeprecationInfo? {
require(symbol is KtFirSymbol<*>) require(symbol is KtFirSymbol<*>)
return if (annotationUseSiteTarget != null) { return if (annotationUseSiteTarget != null) {
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.analysis.api.fir.annotations.KtFirAnnotationListForD
import org.jetbrains.kotlin.analysis.api.fir.utils.cached import org.jetbrains.kotlin.analysis.api.fir.utils.cached
import org.jetbrains.kotlin.analysis.api.impl.base.annotations.KtEmptyAnnotationsList import org.jetbrains.kotlin.analysis.api.impl.base.annotations.KtEmptyAnnotationsList
import org.jetbrains.kotlin.analysis.api.impl.base.symbols.toKtClassKind 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.lifetime.withValidityAssertion
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
@@ -25,19 +24,18 @@ import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.api.types.KtType import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.firClassByPsiClassProvider
import org.jetbrains.kotlin.analysis.project.structure.getKtModule
import org.jetbrains.kotlin.analysis.utils.classIdIfNonLocal
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibility 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.classKind
import org.jetbrains.kotlin.fir.java.modality import org.jetbrains.kotlin.fir.java.modality
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.load.java.structure.JavaClass import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
/** /**
* Implements [KtNamedClassOrObjectSymbol] for a Java class. The underlying [firSymbol] is built lazily and only when needed. Many simple * Implements [KtNamedClassOrObjectSymbol] for a Java class. The underlying [firSymbol] is built lazily and only when needed. Many simple
@@ -105,7 +103,13 @@ internal class KtFirPsiJavaClassSymbol(
} }
val hasTypeParameters: Boolean val hasTypeParameters: Boolean
get() = withValidityAssertion { javaClass.typeParameters.isNotEmpty() } get() = withValidityAssertion { psi.typeParameters.isNotEmpty() }
val annotationSimpleNames: List<String?>
get() = withValidityAssertion { psi.annotations.map { it.nameReferenceElement?.referenceName } }
val hasAnnotations: Boolean
get() = withValidityAssertion { psi.annotations.isNotEmpty() }
override val isData: Boolean get() = withValidityAssertion { false } override val isData: Boolean get() = withValidityAssertion { false }
override val isInline: Boolean get() = withValidityAssertion { false } override val isInline: Boolean get() = withValidityAssertion { false }
@@ -152,8 +156,8 @@ internal class KtFirPsiJavaClassSymbol(
} }
override val annotationsList: KtAnnotationsList by cached { override val annotationsList: KtAnnotationsList by cached {
if (javaClass.annotations.isEmpty()) KtEmptyAnnotationsList(token) if (hasAnnotations) KtFirAnnotationListForDeclaration.create(firSymbol, analysisSession.useSiteSession, token)
else KtFirAnnotationListForDeclaration.create(firSymbol, analysisSession.useSiteSession, token) else KtEmptyAnnotationsList(token)
} }
context(KtAnalysisSession) context(KtAnalysisSession)
@@ -178,6 +178,12 @@ private fun FirAnnotation.getDeprecationLevel(): DeprecationLevelValue? {
return DeprecationLevelValue.values().find { it.name == targetName } return DeprecationLevelValue.values().find { it.name == targetName }
} }
val deprecationAnnotationSimpleNames: Set<String> = setOf(
StandardClassIds.Annotations.Deprecated.shortClassName.asString(),
StandardClassIds.Annotations.Java.Deprecated.shortClassName.asString(),
StandardClassIds.Annotations.SinceKotlin.shortClassName.asString(),
)
private fun List<FirAnnotation>.extractDeprecationAnnotationInfoPerUseSite( private fun List<FirAnnotation>.extractDeprecationAnnotationInfoPerUseSite(
session: FirSession, fromJava: Boolean session: FirSession, fromJava: Boolean
): DeprecationAnnotationInfoPerUseSiteStorage { ): DeprecationAnnotationInfoPerUseSiteStorage {