[FIR & IR] Implement JS MPP test infrastructure

^KT-55295 Fixed
This commit is contained in:
Ivan Kochurkin
2023-01-21 15:38:51 +01:00
committed by Space Team
parent e42efe1ee6
commit d401ff7b09
13 changed files with 141 additions and 52 deletions
@@ -35,6 +35,6 @@ object IrActualizer {
}
private fun mergeIrFragments(mainFragment: IrModuleFragment, dependentFragments: List<IrModuleFragment>) {
mainFragment.files.addAll(dependentFragments.flatMap { it.files })
mainFragment.files.addAll(0, dependentFragments.flatMap { it.files })
}
}
@@ -1,7 +1,7 @@
// !LANGUAGE: +MultiPlatformProjects
// WITH_STDLIB
// IGNORE_BACKEND_K1: ANY
// IGNORE_BACKEND_K2: JS_IR, NATIVE
// IGNORE_BACKEND_K2: NATIVE
// MODULE: common
// FILE: common.kt
@@ -0,0 +1,28 @@
// KJS_WITH_FULL_RUNTIME
// IGNORE_BACKEND_K1: JS, JS_IR, JS_IR_ES6
// TARGET_BACKEND: JS_IR
// !LANGUAGE: +MultiPlatformProjects
// MODULE: common
// TARGET_PLATFORM: Common
// FILE: common.kt
expect fun func(): String
expect var prop: String
fun test(): String {
prop = "K"
return func() + prop
}
// MODULE: js()()(common)
// TARGET_PLATFORM: JS
// FILE: main.kt
actual fun func(): String = "O"
actual var prop: String = "!"
fun box() = test()
@@ -30,14 +30,18 @@ sealed class IrBackendInput : ResultingArtifact.BackendInput<IrBackendInput>() {
abstract val irPluginContext: IrPluginContext
data class JsIrBackendInput(
override val irModuleFragment: IrModuleFragment,
val mainModuleFragment: IrModuleFragment,
val dependentModuleFragments: List<IrModuleFragment>,
override val irPluginContext: IrPluginContext,
val sourceFiles: List<KtSourceFile>,
val icData: List<KotlinFileSerializedData>,
val expectDescriptorToSymbol: MutableMap<DeclarationDescriptor, IrSymbol>, // TODO: abstract from descriptors
val hasErrors: Boolean,
val serializeSingleFile: (KtSourceFile) -> ProtoBuf.PackageFragment
) : IrBackendInput()
) : IrBackendInput() {
override val irModuleFragment: IrModuleFragment
get() = mainModuleFragment
}
data class JvmIrBackendInput(
val state: GenerationState,
@@ -105,6 +105,7 @@ class ClassicFrontend2IrConverter(
return IrBackendInput.JsIrBackendInput(
moduleFragment,
dependentModuleFragments = emptyList(),
pluginContext,
sourceFiles.map(::KtPsiSourceFile),
icData,
@@ -5,26 +5,24 @@
package org.jetbrains.kotlin.test.frontend.fir
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.fir.AbstractFirAnalyzerFacade
import org.jetbrains.kotlin.fir.FirAnalyzerFacade
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.backend.*
import org.jetbrains.kotlin.fir.backend.jvm.Fir2IrJvmSpecialAnnotationSymbolProvider
import org.jetbrains.kotlin.fir.backend.jvm.FirJvmKotlinMangler
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.descriptors.FirModuleDescriptor
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.resolve.providers.firProvider
import org.jetbrains.kotlin.fir.resolve.providers.impl.FirProviderImpl
import org.jetbrains.kotlin.fir.serialization.FirElementAwareSerializableStringTable
import org.jetbrains.kotlin.fir.serialization.FirKLibSerializerExtension
import org.jetbrains.kotlin.fir.serialization.serializeSingleFirFile
@@ -33,8 +31,8 @@ import org.jetbrains.kotlin.ir.backend.js.JsFactories
import org.jetbrains.kotlin.ir.backend.js.getSerializedData
import org.jetbrains.kotlin.ir.backend.js.incrementalDataProvider
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerIr
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.library.metadata.resolver.KotlinResolvedLibrary
import org.jetbrains.kotlin.library.unresolvedDependencies
import org.jetbrains.kotlin.storage.LockBasedStorageManager
@@ -59,34 +57,67 @@ class Fir2IrJsResultsConverter(
module: TestModule,
inputArtifact: FirOutputArtifact
): IrBackendInput {
val compilerConfigurationProvider = testServices.compilerConfigurationProvider
val configuration = compilerConfigurationProvider.getCompilerConfiguration(module)
val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
val fir2IrExtensions = Fir2IrExtensions.Default
val firFiles = inputArtifact.allFirFiles.values
val (irModuleFragment, components, pluginContext) =
inputArtifact.partsForDependsOnModules.last().firAnalyzerFacade.convertToJsIr(firFiles, fir2IrExtensions, module, configuration, testServices)
lateinit var mainIrPart: IrModuleFragment
val dependentIrParts = mutableListOf<IrModuleFragment>()
val sourceFiles = mutableListOf<KtSourceFile>()
val firFilesAndComponentsBySourceFile = mutableMapOf<KtSourceFile, Pair<FirFile, Fir2IrComponents>>()
lateinit var mainPluginContext: IrPluginContext
var irBuiltIns: IrBuiltInsOverFir? = null
val sourceFiles = firFiles.mapNotNull { it.sourceFile }
val firFilesBySourceFile = firFiles.associateBy { it.sourceFile }
val commonMemberStorage = Fir2IrCommonMemberStorage(
generateSignatures = false,
signatureComposerCreator = null,
manglerCreator = { FirJvmKotlinMangler() } // TODO: replace with potentially simpler JS version
)
for ((index, part) in inputArtifact.partsForDependsOnModules.withIndex()) {
val (irModuleFragment, components, pluginContext) =
part.firAnalyzerFacade.convertToJsIr(
part.firFiles.values,
fir2IrExtensions = Fir2IrExtensions.Default,
module,
configuration,
testServices,
commonMemberStorage,
irBuiltIns
)
irBuiltIns = components.irBuiltIns
mainPluginContext = pluginContext
if (index < inputArtifact.partsForDependsOnModules.size - 1) {
dependentIrParts.add(irModuleFragment)
} else {
mainIrPart = irModuleFragment
}
sourceFiles.addAll(part.firFiles.mapNotNull { it.value.sourceFile })
for (firFile in part.firFiles.values) {
firFilesAndComponentsBySourceFile[firFile.sourceFile!!] = firFile to components
}
}
val icData = configuration.incrementalDataProvider?.getSerializedData(sourceFiles) ?: emptyList()
val expectDescriptorToSymbol = mutableMapOf<DeclarationDescriptor, IrSymbol>()
val metadataVersion = configuration.metadataVersion(module.languageVersionSettings.languageVersion)
// At this point, checkers will already have been run by a previous test step. `runCheckers` returns the cached diagnostics map.
val diagnosticsMap = inputArtifact.partsForDependsOnModules.last().firAnalyzerFacade.runCheckers()
val diagnosticsMap = inputArtifact.partsForDependsOnModules.fold(mutableMapOf<FirFile, List<KtDiagnostic>>()) { result, part ->
result.also { it.putAll(part.firAnalyzerFacade.runCheckers()) }
}
val hasErrors = diagnosticsMap.any { entry -> entry.value.any { it.severity == Severity.ERROR } }
return IrBackendInput.JsIrBackendInput(
irModuleFragment,
pluginContext,
mainIrPart,
dependentIrParts,
mainPluginContext,
sourceFiles,
icData,
expectDescriptorToSymbol,
hasErrors,
configuration.incrementalDataProvider?.getSerializedData(sourceFiles) ?: emptyList(),
expectDescriptorToSymbol = mutableMapOf(),
hasErrors = hasErrors
) { file ->
val firFile = firFilesBySourceFile[file] ?: error("cannot find FIR file by source file ${file.name} (${file.path})")
val (firFile, components) = firFilesAndComponentsBySourceFile[file]
?: error("cannot find FIR file by source file ${file.name} (${file.path})")
serializeSingleFirFile(
firFile,
components.session,
@@ -103,26 +134,17 @@ fun AbstractFirAnalyzerFacade.convertToJsIr(
fir2IrExtensions: Fir2IrExtensions,
module: TestModule,
configuration: CompilerConfiguration,
testServices: TestServices
testServices: TestServices,
commonMemberStorage: Fir2IrCommonMemberStorage,
irBuiltIns: IrBuiltInsOverFir?
): Fir2IrResult {
this as FirAnalyzerFacade
val commonFirFiles = session.moduleData.dependsOnDependencies
.map { it.session }
.filter { it.kind == FirSession.Kind.Source }
.flatMap { (it.firProvider as FirProviderImpl).getAllFirFiles() }
// TODO: consider avoiding repeated libraries resolution
val libraries = resolveJsLibraries(module, testServices, configuration)
val (dependencies, builtIns) = loadResolvedLibraries(libraries, configuration.languageVersionSettings, testServices)
val commonMemberStorage = Fir2IrCommonMemberStorage(
generateSignatures = false,
signatureComposerCreator = null,
manglerCreator = { FirJvmKotlinMangler() } // TODO: replace with potentially simpler JS version
)
return Fir2IrConverter.createModuleFragmentWithSignaturesIfNeeded(
session, scopeSession, firFiles + commonFirFiles,
session, scopeSession, firFiles.toList(),
languageVersionSettings,
fir2IrExtensions,
JsManglerIr, IrFactoryImpl,
@@ -132,7 +154,7 @@ fun AbstractFirAnalyzerFacade.convertToJsIr(
generateSignatures = false,
kotlinBuiltIns = builtIns ?: DefaultBuiltIns.Instance, // TODO: consider passing externally,
commonMemberStorage = commonMemberStorage,
initializedIrBuiltIns = null
initializedIrBuiltIns = irBuiltIns
).also {
(it.irModuleFragment.descriptor as? FirModuleDescriptor)?.let { it.allDependencyModules = dependencies }
}
@@ -150,8 +150,8 @@ open class FirFrontendFacade(
regularModules,
dependsOnModules,
friendModules,
module.targetPlatform,
module.targetPlatform.getAnalyzerServices()
mainModule.targetPlatform,
mainModule.targetPlatform.getAnalyzerServices()
)
moduleInfoProvider.registerModuleData(module, moduleData)
@@ -135,7 +135,7 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
val inferMainModule = JsEnvironmentConfigurationDirectives.INFER_MAIN_MODULE in testServices.moduleStructure.allDirectives
return when {
inferMainModule -> modules.last()
else -> modules.singleOrNull { it.name == ModuleStructureExtractor.DEFAULT_MODULE_NAME } ?: modules.single()
else -> modules.singleOrNull { it.name == ModuleStructureExtractor.DEFAULT_MODULE_NAME } ?: modules.last()
}
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.test.services.impl
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.platform.CommonPlatforms
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.js.JsPlatforms
@@ -351,13 +352,18 @@ class ModuleStructureExtractorImpl(
languageVersionSettings = currentModuleLanguageVersionSettingsBuilder.build()
)
modules += testModule
additionalSourceProviders.flatMapTo(filesOfCurrentModule) { additionalSourceProvider ->
additionalSourceProvider.produceAdditionalFiles(
globalDirectives ?: RegisteredDirectives.Empty,
testModule
).also { additionalFiles ->
require(additionalFiles.all { it.isAdditional }) {
"Files produced by ${additionalSourceProvider::class.qualifiedName} should have flag `isAdditional = true`"
if (testModule.frontendKind != FrontendKinds.FIR ||
!testModule.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects) ||
testModule.dependsOnDependencies.isEmpty()
) {
additionalSourceProviders.flatMapTo(filesOfCurrentModule) { additionalSourceProvider ->
additionalSourceProvider.produceAdditionalFiles(
globalDirectives ?: RegisteredDirectives.Empty,
testModule
).also { additionalFiles ->
require(additionalFiles.all { it.isAdditional }) {
"Files produced by ${additionalSourceProvider::class.qualifiedName} should have flag `isAdditional = true`"
}
}
}
}
@@ -5,11 +5,16 @@
package org.jetbrains.kotlin.js.test.converters
import org.jetbrains.kotlin.backend.common.actualizer.IrActualizer
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.JsFactories
import org.jetbrains.kotlin.ir.backend.js.jsResolveLibraries
import org.jetbrains.kotlin.ir.backend.js.resolverLogger
import org.jetbrains.kotlin.ir.backend.js.serializeModuleIntoKlib
import org.jetbrains.kotlin.ir.util.IrMessageLogger
import org.jetbrains.kotlin.js.test.utils.JsIrIncrementalDataProvider
import org.jetbrains.kotlin.js.test.utils.jsIrIncrementalDataProvider
@@ -23,6 +28,7 @@ import org.jetbrains.kotlin.test.frontend.fir.getAllJsDependenciesPaths
import org.jetbrains.kotlin.test.frontend.fir.resolveJsLibraries
import org.jetbrains.kotlin.test.model.ArtifactKinds
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
@@ -55,6 +61,10 @@ class FirJsKlibBackendFacade(
val libraries = resolveJsLibraries(module, testServices, configuration)
if (firstTimeCompilation) {
if (module.frontendKind == FrontendKinds.FIR && module.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects)) {
IrActualizer.actualize(inputArtifact.mainModuleFragment, inputArtifact.dependentModuleFragments)
}
serializeModuleIntoKlib(
configuration[CommonConfigurationKeys.MODULE_NAME]!!,
configuration,
@@ -23040,6 +23040,12 @@ public class FirJsCodegenBoxTestGenerated extends AbstractFirJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/multiplatform/multiModule/expectActualMemberLink.kt");
}
@Test
@TestMetadata("expectActualSimpleJs.kt")
public void testExpectActualSimpleJs() throws Exception {
runTest("compiler/testData/codegen/box/multiplatform/multiModule/expectActualSimpleJs.kt");
}
@Test
@TestMetadata("expectActualTypealiasLink.kt")
public void testExpectActualTypealiasLink() throws Exception {
@@ -23040,6 +23040,12 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/multiplatform/multiModule/expectActualMemberLink.kt");
}
@Test
@TestMetadata("expectActualSimpleJs.kt")
public void testExpectActualSimpleJs() throws Exception {
runTest("compiler/testData/codegen/box/multiplatform/multiModule/expectActualSimpleJs.kt");
}
@Test
@TestMetadata("expectActualTypealiasLink.kt")
public void testExpectActualTypealiasLink() throws Exception {
@@ -23040,6 +23040,12 @@ public class IrJsES6CodegenBoxTestGenerated extends AbstractIrJsES6CodegenBoxTes
runTest("compiler/testData/codegen/box/multiplatform/multiModule/expectActualMemberLink.kt");
}
@Test
@TestMetadata("expectActualSimpleJs.kt")
public void testExpectActualSimpleJs() throws Exception {
runTest("compiler/testData/codegen/box/multiplatform/multiModule/expectActualSimpleJs.kt");
}
@Test
@TestMetadata("expectActualTypealiasLink.kt")
public void testExpectActualTypealiasLink() throws Exception {