FIR IDE: introduce symbol pointers for restoring symbols in another read action

fix pointer
This commit is contained in:
Ilya Kirillov
2020-06-29 17:20:28 +03:00
parent f88ebed1a3
commit 5cfac8fa3f
20 changed files with 180 additions and 11 deletions
@@ -89,6 +89,7 @@ import org.jetbrains.kotlin.idea.fir.AbstractKtDeclarationAndFirDeclarationEqual
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest
import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractStdlibSymbolsBuildingTest
import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolPointerTest
import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolsByPsiBuildingTest
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyTest
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyWithLibTest
@@ -952,6 +953,10 @@ fun main(args: Array<String>) {
testClass<AbstractStdlibSymbolsBuildingTest> {
model("stdLibSymbols", extension = "txt")
}
testClass<AbstractSymbolPointerTest> {
model("symbolPointer", extension = "kt")
}
}
testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") {
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.idea.frontend.api
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
data class ImplicitReceiverSmartCast(val types: Collection<KtType>, val kind: ImplicitReceiverSmartcastKind)
enum class ImplicitReceiverSmartcastKind {
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.frontend.api
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.*
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.idea.frontend.api
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
sealed class KtTypeArgument
object KtStarProjectionTypeArgument : KtTypeArgument()
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.idea.frontend.api.symbols
import org.jetbrains.kotlin.idea.frontend.api.KtType
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -18,6 +18,8 @@ abstract class KtTypeAliasSymbol : KtClassLikeSymbol() {
abstract class KtClassOrObjectSymbol : KtClassLikeSymbol(), KtSymbolWithTypeParameters, KtSymbolWithModality<KtSymbolModality> {
abstract val classKind: KtClassKind
abstract override fun createPointer(): KtSymbolPointer<KtClassOrObjectSymbol>
}
enum class KtClassKind {
@@ -32,4 +32,6 @@ abstract class KtConstructorSymbol : KtFunctionLikeSymbol() {
abstract override val valueParameters: List<KtConstructorParameterSymbol>
abstract val isPrimary: Boolean
abstract val owner: KtClassOrObjectSymbol
abstract override fun createPointer(): KtSymbolPointer<KtConstructorSymbol>
}
@@ -9,4 +9,6 @@ import org.jetbrains.kotlin.name.FqName
abstract class KtPackageSymbol : KtSymbol {
abstract val fqName: FqName
abstract override fun createPointer(): KtSymbolPointer<KtPackageSymbol>
}
@@ -11,6 +11,8 @@ import org.jetbrains.kotlin.idea.frontend.api.ValidityOwner
interface KtSymbol : ValidityOwner {
val origin: KtSymbolOrigin
val psi: PsiElement?
fun createPointer(): KtSymbolPointer<KtSymbol> = NonRestorableKtSymbolPointer
}
enum class KtSymbolOrigin {
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2020 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.idea.frontend.api.symbols
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
/**
* `KtSymbol` is valid only during read action it was created in
* To pass the symbol from one read action to another the KtSymbolPointer should be used
*
* We can restore the symbol
* * for function & property symbol if its signature was not changed
* * for local variable symbol if code block it was declared in was not changed
* * for class & type alias symbols if its qualified name was not changed
* * for package symbol if the package is still exists
*
* @see org.jetbrains.kotlin.idea.frontend.api.ReadActionConfinementValidityToken
*/
interface KtSymbolPointer<out S : KtSymbol> {
/**
* @return restored symbol (possibly the new symbol instance) if one is still valid, `null` otherwise
*/
fun restoreSymbol(analysisSession: KtAnalysisSession): S?
}
object NonRestorableKtSymbolPointer : KtSymbolPointer<Nothing> {
override fun restoreSymbol(analysisSession: KtAnalysisSession): Nothing? = null
}
inline fun <S : KtSymbol> symbolPointer(crossinline getSymbol: (KtAnalysisSession) -> S?) = object : KtSymbolPointer<S> {
override fun restoreSymbol(analysisSession: KtAnalysisSession): S? = getSymbol(analysisSession)
}
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.idea.frontend.api.symbols
import org.jetbrains.kotlin.idea.frontend.api.KtType
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.name.Name
interface KtNamedSymbol : KtSymbol {
@@ -3,8 +3,10 @@
* 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.idea.frontend.api
package org.jetbrains.kotlin.idea.frontend.api.types
import org.jetbrains.kotlin.idea.frontend.api.KtTypeArgument
import org.jetbrains.kotlin.idea.frontend.api.ValidityOwner
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassLikeSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.name.ClassId
@@ -31,4 +31,6 @@ internal class KtFirAnonymousFunctionSymbol(
builder.buildParameterSymbol(valueParameter)
}
}
override fun createPointer(): KtSymbolPointer<KtAnonymousFunctionSymbol> = NonRestorableKtSymbolPointer
}
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.idea.fir.findPsi
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.ValidityOwner
import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.ReadOnlyWeakRef
@@ -57,4 +58,9 @@ internal class KtFirClassOrObjectSymbol(
else -> KtSymbolKind.TOP_LEVEL
}
}
override fun createPointer(): KtSymbolPointer<KtClassOrObjectSymbol> {
val classId = classId
return symbolPointer { session -> session.symbolProvider.getClassOrObjectSymbolByClassId(classId) }
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.frontend.api.fir.symbols
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.getPrimaryConstructorIfAny
import org.jetbrains.kotlin.fir.declarations.impl.FirValueParameterImpl
import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
import org.jetbrains.kotlin.idea.fir.findPsi
@@ -16,10 +17,7 @@ import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.cached
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.weakRef
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtConstructorParameterSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtConstructorSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolKind
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
internal class KtFirConstructorSymbol(
@@ -49,4 +47,18 @@ internal class KtFirConstructorSymbol(
check(firClass is FirRegularClass) { "Owner class for constructor should be FirRegularClass, but ${firClass::class} was met" }
builder.buildClassSymbol(firClass)
}
override fun createPointer(): KtSymbolPointer<KtConstructorSymbol> = withValidityAssertion {
if (!isPrimary) {
// TODO for now we can not find symbol for member function :(
return NonRestorableKtSymbolPointer
}
val ownerClassId = owner.classId
return symbolPointer { session ->
val ownerSymbol = session.symbolProvider.getClassOrObjectSymbolByClassId(ownerClassId) ?: return@symbolPointer null
check(ownerSymbol is KtFirSymbol<*>)
val classFir = (ownerSymbol.fir as? FirRegularClass) ?: error("FirRegularClass expected but ${ownerSymbol.fir::class} found")
classFir.getPrimaryConstructorIfAny()?.let(builder::buildConstructorSymbol)
}
}
}
@@ -13,12 +13,10 @@ import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.frontend.api.ValidityOwner
import org.jetbrains.kotlin.idea.frontend.api.ValidityOwnerByValidityToken
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.cached
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtPackageSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolOrigin
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolPointer
import org.jetbrains.kotlin.idea.frontend.api.symbols.symbolPointer
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.name.FqName
class KtFirPackageSymbol(
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,2 @@
class A() {
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2020 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.idea.frontend.api.symbols
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.addExternalTestFiles
import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.Assert
import java.io.File
abstract class AbstractSymbolPointerTest : KotlinLightCodeInsightFixtureTestCase() {
protected fun doTest(path: String) {
val file = File(path)
val ktFile = myFixture.configureByText(file.name, FileUtil.loadFile(file)) as KtFile
val pointers = executeOnPooledThreadInReadAction {
val analysisSession = KtFirAnalysisSession(ktFile)
val declarationSymbols = ktFile.collectDescendantsOfType<KtDeclaration>().map { declaration ->
analysisSession.symbolProvider.getSymbol(declaration)
}
declarationSymbols.map { PointerWithPsi(it.createPointer(), it.psi!!) }
}
// another read action
executeOnPooledThreadInReadAction {
val analysisSession = KtFirAnalysisSession(ktFile)
pointers.forEach { (pointer, psi) ->
val restored = pointer.restoreSymbol(analysisSession) ?: error("Symbol $psi was not not restored correctly")
Assert.assertEquals(restored.psi!!, psi)
}
}
}
}
private data class PointerWithPsi(val pointer: KtSymbolPointer<*>, val psi: PsiElement)
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2020 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.idea.frontend.api.symbols;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/idea-frontend-fir/testData/symbolPointer")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class SymbolPointerTestGenerated extends AbstractSymbolPointerTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInSymbolPointer() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/symbolPointer"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("class.kt")
public void testClass() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolPointer/class.kt");
}
@TestMetadata("classPrimaryConstructor.kt")
public void testClassPrimaryConstructor() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolPointer/classPrimaryConstructor.kt");
}
}