[JS IR] Enable DTS generation in IC

^KT-54398 Fixed
This commit is contained in:
Alexander Korepanov
2022-12-02 14:33:50 +01:00
committed by Space Team
parent d21cbfe02e
commit 9edaebf235
24 changed files with 231 additions and 18 deletions
@@ -95,10 +95,14 @@ internal fun File.fileHashForIC(): ICHash {
}
internal fun CompilerConfiguration.configHashForIC() = HashCalculatorForIC().apply {
val importantBooleanSettingKeys = listOf(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION)
updateForEach(importantBooleanSettingKeys) { key ->
val importantSettings = listOf(
JSConfigurationKeys.GENERATE_DTS,
JSConfigurationKeys.MODULE_KIND,
JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION
)
updateForEach(importantSettings) { key ->
update(key.toString())
update(getBoolean(key).toString())
update(get(key).toString())
}
update(languageVersionSettings.toString())
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.ir.backend.js.ic
import org.jetbrains.kotlin.ir.backend.js.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.export.TypeScriptFragment
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CrossModuleReferences
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrModuleHeader
import java.io.File
@@ -15,6 +16,7 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
private const val JS_MODULE_HEADER = "js.module.header.bin"
private const val CACHED_MODULE_JS = "module.js"
private const val CACHED_MODULE_JS_MAP = "module.js.map"
private const val CACHED_MODULE_D_TS = "module.d.ts"
}
private enum class NameType(val typeMask: Int) {
@@ -71,22 +73,26 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
}
}
private fun File.writeIfNotNull(data: String?) {
if (data != null) {
recreate()
writeText(data)
} else {
ifExists { delete() }
}
}
fun fetchCompiledJsCode(artifact: ModuleArtifact) = artifact.artifactsDir?.let { cacheDir ->
val jsCode = File(cacheDir, CACHED_MODULE_JS).ifExists { readText() }
val sourceMap = File(cacheDir, CACHED_MODULE_JS_MAP).ifExists { readText() }
jsCode?.let { CompilationOutputs(it, null, null, sourceMap) }
val tsDefinitions = File(cacheDir, CACHED_MODULE_D_TS).ifExists { TypeScriptFragment(readText()) }
jsCode?.let { CompilationOutputs(it, tsDefinitions, null, sourceMap) }
}
fun commitCompiledJsCode(artifact: ModuleArtifact, compilationOutputs: CompilationOutputs) = artifact.artifactsDir?.let { cacheDir ->
val jsCodeCache = File(cacheDir, CACHED_MODULE_JS).apply { recreate() }
jsCodeCache.writeText(compilationOutputs.jsCode)
val jsMapCache = File(cacheDir, CACHED_MODULE_JS_MAP)
if (compilationOutputs.sourceMap != null) {
jsMapCache.recreate()
jsMapCache.writeText(compilationOutputs.sourceMap)
} else {
jsMapCache.ifExists { delete() }
}
File(cacheDir, CACHED_MODULE_JS).writeIfNotNull(compilationOutputs.jsCode)
File(cacheDir, CACHED_MODULE_JS_MAP).writeIfNotNull(compilationOutputs.sourceMap)
File(cacheDir, CACHED_MODULE_D_TS).writeIfNotNull(compilationOutputs.tsDefinitions?.raw)
}
fun loadProgramHeadersFromCache(): List<CachedModuleInfo> {
@@ -47,7 +47,8 @@ class ModuleInfo(val moduleName: String) {
val id: Int,
val dependencies: Collection<Dependency>,
val modifications: List<Modification>,
val expectedFileStats: Map<String, Set<String>>
val expectedFileStats: Map<String, Set<String>>,
val expectedDTS: Set<String>
)
val steps = mutableListOf<ModuleStep>()
@@ -65,6 +66,7 @@ private const val FRIENDS = "friends"
private const val MODIFICATIONS = "modifications"
private const val MODIFICATION_UPDATE = "U"
private const val MODIFICATION_DELETE = "D"
private const val EXPECTED_DTS_LIST = "expected dts"
private val STEP_PATTERN = Pattern.compile("^\\s*STEP\\s+(\\d+)\\.*(\\d+)?\\s*:?$")
@@ -210,6 +212,7 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
val regularDependencies = mutableSetOf<String>()
val friendDependencies = mutableSetOf<String>()
val modifications = mutableListOf<ModuleInfo.Modification>()
val expectedDTS = mutableSetOf<String>()
loop { line ->
if (line.matches(STEP_PATTERN.toRegex()))
@@ -230,6 +233,7 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
DEPENDENCIES -> getOpArgs().forEach { regularDependencies += it }
FRIENDS -> getOpArgs().forEach { friendDependencies += it }
MODIFICATIONS -> modifications += parseModifications()
EXPECTED_DTS_LIST -> getOpArgs().forEach { expectedDTS += it }
else -> error(diagnosticMessage("Unknown op $op", line))
}
}
@@ -250,7 +254,8 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
id = it,
dependencies = dependencies,
modifications = modifications,
expectedFileStats = expectedFileStats
expectedFileStats = expectedFileStats,
expectedDTS = expectedDTS
)
}
}
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.test.builders.LanguageVersionSettingsBuilder
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.util.JUnit4Assertions
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import org.junit.ComparisonFailure
import java.io.File
import java.util.EnumSet
@@ -49,6 +50,8 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
private const val STDLIB_MODULE_NAME = "kotlin-kotlin-stdlib-js-ir"
private val KT_FILE_IGNORE_PATTERN = Regex("^.*\\..+\\.kt$")
private val JS_MODULE_KIND = ModuleKind.PLAIN
}
override fun createEnvironment(): KotlinCoreEnvironment {
@@ -94,7 +97,8 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
private fun createConfiguration(moduleName: String, language: List<String>): CompilerConfiguration {
val copy = environment.configuration.copy()
copy.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
copy.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
copy.put(JSConfigurationKeys.GENERATE_DTS, true)
copy.put(JSConfigurationKeys.MODULE_KIND, JS_MODULE_KIND)
copy.put(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION, true)
copy.languageVersionSettings = with(LanguageVersionSettingsBuilder()) {
@@ -123,7 +127,8 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
val moduleName: String,
val modulePath: String,
val friends: List<String>,
val expectedFileStats: Map<String, Set<String>>
val expectedFileStats: Map<String, Set<String>>,
val expectedDTS: String?
)
private fun setupTestStep(projStep: ProjectInfo.ProjectBuildStep, module: String, buildKlib: Boolean): TestStepInfo {
@@ -158,11 +163,16 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
val configuration = createConfiguration(module, projStep.language)
buildArtifact(configuration, module, moduleSourceDir, dependencies, friends, outputKlibFile)
}
val dtsFile = moduleStep.expectedDTS.ifNotEmpty {
moduleTestDir.resolve(singleOrNull() ?: error("$module module may generate only one d.ts at step $projStepId"))
}
return TestStepInfo(
module.safeModuleName,
outputKlibFile.canonicalPath,
friends.map { it.canonicalPath },
expectedFileStats
expectedFileStats,
dtsFile?.readText()
)
}
@@ -232,6 +242,17 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
}
}
private fun verifyDTS(stepId: Int, testInfo: List<TestStepInfo>, jsOutput: CompilationOutputs) {
for (info in testInfo) {
val expectedDTS = info.expectedDTS ?: continue
val output = jsOutput.dependencies.find { it.first == info.moduleName }?.second ?: jsOutput
val gotDTS = output.getFullTsDefinition(info.moduleName, JS_MODULE_KIND)
JUnit4Assertions.assertEquals(expectedDTS, gotDTS) {
"Mismatched d.ts for module ${info.moduleName} at step $stepId"
}
}
}
fun execute() {
for (projStep in projectInfo.steps) {
val testInfo = projStep.order.map { setupTestStep(projStep, it, true) }
@@ -272,6 +293,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
val (jsOutput, rebuiltModules) = jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true)
verifyJsExecutableProducerBuildModules(projStep.id, rebuiltModules, projStep.dirtyJS)
verifyJsCode(projStep.id, mainModuleName, jsOutput)
verifyDTS(projStep.id, testInfo, jsOutput)
}
}
}
@@ -330,6 +330,11 @@ public class InvalidationTestGenerated extends AbstractInvalidationTest {
runTest("js/js.translator/testData/incremental/invalidation/transitiveInlineFunction/");
}
@TestMetadata("typeScriptExports")
public void testTypeScriptExports() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/typeScriptExports/");
}
@TestMetadata("unicodeSerializationAndDeserialization")
public void testUnicodeSerializationAndDeserialization() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/unicodeSerializationAndDeserialization/");
@@ -0,0 +1 @@
fun foo() = 0
@@ -0,0 +1,2 @@
@JsExport
fun foo() = 0
@@ -0,0 +1,3 @@
@JsExport
@JsName("bar")
fun foo() = 0
@@ -0,0 +1,18 @@
STEP 0:
modifications:
U : l1.0.4.kt -> l1.kt
added file: l1.kt
STEP 1:
modifications:
U : l1.1.kt -> l1.kt
modified ir: l1.kt
STEP 2:
modifications:
U : l1.2.kt -> l1.kt
modified ir: l1.kt
STEP 3:
STEP 4:
modifications:
U : l1.0.4.kt -> l1.kt
modified ir: l1.kt
STEP 5..6:
@@ -0,0 +1,4 @@
class MyClass(val stepId: Int) {
fun qux() = foo() + stepId
}
@@ -0,0 +1,5 @@
@JsExport
class MyClass(val stepId: Int) {
fun qux() = foo() + stepId
}
@@ -0,0 +1,6 @@
@JsExport
class MyClass(val stepId: Int) {
@JsName("baz")
fun qux() = foo() + stepId
}
@@ -0,0 +1,6 @@
@JsExport
class MyClass(val stepId: Int) {
@JsName("bar")
fun qux() = foo() + stepId
}
@@ -0,0 +1,26 @@
STEP 0:
dependencies: lib1
modifications:
U : l2.0.kt -> l2.kt
added file: l2.kt
STEP 1..2:
dependencies: lib1
updated imports: l2.kt
STEP 3:
dependencies: lib1
modifications:
U : l2.3.kt -> l2.kt
modified ir: l2.kt
STEP 4:
dependencies: lib1
updated imports: l2.kt
STEP 5:
dependencies: lib1
modifications:
U : l2.5.kt -> l2.kt
modified ir: l2.kt
STEP 6:
dependencies: lib1
modifications:
U : l2.6.kt -> l2.kt
modified ir: l2.kt
@@ -0,0 +1,4 @@
declare namespace kotlin_main {
type Nullable<T> = T | null | undefined
function box(stepId: number): string;
}
@@ -0,0 +1,5 @@
declare namespace kotlin_main {
type Nullable<T> = T | null | undefined
function foo(): number;
function box(stepId: number): string;
}
@@ -0,0 +1,5 @@
declare namespace kotlin_main {
type Nullable<T> = T | null | undefined
function bar(): number;
function box(stepId: number): string;
}
@@ -0,0 +1,10 @@
declare namespace kotlin_main {
type Nullable<T> = T | null | undefined
function bar(): number;
class MyClass {
constructor(stepId: number);
get stepId(): number;
qux(): number;
}
function box(stepId: number): string;
}
@@ -0,0 +1,9 @@
declare namespace kotlin_main {
type Nullable<T> = T | null | undefined
class MyClass {
constructor(stepId: number);
get stepId(): number;
qux(): number;
}
function box(stepId: number): string;
}
@@ -0,0 +1,9 @@
declare namespace kotlin_main {
type Nullable<T> = T | null | undefined
class MyClass {
constructor(stepId: number);
get stepId(): number;
baz(): number;
}
function box(stepId: number): string;
}
@@ -0,0 +1,9 @@
declare namespace kotlin_main {
type Nullable<T> = T | null | undefined
class MyClass {
constructor(stepId: number);
get stepId(): number;
bar(): number;
}
function box(stepId: number): string;
}
@@ -0,0 +1,7 @@
fun box(stepId: Int): String {
val x = MyClass(stepId).qux()
if (x != stepId) {
return "Fail: $x != $stepId"
}
return "OK"
}
@@ -0,0 +1,25 @@
STEP 0:
dependencies: lib1, lib2
added file: m.kt
expected dts: m.0.d.ts
STEP 1:
dependencies: lib1, lib2
expected dts: m.1.d.ts
STEP 2:
dependencies: lib1, lib2
expected dts: m.2.d.ts
STEP 3:
dependencies: lib1, lib2
updated imports: m.kt
expected dts: m.3.d.ts
STEP 4:
dependencies: lib1, lib2
expected dts: m.4.d.ts
STEP 5:
dependencies: lib1, lib2
updated imports: m.kt
expected dts: m.5.d.ts
STEP 6:
dependencies: lib1, lib2
updated imports: m.kt
expected dts: m.6.d.ts
@@ -0,0 +1,17 @@
MODULES: lib1, lib2, main
STEP 0..1:
libs: lib1, lib2, main
dirty js: lib1, lib2, main
STEP 2:
libs: lib1, lib2, main
dirty js: lib1, lib2
STEP 3:
libs: lib1, lib2, main
dirty js: lib2, main
STEP 4:
libs: lib1, lib2, main
dirty js: lib1, lib2, main
STEP 5..6:
libs: lib1, lib2, main
dirty js: lib2, main