[LL FIR] introduce tests to check lazy resolve stdlib as sources

^KTIJ-25761
This commit is contained in:
Dmitrii Gridin
2023-06-09 17:00:32 +02:00
committed by Space Team
parent e81099ca96
commit 563f913a95
6 changed files with 270 additions and 111 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2022 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,17 +7,14 @@ package org.jetbrains.kotlin.analysis.api.fir.test.configurators.library
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisApiBaseTestServiceRegistrar
import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisApiDecompiledCodeTestServiceRegistrar
import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisApiLibraryBaseTestServiceRegistrar
import org.jetbrains.kotlin.analysis.api.impl.base.util.LibraryUtils
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.low.level.api.fir.compiler.based.SealedClassesInheritorsCaclulatorPreAnalysisHandler
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AnalysisApiFirTestServiceRegistrar
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtLibraryModuleImpl
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtLibrarySourceModuleImpl
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.createKtLibrarySourceModule
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtModuleFactory
import org.jetbrains.kotlin.analysis.test.framework.project.structure.TestModuleStructureFactory
import org.jetbrains.kotlin.analysis.test.framework.services.libraries.CompiledLibraryProvider
@@ -67,26 +64,11 @@ object AnalysisApiFirLibrarySourceTestConfigurator : AnalysisApiTestConfigurator
private class KtLibrarySourceModuleFactory : KtModuleFactory {
override fun createModule(testModule: TestModule, testServices: TestServices, project: Project): KtModuleWithFiles {
val (libraryJar, librarySourcesJar) = testServices.compiledLibraryProvider.compileToLibrary(testModule)
val libraryKtModule = KtLibraryModuleImpl(
testModule.name,
testModule.targetPlatform,
GlobalSearchScope.filesScope(project, LibraryUtils.getAllVirtualFilesFromJar(libraryJar)),
project,
binaryRoots = listOf(libraryJar),
librarySources = null,
return createKtLibrarySourceModule(
libraryJar = libraryJar,
librarySourcesJar = librarySourcesJar,
testModule = testModule,
project = project,
)
val decompiledPsiFilesFromSourceJar = LibraryUtils.getAllPsiFilesFromTheJar(librarySourcesJar, project)
val librarySourceKtModule = KtLibrarySourceModuleImpl(
testModule.name,
testModule.targetPlatform,
GlobalSearchScope.filesScope(project, decompiledPsiFilesFromSourceJar.map { it.virtualFile }),
project,
binaryLibrary = libraryKtModule,
)
libraryKtModule.librarySources = librarySourceKtModule
return KtModuleWithFiles(librarySourceKtModule, decompiledPsiFilesFromSourceJar)
}
}
@@ -5,56 +5,35 @@
package org.jetbrains.kotlin.analysis.low.level.api.fir
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDesignationWithFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.resolveToFirSymbol
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.asResolveTarget
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirResolvableModuleSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AbstractLowLevelApiSingleFileTest
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirOutOfContentRootTestConfigurator
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirSourceTestConfigurator
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.LLFirLazyResolverRunner
import org.jetbrains.kotlin.analysis.test.framework.services.expressionMarkerProvider
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.renderer.FirDeclarationRendererWithAttributes
import org.jetbrains.kotlin.fir.renderer.FirErrorExpressionExtendedRenderer
import org.jetbrains.kotlin.fir.renderer.FirFileAnnotationsContainerRenderer
import org.jetbrains.kotlin.fir.renderer.FirRenderer
import org.jetbrains.kotlin.fir.renderer.FirResolvePhaseRenderer
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.ConfigurationDirectives
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
import org.jetbrains.kotlin.test.directives.model.ValueDirective
import org.jetbrains.kotlin.test.directives.model.singleOrZeroValue
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
import org.junit.jupiter.api.parallel.Execution
import org.junit.jupiter.api.parallel.ExecutionMode
/**
* Test that we do not resolve declarations we do not need & do not build bodies for them
*/
@Execution(ExecutionMode.SAME_THREAD)
abstract class AbstractFirLazyDeclarationResolveTest : AbstractLowLevelApiSingleFileTest() {
override fun doTestByFileStructure(ktFile: KtFile, moduleStructure: TestModuleStructure, testServices: TestServices) {
val resultBuilder = StringBuilder()
val renderer = FirRenderer(
builder = resultBuilder,
declarationRenderer = FirDeclarationRendererWithAttributes(),
resolvePhaseRenderer = FirResolvePhaseRenderer(),
errorExpressionRenderer = FirErrorExpressionExtendedRenderer(),
fileAnnotationsContainerRenderer = FirFileAnnotationsContainerRenderer(),
)
abstract class AbstractFirLazyDeclarationResolveTest : AbstractFirLazyDeclarationResolveTestCase() {
override fun checkSession(firSession: LLFirResolveSession) {
require(firSession.isSourceSession)
}
resolveWithClearCaches(ktFile) { firResolveSession ->
check(firResolveSession.isSourceSession)
val resolver = if (Directives.RESOLVE_FILE_ANNOTATIONS in moduleStructure.allDirectives) {
override fun doTestByFileStructure(ktFile: KtFile, moduleStructure: TestModuleStructure, testServices: TestServices) {
doLazyResolveTest(ktFile, testServices) { firResolveSession ->
if (Directives.RESOLVE_FILE_ANNOTATIONS in moduleStructure.allDirectives) {
val annotationContainer = firResolveSession.getOrBuildFirFile(ktFile).annotationsContainer
val session = annotationContainer.moduleData.session as LLFirResolvableModuleSession
fun(phase: FirResolvePhase) {
@@ -72,50 +51,6 @@ abstract class AbstractFirLazyDeclarationResolveTest : AbstractLowLevelApiSingle
firDeclaration.lazyResolveToPhase(phase)
}
}
for (currentPhase in FirResolvePhase.values()) {
if (currentPhase == FirResolvePhase.SEALED_CLASS_INHERITORS) continue
resolver(currentPhase)
val firFile = firResolveSession.getOrBuildFirFile(ktFile)
if (resultBuilder.isNotEmpty()) {
resultBuilder.appendLine()
}
resultBuilder.append("${currentPhase.name}:\n")
renderer.renderElementAsString(firFile)
}
}
resolveWithClearCaches(ktFile) { llSession ->
check(llSession.isSourceSession)
val firFile = llSession.getOrBuildFirFile(ktFile)
firFile.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
if (resultBuilder.isNotEmpty()) {
resultBuilder.appendLine()
}
resultBuilder.append("FILE RAW TO BODY:\n")
renderer.renderElementAsString(firFile)
}
testServices.assertions.assertEqualsToTestDataFileSibling(resultBuilder.toString())
}
private fun chooseMemberDeclarationIfNeeded(symbol: FirBasedSymbol<*>, moduleStructure: TestModuleStructure): FirBasedSymbol<*> {
val directives = moduleStructure.allDirectives
val memberClassFilters = listOfNotNull(
directives.singleOrZeroValue(Directives.MEMBER_CLASS_FILTER),
directives.singleOrZeroValue(Directives.MEMBER_NAME_FILTER),
).ifEmpty { return symbol }
val classSymbol = symbol as FirClassSymbol
val declarations = classSymbol.declarationSymbols
val filteredSymbols = declarations.filter { declaration -> memberClassFilters.all { it.invoke(declaration) } }
return when (filteredSymbols.size) {
0 -> error("Empty result for:${declarations.joinToString("\n")}")
1 -> filteredSymbols.single()
else -> error("Result ambiguity:\n${filteredSymbols.joinToString("\n")}")
}
}
@@ -131,19 +66,6 @@ abstract class AbstractFirLazyDeclarationResolveTest : AbstractLowLevelApiSingle
}
private object Directives : SimpleDirectivesContainer() {
val MEMBER_CLASS_FILTER: ValueDirective<(FirBasedSymbol<*>) -> Boolean> by valueDirective("Choose member declaration by a declaration class") { value ->
val clazz = Class.forName(value)
({ symbol: FirBasedSymbol<*> ->
clazz.isInstance(symbol)
})
}
val MEMBER_NAME_FILTER: ValueDirective<(FirBasedSymbol<*>) -> Boolean> by valueDirective("Choose member declaration by a declaration name") { value ->
{ symbol: FirBasedSymbol<*> ->
symbol.name() == value
}
}
val RESOLVE_FILE_ANNOTATIONS by directive("Resolve file annotations instead of declaration at caret")
}
}
@@ -0,0 +1,119 @@
/*
* 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
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AbstractLowLevelApiSingleFileTest
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.renderer.FirDeclarationRendererWithAttributes
import org.jetbrains.kotlin.fir.renderer.FirErrorExpressionExtendedRenderer
import org.jetbrains.kotlin.fir.renderer.FirFileAnnotationsContainerRenderer
import org.jetbrains.kotlin.fir.renderer.FirRenderer
import org.jetbrains.kotlin.fir.renderer.FirResolvePhaseRenderer
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
import org.jetbrains.kotlin.test.directives.model.ValueDirective
import org.jetbrains.kotlin.test.directives.model.singleOrZeroValue
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
/**
* Test that we do not resolve declarations we do not need & do not build bodies for them
*/
abstract class AbstractFirLazyDeclarationResolveTestCase : AbstractLowLevelApiSingleFileTest() {
abstract fun checkSession(firSession: LLFirResolveSession)
protected fun doLazyResolveTest(
ktFile: KtFile,
testServices: TestServices,
resolverProvider: (LLFirResolveSession) -> ((FirResolvePhase) -> Unit),
) {
val resultBuilder = StringBuilder()
val renderer = FirRenderer(
builder = resultBuilder,
declarationRenderer = FirDeclarationRendererWithAttributes(),
resolvePhaseRenderer = FirResolvePhaseRenderer(),
errorExpressionRenderer = FirErrorExpressionExtendedRenderer(),
fileAnnotationsContainerRenderer = FirFileAnnotationsContainerRenderer(),
)
resolveWithClearCaches(ktFile) { firResolveSession ->
checkSession(firResolveSession)
val resolver = resolverProvider(firResolveSession)
for (currentPhase in FirResolvePhase.entries) {
if (currentPhase == FirResolvePhase.SEALED_CLASS_INHERITORS) continue
resolver(currentPhase)
val firFile = firResolveSession.getOrBuildFirFile(ktFile)
if (resultBuilder.isNotEmpty()) {
resultBuilder.appendLine()
}
resultBuilder.append("${currentPhase.name}:\n")
renderer.renderElementAsString(firFile)
}
}
resolveWithClearCaches(ktFile) { llSession ->
checkSession(llSession)
val firFile = llSession.getOrBuildFirFile(ktFile)
firFile.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
if (resultBuilder.isNotEmpty()) {
resultBuilder.appendLine()
}
resultBuilder.append("FILE RAW TO BODY:\n")
renderer.renderElementAsString(firFile)
}
testServices.assertions.assertEqualsToTestDataFileSibling(resultBuilder.toString())
}
protected fun chooseMemberDeclarationIfNeeded(symbol: FirBasedSymbol<*>, moduleStructure: TestModuleStructure): FirBasedSymbol<*> {
val directives = moduleStructure.allDirectives
val memberClassFilters = listOfNotNull(
directives.singleOrZeroValue(Directives.MEMBER_CLASS_FILTER),
directives.singleOrZeroValue(Directives.MEMBER_NAME_FILTER),
).ifEmpty { return symbol }
val classSymbol = symbol as FirClassSymbol
val declarations = classSymbol.declarationSymbols
val filteredSymbols = declarations.filter { declaration -> memberClassFilters.all { it.invoke(declaration) } }
return when (filteredSymbols.size) {
0 -> error("Empty result for:${declarations.joinToString("\n")}")
1 -> filteredSymbols.single()
else -> error("Result ambiguity:\n${filteredSymbols.joinToString("\n")}")
}
}
override fun configureTest(builder: TestConfigurationBuilder) {
super.configureTest(builder)
with(builder) {
useDirectives(Directives)
}
}
private object Directives : SimpleDirectivesContainer() {
val MEMBER_CLASS_FILTER: ValueDirective<(FirBasedSymbol<*>) -> Boolean> by valueDirective("Choose member declaration by a declaration class") { value ->
val clazz = Class.forName(value)
({ symbol: FirBasedSymbol<*> ->
clazz.isInstance(symbol)
})
}
val MEMBER_NAME_FILTER: ValueDirective<(FirBasedSymbol<*>) -> Boolean> by valueDirective("Choose member declaration by a declaration name") { value ->
{ symbol: FirBasedSymbol<*> ->
symbol.name() == value
}
}
}
}
@@ -0,0 +1,65 @@
/*
* 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
import org.jetbrains.kotlin.analysis.api.fir.findPsi
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirStdlibSourceTestConfigurator
import org.jetbrains.kotlin.analysis.project.structure.KtLibrarySourceModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
import org.jetbrains.kotlin.analysis.utils.errors.requireIsInstance
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.providers.getRegularClassSymbolByClassId
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
import org.jetbrains.kotlin.test.directives.model.singleValue
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractStdLibSourcesLazyDeclarationResolveTest : AbstractFirLazyDeclarationResolveTestCase() {
override fun checkSession(firSession: LLFirResolveSession) {
requireIsInstance<KtLibrarySourceModule>(firSession.useSiteKtModule)
}
override fun configureTest(builder: TestConfigurationBuilder) {
super.configureTest(builder)
builder.useDirectives(Directives)
}
override val configurator get() = AnalysisApiFirStdlibSourceTestConfigurator
override fun doTestByFileStructure(ktFile: KtFile, moduleStructure: TestModuleStructure, testServices: TestServices) {
val project = ktFile.project
val classId = moduleStructure.allDirectives.singleValue(Directives.CLASS_ID).let(ClassId::fromString)
val module = ProjectStructureProvider.getModule(project, ktFile, contextualModule = null)
val resolveSession = LLFirResolveSessionService.getInstance(project).getFirResolveSession(module)
val classDeclaration = findRegularClass(classId, module, resolveSession).findPsi() as KtClassOrObject
val file = classDeclaration.containingFile as KtFile
doLazyResolveTest(file, testServices) { firSession ->
val regularClass = findRegularClass(classId, module, firSession)
val declarationToResolve = chooseMemberDeclarationIfNeeded(regularClass, moduleStructure)
fun(phase: FirResolvePhase) {
declarationToResolve.lazyResolveToPhase(phase)
}
}
}
private fun findRegularClass(classId: ClassId, module: KtModule, firSession: LLFirResolveSession): FirRegularClassSymbol {
val symbolProvider = firSession.getSessionFor(module).symbolProvider
return symbolProvider.getRegularClassSymbolByClassId(classId) ?: error("'$classId' is not found")
}
private object Directives : SimpleDirectivesContainer() {
val CLASS_ID by stringDirective("ClassId of expected class")
}
}
@@ -0,0 +1,67 @@
/*
* 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.test.configurators
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import java.nio.file.Path
import org.jetbrains.kotlin.analysis.api.impl.base.util.LibraryUtils
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.project.structure.KtLibrarySourceModule
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtLibraryModuleImpl
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtLibrarySourceModuleImpl
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtModuleFactory
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
object AnalysisApiFirStdlibSourceTestConfigurator : AnalysisApiFirSourceLikeTestConfigurator(false) {
override fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable) {
super.configureTest(builder, disposable)
builder.apply {
useAdditionalService<KtModuleFactory> { KtStdlibSourceModuleFactory() }
}
}
}
fun createKtLibrarySourceModule(libraryJar: Path, librarySourcesJar: Path, testModule: TestModule, project: Project): KtModuleWithFiles {
val libraryKtModule = KtLibraryModuleImpl(
testModule.name,
testModule.targetPlatform,
GlobalSearchScope.filesScope(project, LibraryUtils.getAllVirtualFilesFromJar(libraryJar)),
project,
binaryRoots = listOf(libraryJar),
librarySources = null,
)
val decompiledPsiFilesFromSourceJar = LibraryUtils.getAllPsiFilesFromTheJar(librarySourcesJar, project)
val librarySourceKtModule = KtLibrarySourceModuleImpl(
testModule.name,
testModule.targetPlatform,
GlobalSearchScope.filesScope(project, decompiledPsiFilesFromSourceJar.map { it.virtualFile }),
project,
binaryLibrary = libraryKtModule,
)
libraryKtModule.librarySources = librarySourceKtModule
return KtModuleWithFiles(librarySourceKtModule, decompiledPsiFilesFromSourceJar)
}
private class KtStdlibSourceModuleFactory : KtModuleFactory {
override fun createModule(testModule: TestModule, testServices: TestServices, project: Project): KtModuleWithFiles {
val libraryJar = ForTestCompileRuntime.runtimeJarForTests().toPath()
val librarySourcesJar = ForTestCompileRuntime.runtimeSourcesJarForTests().toPath()
return createKtLibrarySourceModule(
libraryJar = libraryJar,
librarySourcesJar = librarySourcesJar,
testModule = testModule,
project = project,
)
}
}
@@ -44,6 +44,10 @@ internal fun TestGroupSuite.generateFirLowLevelApiTests() {
model("lazyResolve")
}
testClass<AbstractStdLibSourcesLazyDeclarationResolveTest> {
model("lazyResolveStdlibSources")
}
testClass<AbstractFirOutOfContentRootLazyDeclarationResolveTest> {
model("lazyResolve")
}