FIR IDE: add tests for lazy resolving single declaration in file

This commit is contained in:
Ilya Kirillov
2020-10-09 11:36:27 +03:00
parent f4199a0729
commit 71b5b6df7c
23 changed files with 298 additions and 12 deletions
@@ -84,6 +84,7 @@ import org.jetbrains.kotlin.idea.filters.AbstractKotlinExceptionFilterTest
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyResolveTest
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolveTest
import org.jetbrains.kotlin.idea.fir.AbstractKtDeclarationAndFirDeclarationEqualityChecker
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyDeclarationResolveTest
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleLazyResolveTest
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest
@@ -1035,6 +1036,9 @@ fun main(args: Array<String>) {
testClass<AbstractFirMultiModuleLazyResolveTest> {
model("multiModuleLazyResolve", recursive = false, extension = null)
}
testClass<AbstractFirLazyDeclarationResolveTest> {
model("lazyResolve")
}
}
testGroup("idea/idea-fir/tests", "idea") {
@@ -11,10 +11,12 @@ import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.fir.dependenciesWithoutSelf
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSessionFactory
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSessionProvider
@@ -31,22 +33,33 @@ internal class FirIdeResolveStateService(project: Project) {
ConcurrentHashMap<IdeaModuleInfo, FirModuleResolveStateImpl>()
}
private fun createResolveStateFor(moduleInfo: IdeaModuleInfo): FirModuleResolveStateImpl {
require(moduleInfo is ModuleSourceInfo)
val sessionProvider = sessionProviderStorage.getSessionProvider(moduleInfo)
val firFileBuilder = sessionProvider.rootModuleSession.firFileBuilder
return FirModuleResolveStateImpl(
moduleInfo,
sessionProvider,
firFileBuilder,
FirLazyDeclarationResolver(firFileBuilder),
)
}
fun getResolveState(moduleInfo: IdeaModuleInfo): FirModuleResolveStateImpl =
stateCache.computeIfAbsent(moduleInfo) { createResolveStateFor(moduleInfo) }
stateCache.computeIfAbsent(moduleInfo) { createResolveStateFor(moduleInfo, sessionProviderStorage) }
companion object {
fun getInstance(project: Project): FirIdeResolveStateService = project.service()
internal fun createResolveStateFor(
moduleInfo: IdeaModuleInfo,
sessionProviderStorage: FirIdeSessionProviderStorage
): FirModuleResolveStateImpl {
require(moduleInfo is ModuleSourceInfo)
val sessionProvider = sessionProviderStorage.getSessionProvider(moduleInfo)
val firFileBuilder = sessionProvider.rootModuleSession.firFileBuilder
return FirModuleResolveStateImpl(
moduleInfo,
sessionProvider,
firFileBuilder,
FirLazyDeclarationResolver(firFileBuilder),
)
}
}
}
@TestOnly
fun createResolveStateForNoCaching(
moduleInfo: IdeaModuleInfo,
): FirModuleResolveState = FirIdeResolveStateService.createResolveStateFor(moduleInfo, FirIdeSessionProviderStorage(moduleInfo.project!!))
@@ -79,4 +79,8 @@ internal class FirModuleResolveStateForCompletion(
override fun findNonLocalSourceFirDeclaration(ktDeclaration: KtDeclaration): FirDeclaration {
error("Should not be used in completion")
}
override fun getBuiltFirFileOrNull(ktFile: KtFile): FirFile? {
error("Should not be used in completion")
}
}
@@ -57,6 +57,11 @@ internal class FirModuleResolveStateImpl(
override fun collectDiagnosticsForFile(ktFile: KtFile): Collection<Diagnostic> =
diagnosticsCollector.collectDiagnosticsForFile(ktFile)
override fun getBuiltFirFileOrNull(ktFile: KtFile): FirFile? {
val cache = sessionProvider.getModuleCache(ktFile.getModuleInfo() as ModuleSourceInfo)
return firFileBuilder.getBuiltFirFileOrNull(ktFile, cache)
}
override fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile) {
error("Should be called only from FirModuleResolveStateForCompletion")
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.fir.low.level.api.api
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
@@ -43,6 +44,9 @@ abstract class FirModuleResolveState {
internal abstract fun collectDiagnosticsForFile(ktFile: KtFile): Collection<Diagnostic>
@TestOnly
internal abstract fun getBuiltFirFileOrNull(ktFile: KtFile): FirFile?
abstract fun findNonLocalSourceFirDeclaration(
ktDeclaration: KtDeclaration,
): FirDeclaration
@@ -36,6 +36,9 @@ internal class FirFileBuilder(
RawFirBuilder(cache.session, scopeProvider, stubMode = false, lazyBodiesMode = lazyBodiesMode).buildFirFile(ktFile)
}
fun getBuiltFirFileOrNull(ktFile: KtFile, cache: ModuleFileCache): FirFile? =
cache.getCachedFirFile(ktFile)
fun isFirFileBuilt(
ktFile: KtFile,
cache: ModuleFileCache
@@ -0,0 +1,17 @@
class A {
fun resolveMe() {
receive(functionWithLazyBody())
}
val x: Int = 10
get() = field
set(value) {
field = value
}
fun receive(value: String) {}
fun functionWithLazyBody(): String {
return "42"
}
}
@@ -0,0 +1,15 @@
FILE: classMembers.kt
public final [BODY_RESOLVE] class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public final [BODY_RESOLVE] fun resolveMe(): R|kotlin/Unit| {
this@R|/A|.R|/A.receive|(this@R|/A|.R|/A.functionWithLazyBody|())
}
public final [STATUS] fun receive(value: R|kotlin/String|): R|kotlin/Unit| { LAZY_BLOCK }
public final [STATUS] fun functionWithLazyBody(): R|kotlin/String| { LAZY_BLOCK }
}
@@ -0,0 +1,8 @@
fun resolveMe() {
receive(withGetter)
}
fun receive(value: Int){}
val withGetter: Int
get() = 42
@@ -0,0 +1,7 @@
FILE: propertyWithGetter.kt
public final [BODY_RESOLVE] fun resolveMe(): R|kotlin/Unit| {
R|/receive|(R|/withGetter|)
}
public final [STATUS] fun receive(value: R|kotlin/Int|): R|kotlin/Unit| { LAZY_BLOCK }
public final [STATUS] val withGetter: R|kotlin/Int|
public get(): R|kotlin/Int| { LAZY_BLOCK }
@@ -0,0 +1,12 @@
fun resolveMe() {
receive(withGetterAndSetter)
withGetterAndSetter = 123
}
fun receive(value: Int) {}
var withGetterAndSetter: Int = 42
get() = field
set(value) {
field = value
}
@@ -0,0 +1,9 @@
FILE: propertyWithGetterAndSetter.kt
public final [BODY_RESOLVE] fun resolveMe(): R|kotlin/Unit| {
R|/receive|(R|/withGetterAndSetter|)
R|/withGetterAndSetter| = Int(123)
}
public final [STATUS] fun receive(value: R|kotlin/Int|): R|kotlin/Unit| { LAZY_BLOCK }
public final [STATUS] var withGetterAndSetter: R|kotlin/Int| = IntegerLiteral(42)
public get(): R|kotlin/Int| { LAZY_BLOCK }
public set(value: R|kotlin/Int|): R|kotlin/Unit| { LAZY_BLOCK }
@@ -0,0 +1,11 @@
fun resolveMe() {
receive(A(42))
}
fun receive(value: A){}
class A {
constructor(x: Int) {
val a = x
}
}
@@ -0,0 +1,9 @@
FILE: secondaryConstructor.kt
public final [BODY_RESOLVE] fun resolveMe(): R|kotlin/Unit| {
R|/receive|(R|/A.A|(Int(42)))
}
public final [STATUS] fun receive(value: R|A|): R|kotlin/Unit| { LAZY_BLOCK }
public final [STATUS] class A : R|kotlin/Any| {
public constructor(x: R|kotlin/Int|): R|A| { LAZY_BLOCK }
}
@@ -0,0 +1,9 @@
fun resolveMe() {
receive(functionWithLazyBody())
}
fun receive(value: String){}
fun functionWithLazyBody(): String {
return "42"
}
@@ -0,0 +1,6 @@
FILE: topLevelFunctions.kt
public final [BODY_RESOLVE] fun resolveMe(): R|kotlin/Unit| {
R|/receive|(R|/functionWithLazyBody|())
}
public final [STATUS] fun receive(value: R|kotlin/String|): R|kotlin/Unit| { LAZY_BLOCK }
public final [STATUS] fun functionWithLazyBody(): R|kotlin/String| { LAZY_BLOCK }
@@ -0,0 +1,7 @@
fun resolveMe() {
receive(functionWithLazyBody())
}
fun receive(value: String){}
fun functionWithLazyBody(): String = "42"
@@ -0,0 +1,6 @@
FILE: topLevelFunctionsWithExpressionBodyAndExplicitType.kt
public final [BODY_RESOLVE] fun resolveMe(): R|kotlin/Unit| {
R|/receive|(R|/functionWithLazyBody|())
}
public final [STATUS] fun receive(value: R|kotlin/String|): R|kotlin/Unit| { LAZY_BLOCK }
public final [STATUS] fun functionWithLazyBody(): R|kotlin/String| { LAZY_BLOCK }
@@ -0,0 +1,7 @@
fun resolveMe() {
receive(functionWithLazyBody())
}
fun receive(value: String){}
fun functionWithLazyBody() = "42"
@@ -0,0 +1,8 @@
FILE: topLevelFunctionsWithImplicitType.kt
public final [BODY_RESOLVE] fun resolveMe(): R|kotlin/Unit| {
R|/receive|(R|/functionWithLazyBody|())
}
public final [STATUS] fun receive(value: R|kotlin/String|): R|kotlin/Unit| { LAZY_BLOCK }
public final [IMPLICIT_TYPES_BODY_RESOLVE] fun functionWithLazyBody(): R|kotlin/String| {
^functionWithLazyBody String(42)
}
@@ -0,0 +1,47 @@
/*
* 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.fir.low.level.api
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.fir.FirRenderer
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.idea.fir.low.level.api.api.LowLevelFirApiFacade
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
/**
* Test that we do not resolve declarations we do not need & do not build bodies for them
*/
abstract class AbstractFirLazyDeclarationResolveTest : KotlinLightCodeInsightFixtureTestCase() {
override fun isFirPlugin(): Boolean = true
fun doTest(path: String) {
val testDataFile = File(path)
val ktFile = myFixture.configureByText(testDataFile.name, FileUtil.loadFile(testDataFile)) as KtFile
val lazyDeclarations = ktFile.collectDescendantsOfType<KtDeclaration> { ktDeclaration -> !KtPsiUtil.isLocal(ktDeclaration) }
val declarationToResolve = lazyDeclarations.firstOrNull { it.name?.toLowerCase() == "resolveme" }
?: error("declaration with name `resolveMe` was not found")
resolveWithClearCaches(ktFile) { firModuleResolveState ->
val rendered = LowLevelFirApiFacade.withFirDeclaration(
declarationToResolve,
firModuleResolveState,
FirResolvePhase.BODY_RESOLVE
) @Suppress("UNUSED_ANONYMOUS_PARAMETER") { firDeclaration ->
val firFile = firModuleResolveState.getBuiltFirFileOrNull(ktFile)!!
firFile.render(FirRenderer.RenderMode.WithResolvePhases)
}
val expectedFileName = testDataFile.name.replace(".kt", ".txt")
KotlinTestUtils.assertEqualsToFile(testDataFile.parentFile.resolve(expectedFileName), rendered)
}
}
}
@@ -0,0 +1,65 @@
/*
* 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.fir.low.level.api;
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/idea-fir-low-level-api/testdata/lazyResolve")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class FirLazyDeclarationResolveTestGenerated extends AbstractFirLazyDeclarationResolveTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInLazyResolve() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/lazyResolve"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("classMembers.kt")
public void testClassMembers() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/lazyResolve/classMembers.kt");
}
@TestMetadata("propertyWithGetter.kt")
public void testPropertyWithGetter() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/lazyResolve/propertyWithGetter.kt");
}
@TestMetadata("propertyWithGetterAndSetter.kt")
public void testPropertyWithGetterAndSetter() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/lazyResolve/propertyWithGetterAndSetter.kt");
}
@TestMetadata("secondaryConstructor.kt")
public void testSecondaryConstructor() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/lazyResolve/secondaryConstructor.kt");
}
@TestMetadata("topLevelFunctions.kt")
public void testTopLevelFunctions() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/lazyResolve/topLevelFunctions.kt");
}
@TestMetadata("topLevelFunctionsWithExpressionBodyAndExplicitType.kt")
public void testTopLevelFunctionsWithExpressionBodyAndExplicitType() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/lazyResolve/topLevelFunctionsWithExpressionBodyAndExplicitType.kt");
}
@TestMetadata("topLevelFunctionsWithImplicitType.kt")
public void testTopLevelFunctionsWithImplicitType() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/lazyResolve/topLevelFunctionsWithImplicitType.kt");
}
}
@@ -7,5 +7,15 @@ package org.jetbrains.kotlin.idea.fir.low.level.api
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.api.LowLevelFirApiFacade
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSessionProviderStorage
import org.jetbrains.kotlin.psi.KtElement
internal fun Project.allModules() = ModuleManager.getInstance(this).modules.toList()
inline fun resolveWithClearCaches(context: KtElement, action: (FirModuleResolveState) -> Unit) {
val resolveState = createResolveStateForNoCaching(context.getModuleInfo())
action(resolveState)
}