diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/caches/FirCacheWithInvalidation.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/caches/FirCacheWithInvalidation.kt new file mode 100644 index 00000000000..0324857b8a1 --- /dev/null +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/caches/FirCacheWithInvalidation.kt @@ -0,0 +1,48 @@ +/* + * 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.low.level.api.fir.caches + +import org.jetbrains.kotlin.fir.caches.FirCache + +internal interface FirCacheWithInvalidation { + /** + * Drops the incorrect value from the cache and add a new value instead. + */ + fun fixInconsistentValue( + key: K, + context: CONTEXT & Any, + inconsistencyMessage: String, + mapping: (oldValue: V, newValue: V & Any) -> V & Any, + ): V & Any +} + +/** + * Return cached value or created a new one from [context]. + * This method assumes that we can't return null for not-null context. + * Logs inconsistency error if it is present. + * + * @return not-null [VALUE] in case of [FirCacheWithInvalidation] cache. + */ +internal fun FirCache.getNotNullValueForNotNullContext( + key: KEY, + context: CONTEXT, +): VALUE { + val value = getValue(key, context) + @Suppress("CANNOT_CHECK_FOR_ERASED") + return if (value != null || + context == null || + this !is FirCacheWithInvalidation + ) { + value + } else { + fixInconsistentValue( + key = key, + context = context, + inconsistencyMessage = "Inconsistency in the cache. Someone without context put a null value in the cache", + mapping = { old, new -> old ?: new }, + ) + } +} diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/caches/FirThreadSafeCache.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/caches/FirThreadSafeCache.kt index c2d7c927922..b77dead02e1 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/caches/FirThreadSafeCache.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/caches/FirThreadSafeCache.kt @@ -1,23 +1,76 @@ /* - * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.low.level.api.fir.fir.caches +import com.intellij.openapi.diagnostic.Logger +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.analysis.low.level.api.fir.caches.FirCacheWithInvalidation +import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.caches.FirCache +import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry +import org.jetbrains.kotlin.fir.utils.exceptions.withFirSymbolEntry +import org.jetbrains.kotlin.utils.exceptions.ExceptionAttachmentBuilder +import org.jetbrains.kotlin.utils.exceptions.checkWithAttachment +import org.jetbrains.kotlin.utils.exceptions.logErrorWithAttachment +import org.jetbrains.kotlin.utils.exceptions.withPsiEntry import java.util.concurrent.ConcurrentHashMap internal class FirThreadSafeCache( private val map: ConcurrentHashMap = ConcurrentHashMap(), - private val createValue: (K, CONTEXT) -> V -) : FirCache() { + private val createValue: (K, CONTEXT) -> V, +) : FirCache(), FirCacheWithInvalidation { + override fun getValue(key: K, context: CONTEXT): V = map.getOrPutWithNullableValue(key) { + createValue(it, context) + } - override fun getValue(key: K, context: CONTEXT): V = - map.getOrPutWithNullableValue(key) { - createValue(it, context) + override fun getValueIfComputed(key: K): V? = map[key]?.nullValueToNull() + + override fun fixInconsistentValue( + key: K, + context: CONTEXT & Any, + inconsistencyMessage: String, + mapping: (oldValue: V, newValue: V & Any) -> V & Any, + ): V & Any { + val newValue = createValue(key, context) + checkWithAttachment( + newValue != null, + message = { "A value for requested key & context must not be null due to the contract" }, + ) { + buildAttachments(key, context, newValue) } - override fun getValueIfComputed(key: K): V? = - map[key]?.nullValueToNull() + LOG.logErrorWithAttachment(inconsistencyMessage) { + buildAttachments(key, context, newValue) + } + + val result = map.merge(key, newValue) { old, _ -> + mapping(old.nullValueToNull(), newValue) + } + + @Suppress("UNCHECKED_CAST") + return result as (V & Any) + } + + private fun ExceptionAttachmentBuilder.buildAttachments(key: K, context: CONTEXT, value: V) { + withEntry("key", key.toString()) + + if (context is PsiElement) { + withPsiEntry("context", context) + } else { + withEntry("context", context.toString()) + } + + val unwrapped = (value as? Collection<*>)?.singleOrNull() ?: value + when (unwrapped) { + is FirElement -> withFirEntry("value", unwrapped) + is FirBasedSymbol<*> -> withFirSymbolEntry("value", unwrapped) + else -> withEntry("value", unwrapped.toString()) + } + } } + +private val LOG = Logger.getInstance(FirThreadSafeCache::class.java) \ No newline at end of file diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProviderHelper.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProviderHelper.kt index 3bfb7f7a188..c3abef0d20d 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProviderHelper.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirProviderHelper.kt @@ -1,11 +1,12 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.low.level.api.fir.providers import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analysis.low.level.api.fir.caches.getNotNullValueForNotNullContext import org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder.LLFirFileBuilder import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.CompositeKotlinPackageProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.extensions.LLFirResolveExtensionTool @@ -115,7 +116,7 @@ internal class LLFirProviderHelper( ): FirClassLikeDeclaration? { if (classId.isLocal) return null if (!allowKotlinPackage && classId.isKotlinPackage()) return null - return classifierByClassId.getValue(classId, classLikeDeclaration) + return classifierByClassId.getNotNullValueForNotNullContext(classId, classLikeDeclaration) } fun getTopLevelClassNamesInPackage(packageFqName: FqName): Set { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/stubBased/deserialization/StubBasedFirDeserializedSymbolProvider.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/stubBased/deserialization/StubBasedFirDeserializedSymbolProvider.kt index 7d4a12b92e2..5972c5a701f 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/stubBased/deserialization/StubBasedFirDeserializedSymbolProvider.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/stubBased/deserialization/StubBasedFirDeserializedSymbolProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -7,18 +7,16 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.stubBased.deserializatio import com.intellij.openapi.project.Project import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analysis.low.level.api.fir.caches.getNotNullValueForNotNullContext import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirKotlinSymbolProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.util.LLFirKotlinSymbolNamesProvider -import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider import org.jetbrains.kotlin.analysis.providers.createDeclarationProvider import org.jetbrains.kotlin.analysis.providers.createPackageProvider import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.caches.FirCache import org.jetbrains.kotlin.fir.caches.firCachesFactory -import org.jetbrains.kotlin.fir.caches.getValue import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.deserialization.SingleModuleDataProvider -import org.jetbrains.kotlin.fir.java.deserialization.JvmClassFileBasedSymbolProvider import org.jetbrains.kotlin.fir.java.deserialization.KotlinBuiltins import org.jetbrains.kotlin.fir.realPsi import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolNamesProvider @@ -29,9 +27,8 @@ import org.jetbrains.kotlin.name.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.stubs.impl.* import org.jetbrains.kotlin.resolve.jvm.JvmClassName -import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty -import java.util.ArrayList +import org.jetbrains.kotlin.fir.caches.getValue typealias DeserializedTypeAliasPostProcessor = (FirTypeAliasSymbol) -> Unit @@ -197,12 +194,12 @@ internal open class StubBasedFirDeserializedSymbolProvider( } } - private fun getClass( - classId: ClassId, - parentContext: StubBasedFirDeserializationContext? = null - ): FirRegularClassSymbol? { - return classCache.getValue(classId, parentContext) - } + private fun getClass(classId: ClassId, parentContext: StubBasedFirDeserializationContext? = null): FirRegularClassSymbol? = + if (parentContext?.classLikeDeclaration != null) { + classCache.getNotNullValueForNotNullContext(classId, parentContext) + } else { + classCache.getValue(classId, parentContext) + } private fun getTypeAlias(classId: ClassId, context: StubBasedFirDeserializationContext? = null): FirTypeAliasSymbol? { if (!classId.relativeClassName.isOneSegmentFQN()) return null @@ -294,15 +291,11 @@ internal open class StubBasedFirDeserializedSymbolProvider( outerClassSymbol = null, outerTypeParameters = emptyList(), initialOrigin, - classLikeDeclaration + classLikeDeclaration, ) - if (classLikeDeclaration is KtClassOrObject) { - return classCache.getValue( - classId, - deserializationContext - ) - } - return typeAliasCache.getValue(classId, deserializationContext) + + val cache = if (classLikeDeclaration is KtClassOrObject) classCache else typeAliasCache + return cache.getNotNullValueForNotNullContext(classId, deserializationContext) } fun getTopLevelCallableSymbol(