[K/JS] Add serialization/deserialization for JsImport/JsExport nodes

This commit is contained in:
Artem Kobzar
2023-04-13 12:58:46 +00:00
committed by Space Team
parent 85644bbbf6
commit 5dc6da2b33
19 changed files with 196 additions and 22 deletions
@@ -29,6 +29,19 @@ object StatementIds {
const val EMPTY = 17 const val EMPTY = 17
const val SINGLE_LINE_COMMENT = 18 const val SINGLE_LINE_COMMENT = 18
const val MULTI_LINE_COMMENT = 19 const val MULTI_LINE_COMMENT = 19
const val IMPORT = 20
const val EXPORT = 21
}
object ImportType {
const val ALL = 0
const val ITEMS = 1
const val DEFAULT = 2
}
object ExportType {
const val ALL = 0
const val ITEMS = 1
} }
object ExpressionIds { object ExpressionIds {
@@ -10,13 +10,9 @@ import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrIcClassModel
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.JsImportedModule
import org.jetbrains.kotlin.js.backend.ast.metadata.* import org.jetbrains.kotlin.js.backend.ast.metadata.*
import org.jetbrains.kotlin.js.backend.ast.metadata.LocalAlias
import org.jetbrains.kotlin.js.backend.ast.metadata.SpecialFunction
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.* import java.util.*
import java.util.ArrayDeque
fun deserializeJsIrProgramFragment(input: ByteArray): JsIrProgramFragment { fun deserializeJsIrProgramFragment(input: ByteArray): JsIrProgramFragment {
return JsIrAstDeserializer(input).readFragment() return JsIrAstDeserializer(input).readFragment()
@@ -219,6 +215,38 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
ifTrue { readBlock() } ifTrue { readBlock() }
) )
} }
EXPORT -> {
JsExport(
when (val type = readByte().toInt()) {
ExportType.ALL -> JsExport.Subject.All
ExportType.ITEMS -> JsExport.Subject.Elements(readList {
JsExport.Element(
nameTable[readInt()].makeRef(),
ifTrue { nameTable[readInt()] }
)
})
else -> error("Unknown JsExport type $type")
},
ifTrue { readString() }
)
}
IMPORT -> {
JsImport(
readString(),
when (val type = readByte().toInt()) {
ImportType.ALL -> JsImport.Target.All(nameTable[readInt()].makeRef())
ImportType.DEFAULT -> JsImport.Target.Default(nameTable[readInt()].makeRef())
ImportType.ITEMS -> JsImport.Target.Elements(readList {
JsImport.Element(
nameTable[readInt()],
ifTrue { nameTable[readInt()].makeRef() }
)
}.toMutableList())
else -> error("Unknown JsImport type $type")
}
)
}
EMPTY -> { EMPTY -> {
JsEmpty JsEmpty
} }
@@ -8,14 +8,12 @@ package org.jetbrains.kotlin.ir.backend.js.utils.serialization
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrIcClassModel import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrIcClassModel
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.JsImportedModule
import org.jetbrains.kotlin.js.backend.ast.metadata.* import org.jetbrains.kotlin.js.backend.ast.metadata.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.io.OutputStream import java.io.OutputStream
import java.util.* import java.util.*
import java.util.ArrayDeque
fun JsIrProgramFragment.serializeTo(output: OutputStream) { fun JsIrProgramFragment.serializeTo(output: OutputStream) {
JsIrAstSerializer().append(this).saveTo(output) JsIrAstSerializer().append(this).saveTo(output)
@@ -301,6 +299,46 @@ private class JsIrAstSerializer {
ifNotNull(x.finallyBlock) { writeBlock(it) } ifNotNull(x.finallyBlock) { writeBlock(it) }
} }
override fun visitExport(export: JsExport) {
writeByte(StatementIds.EXPORT)
when (val subject = export.subject) {
is JsExport.Subject.All -> writeByte(ExportType.ALL)
is JsExport.Subject.Elements -> {
writeByte(ExportType.ITEMS)
writeCollection(subject.elements) {
writeInt(internalizeName(it.name.name!!))
ifNotNull(it.alias) { writeInt(internalizeName(it)) }
}
}
}
ifNotNull(export.fromModule) { writeString(it) }
}
override fun visitImport(import: JsImport) {
writeByte(StatementIds.IMPORT)
writeString(import.module)
when (val target = import.target) {
is JsImport.Target.All -> {
writeByte(ImportType.ALL)
writeInt(internalizeName(target.alias.name!!))
}
is JsImport.Target.Default -> {
writeByte(ImportType.DEFAULT)
writeInt(internalizeName(target.name.name!!))
}
is JsImport.Target.Elements -> {
writeByte(ImportType.ITEMS)
writeCollection(target.elements) {
writeInt(internalizeName(it.name))
ifNotNull(it.alias) { writeInt(internalizeName(it.name!!)) }
}
}
}
}
override fun visitEmpty(x: JsEmpty) { override fun visitEmpty(x: JsEmpty) {
writeByte(StatementIds.EMPTY) writeByte(StatementIds.EMPTY)
} }
@@ -6,10 +6,11 @@
package org.jetbrains.kotlin.codegen package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.ir.backend.js.ic.DirtyFileState import org.jetbrains.kotlin.ir.backend.js.ic.DirtyFileState
import org.jetbrains.kotlin.serialization.js.ModuleKind
import java.io.File import java.io.File
import java.util.regex.Pattern import java.util.regex.Pattern
class ProjectInfo(val name: String, val modules: List<String>, val steps: List<ProjectBuildStep>, val muted: Boolean) { class ProjectInfo(val name: String, val modules: List<String>, val steps: List<ProjectBuildStep>, val muted: Boolean, val moduleKind: ModuleKind) {
class ProjectBuildStep(val id: Int, val order: List<String>, val dirtyJS: List<String>, val language: List<String>) class ProjectBuildStep(val id: Int, val order: List<String>, val dirtyJS: List<String>, val language: List<String>)
} }
@@ -57,6 +58,7 @@ class ModuleInfo(val moduleName: String) {
const val PROJECT_INFO_FILE = "project.info" const val PROJECT_INFO_FILE = "project.info"
private const val MODULES_LIST = "MODULES" private const val MODULES_LIST = "MODULES"
private const val MODULES_KIND = "MODULE_KIND"
private const val LIBS_LIST = "libs" private const val LIBS_LIST = "libs"
private const val DIRTY_JS_MODULES_LIST = "dirty js" private const val DIRTY_JS_MODULES_LIST = "dirty js"
private const val LANGUAGE = "language" private const val LANGUAGE = "language"
@@ -107,7 +109,13 @@ abstract class InfoParser<Info>(protected val infoFile: File) {
private fun String.splitAndTrim() = split(",").map { it.trim() }.filter { it.isNotBlank() } private fun String.splitAndTrim() = split(",").map { it.trim() }.filter { it.isNotBlank() }
class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) { class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
private val moduleKindMap = mapOf(
"plain" to ModuleKind.PLAIN,
"commonjs" to ModuleKind.COMMON_JS,
"amd" to ModuleKind.AMD,
"umd" to ModuleKind.UMD,
"es" to ModuleKind.ES,
)
private fun parseSteps(firstId: Int, lastId: Int): List<ProjectInfo.ProjectBuildStep> { private fun parseSteps(firstId: Int, lastId: Int): List<ProjectInfo.ProjectBuildStep> {
val order = mutableListOf<String>() val order = mutableListOf<String>()
@@ -145,6 +153,7 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
val libraries = mutableListOf<String>() val libraries = mutableListOf<String>()
val steps = mutableListOf<ProjectInfo.ProjectBuildStep>() val steps = mutableListOf<ProjectInfo.ProjectBuildStep>()
var muted = false var muted = false
var moduleKind = ModuleKind.COMMON_JS
loop { line -> loop { line ->
lineCounter++ lineCounter++
@@ -162,6 +171,9 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
when { when {
op == MODULES_LIST -> libraries += split[1].splitAndTrim() op == MODULES_LIST -> libraries += split[1].splitAndTrim()
op == MODULES_KIND -> moduleKind = split[1].trim()
.ifEmpty { error("Module kind value should be provided if MODULE_KIND pragma was specified") }
.let { moduleKindMap[it] ?: error("Unknown MODULE_KIND value '$it'") }
op.matches(STEP_PATTERN.toRegex()) -> { op.matches(STEP_PATTERN.toRegex()) -> {
val m = STEP_PATTERN.matcher(op) val m = STEP_PATTERN.matcher(op)
if (!m.matches()) throwSyntaxError(line) if (!m.matches()) throwSyntaxError(line)
@@ -188,7 +200,7 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
false false
} }
return ProjectInfo(entryName, libraries, steps, muted) return ProjectInfo(entryName, libraries, steps, muted, moduleKind)
} }
} }
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ic.* import org.jetbrains.kotlin.ir.backend.js.ic.*
import org.jetbrains.kotlin.ir.backend.js.jsPhases import org.jetbrains.kotlin.ir.backend.js.jsPhases
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.extension
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JSConfigurationKeys
@@ -58,8 +59,6 @@ abstract class AbstractInvalidationTest(private val targetBackend: TargetBackend
private val TEST_FILE_IGNORE_PATTERN = Regex("^.*\\..+\\.\\w\\w$") private val TEST_FILE_IGNORE_PATTERN = Regex("^.*\\..+\\.\\w\\w$")
private val JS_MODULE_KIND = ModuleKind.COMMON_JS
private const val SOURCE_MAPPING_URL_PREFIX = "//# sourceMappingURL=" private const val SOURCE_MAPPING_URL_PREFIX = "//# sourceMappingURL="
} }
@@ -114,11 +113,11 @@ abstract class AbstractInvalidationTest(private val targetBackend: TargetBackend
return File(File(buildDir, moduleName), "$moduleName.klib") return File(File(buildDir, moduleName), "$moduleName.klib")
} }
private fun createConfiguration(moduleName: String, language: List<String>): CompilerConfiguration { private fun createConfiguration(moduleName: String, language: List<String>, moduleKind: ModuleKind): CompilerConfiguration {
val copy = environment.configuration.copy() val copy = environment.configuration.copy()
copy.put(CommonConfigurationKeys.MODULE_NAME, moduleName) copy.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
copy.put(JSConfigurationKeys.GENERATE_DTS, true) copy.put(JSConfigurationKeys.GENERATE_DTS, true)
copy.put(JSConfigurationKeys.MODULE_KIND, JS_MODULE_KIND) copy.put(JSConfigurationKeys.MODULE_KIND, moduleKind)
copy.put(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION, true) copy.put(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION, true)
copy.put(JSConfigurationKeys.SOURCE_MAP, true) copy.put(JSConfigurationKeys.SOURCE_MAP, true)
@@ -185,7 +184,7 @@ abstract class AbstractInvalidationTest(private val targetBackend: TargetBackend
friends += klibFile friends += klibFile
} }
} }
val configuration = createConfiguration(module, projStep.language) val configuration = createConfiguration(module, projStep.language, projectInfo.moduleKind)
outputKlibFile.delete() outputKlibFile.delete()
buildKlib(configuration, module, moduleSourceDir, dependencies, friends, outputKlibFile) buildKlib(configuration, module, moduleSourceDir, dependencies, friends, outputKlibFile)
} }
@@ -241,7 +240,7 @@ abstract class AbstractInvalidationTest(private val targetBackend: TargetBackend
} }
private fun File.writeAsJsModule(jsCode: String, moduleName: String) { private fun File.writeAsJsModule(jsCode: String, moduleName: String) {
writeText(ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(jsCode, JS_MODULE_KIND, moduleName)) writeText(ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(jsCode, projectInfo.moduleKind, moduleName))
} }
private fun prepareExternalJsFiles(): MutableList<String> { private fun prepareExternalJsFiles(): MutableList<String> {
@@ -258,12 +257,13 @@ abstract class AbstractInvalidationTest(private val targetBackend: TargetBackend
try { try {
V8IrJsTestChecker.checkWithTestFunctionArgs( V8IrJsTestChecker.checkWithTestFunctionArgs(
files = jsFiles, files = jsFiles,
testModuleName = "./$mainModuleName.js", testModuleName = "./$mainModuleName${projectInfo.moduleKind.extension}",
testPackageName = null, testPackageName = null,
testFunctionName = BOX_FUNCTION_NAME, testFunctionName = BOX_FUNCTION_NAME,
testFunctionArgs = "$stepId", testFunctionArgs = "$stepId",
expectedResult = "OK", expectedResult = "OK",
withModuleSystem = true entryModulePath = jsFiles.last(),
withModuleSystem = projectInfo.moduleKind in setOf(ModuleKind.COMMON_JS, ModuleKind.UMD, ModuleKind.AMD)
) )
} catch (e: ComparisonFailure) { } catch (e: ComparisonFailure) {
throw ComparisonFailure("Mismatched box out at step $stepId", e.expected, e.actual) throw ComparisonFailure("Mismatched box out at step $stepId", e.expected, e.actual)
@@ -301,8 +301,8 @@ abstract class AbstractInvalidationTest(private val targetBackend: TargetBackend
} }
private fun writeJsCode(stepId: Int, mainModuleName: String, jsOutput: CompilationOutputs): List<String> { private fun writeJsCode(stepId: Int, mainModuleName: String, jsOutput: CompilationOutputs): List<String> {
val compiledJsFiles = jsOutput.writeAll(jsDir, mainModuleName, true, mainModuleName, JS_MODULE_KIND).filter { val compiledJsFiles = jsOutput.writeAll(jsDir, mainModuleName, true, mainModuleName, projectInfo.moduleKind).filter {
it.extension == "js" it.extension == "js" || it.extension == "mjs"
} }
for (jsCodeFile in compiledJsFiles) { for (jsCodeFile in compiledJsFiles) {
val sourceMappingUrlLine = jsCodeFile.readLines().singleOrNull { it.startsWith(SOURCE_MAPPING_URL_PREFIX) } val sourceMappingUrlLine = jsCodeFile.readLines().singleOrNull { it.startsWith(SOURCE_MAPPING_URL_PREFIX) }
@@ -325,7 +325,7 @@ abstract class AbstractInvalidationTest(private val targetBackend: TargetBackend
error("module ${it.moduleName} has friends, but only main module may have the friends") error("module ${it.moduleName} has friends, but only main module may have the friends")
} }
val configuration = createConfiguration(projStep.order.last(), projStep.language) val configuration = createConfiguration(projStep.order.last(), projStep.language, projectInfo.moduleKind)
val cacheUpdater = CacheUpdater( val cacheUpdater = CacheUpdater(
mainModule = mainModuleInfo.modulePath, mainModule = mainModuleInfo.modulePath,
allModules = testInfo.mapTo(mutableListOf(STDLIB_KLIB)) { it.modulePath }, allModules = testInfo.mapTo(mutableListOf(STDLIB_KLIB)) { it.modulePath },
@@ -79,9 +79,10 @@ abstract class AbstractJsTestChecker {
testFunctionName: String, testFunctionName: String,
testFunctionArgs: String, testFunctionArgs: String,
expectedResult: String, expectedResult: String,
withModuleSystem: Boolean withModuleSystem: Boolean,
entryModulePath: String? = null
) { ) {
val actualResult = run(files, testModuleName, testPackageName, testFunctionName, testFunctionArgs, withModuleSystem) val actualResult = run(files, testModuleName, testPackageName, testFunctionName, testFunctionArgs, withModuleSystem, entryModulePath)
Assert.assertEquals(expectedResult, actualResult.normalize()) Assert.assertEquals(expectedResult, actualResult.normalize())
} }
@@ -115,6 +115,12 @@ public class JsFirInvalidationTestGenerated extends AbstractJsFirInvalidationTes
runTest("js/js.translator/testData/incremental/invalidation/enumsInInlineFunctions/"); runTest("js/js.translator/testData/incremental/invalidation/enumsInInlineFunctions/");
} }
@Test
@TestMetadata("esModules")
public void testEsModules() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/esModules/");
}
@Test @Test
@TestMetadata("exceptionsFromInlineFunction") @TestMetadata("exceptionsFromInlineFunction")
public void testExceptionsFromInlineFunction() throws Exception { public void testExceptionsFromInlineFunction() throws Exception {
@@ -115,6 +115,12 @@ public class JsIrES6InvalidationTestGenerated extends AbstractJsIrES6Invalidatio
runTest("js/js.translator/testData/incremental/invalidation/enumsInInlineFunctions/"); runTest("js/js.translator/testData/incremental/invalidation/enumsInInlineFunctions/");
} }
@Test
@TestMetadata("esModules")
public void testEsModules() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/esModules/");
}
@Test @Test
@TestMetadata("exceptionsFromInlineFunction") @TestMetadata("exceptionsFromInlineFunction")
public void testExceptionsFromInlineFunction() throws Exception { public void testExceptionsFromInlineFunction() throws Exception {
@@ -115,6 +115,12 @@ public class JsIrInvalidationTestGenerated extends AbstractJsIrInvalidationTest
runTest("js/js.translator/testData/incremental/invalidation/enumsInInlineFunctions/"); runTest("js/js.translator/testData/incremental/invalidation/enumsInInlineFunctions/");
} }
@Test
@TestMetadata("esModules")
public void testEsModules() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/esModules/");
}
@Test @Test
@TestMetadata("exceptionsFromInlineFunction") @TestMetadata("exceptionsFromInlineFunction")
public void testExceptionsFromInlineFunction() throws Exception { public void testExceptionsFromInlineFunction() throws Exception {
@@ -0,0 +1,2 @@
data class Demo(val x: Int) {
}
@@ -0,0 +1,2 @@
class Demo(val x: Int) {
}
@@ -0,0 +1,2 @@
inline class Demo(val x: Int) {
}
@@ -0,0 +1,16 @@
STEP 0:
modifications:
U : l1.0_data_class.kt -> l1.kt
added file: l1.kt
STEP 1:
modifications:
U : l1.1_simple_class.kt -> l1.kt
modified ir: l1.kt
STEP 2:
modifications:
U : l1.2_inline_class.kt -> l1.kt
modified ir: l1.kt
STEP 3:
modifications:
U : l1.1_simple_class.kt -> l1.kt
modified ir: l1.kt
@@ -0,0 +1,18 @@
fun box(stepId: Int): String {
val d = Demo(15)
when (stepId) {
0, 2 -> {
if (!testEquals(d, Demo(15))) return "Fail equals"
if (testHashCode(d) != Demo(15).hashCode()) return "Fail hashCode"
if (testToString(d) != "Demo(x=15)") return "Fail toString"
}
1, 3-> {
if (testEquals(d, Demo(15))) return "Fail equals"
if (testHashCode(d) == Demo(15).hashCode()) return "Fail hashCode"
if (testToString(d) != "[object Object]") return "Fail toString"
}
else -> return "Unknown"
}
return "OK"
}
@@ -0,0 +1,6 @@
STEP 0:
dependencies: lib1
added file: m.kt, testEquals.kt, testHashCode.kt, testToString.kt
STEP 1..3:
dependencies: lib1
updated imports: m.kt, testEquals.kt, testHashCode.kt, testToString.kt
@@ -0,0 +1,3 @@
fun testEquals(lhs: Demo, rhs: Demo): Boolean {
return lhs == rhs
}
@@ -0,0 +1,3 @@
fun testHashCode(d: Demo): Int {
return d.hashCode()
}
@@ -0,0 +1,3 @@
fun testToString(d: Demo): String {
return d.toString()
}
@@ -0,0 +1,9 @@
MODULES: lib1, main
MODULE_KIND: es
STEP 0:
libs: lib1, main
dirty js: lib1, main
STEP 1..3:
libs: lib1, main
dirty js: lib1, main