JS IR: per-module .js generation support

This commit is contained in:
Anton Bannykh
2020-06-03 18:41:52 +03:00
parent deb5dc1057
commit da79f93c61
38 changed files with 1358 additions and 124 deletions
@@ -135,6 +135,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
) )
var irModuleName: String? by NullableStringFreezableVar(null) var irModuleName: String? by NullableStringFreezableVar(null)
@Argument(value = "-Xir-per-module", description = "Splits generated .js per-module")
var irPerModule: Boolean by FreezableVar(false)
@Argument( @Argument(
value = "-Xinclude", value = "-Xinclude",
valueDescription = "<path>", valueDescription = "<path>",
@@ -229,14 +229,19 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
mainArguments = mainCallArguments, mainArguments = mainCallArguments,
generateFullJs = !arguments.irDce, generateFullJs = !arguments.irDce,
generateDceJs = arguments.irDce, generateDceJs = arguments.irDce,
dceDriven = arguments.irDceDriven dceDriven = arguments.irDceDriven,
multiModule = arguments.irPerModule,
relativeRequirePath = true
) )
} catch (e: JsIrCompilationError) { } catch (e: JsIrCompilationError) {
return COMPILATION_ERROR return COMPILATION_ERROR
} }
val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!! val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!!
outputFile.writeText(jsCode) outputFile.writeText(jsCode.mainModule)
jsCode.dependencies.forEach { (name, content) ->
outputFile.resolveSibling("$name.js").writeText(content)
}
if (arguments.generateDts) { if (arguments.generateDts) {
val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!! val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!!
dtsFile.writeText(compiledModule.tsDefinitions ?: error("No ts definitions")) dtsFile.writeText(compiledModule.tsDefinitions ?: error("No ts definitions"))
@@ -8,10 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js
import org.jetbrains.kotlin.backend.common.ir.isMemberOfOpenClass import org.jetbrains.kotlin.backend.common.ir.isMemberOfOpenClass
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.export.isExported import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.associatedObject import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.isAssociatedObjectAnnotatedAnnotation
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
@@ -26,11 +23,10 @@ import java.util.*
fun eliminateDeadDeclarations( fun eliminateDeadDeclarations(
modules: Iterable<IrModuleFragment>, modules: Iterable<IrModuleFragment>,
context: JsIrBackendContext, context: JsIrBackendContext
mainFunction: IrSimpleFunction?
) { ) {
val allRoots = stageController.withInitialIr { buildRoots(modules, context, mainFunction) } val allRoots = stageController.withInitialIr { buildRoots(modules, context) }
val usefulDeclarations = usefulDeclarations(allRoots, context) val usefulDeclarations = usefulDeclarations(allRoots, context)
@@ -43,7 +39,7 @@ private fun IrField.isConstant(): Boolean {
return correspondingPropertySymbol?.owner?.isConst ?: false return correspondingPropertySymbol?.owner?.isConst ?: false
} }
private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackendContext, mainFunction: IrSimpleFunction?): Iterable<IrDeclaration> { private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackendContext): Iterable<IrDeclaration> {
val rootDeclarations = val rootDeclarations =
(modules.flatMap { it.files } + context.packageLevelJsModules + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file -> (modules.flatMap { it.files } + context.packageLevelJsModules + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file ->
file.declarations.flatMap { if (it is IrProperty) listOfNotNull(it.backingField, it.getter, it.setter) else listOf(it) } file.declarations.flatMap { if (it is IrProperty) listOfNotNull(it.backingField, it.getter, it.setter) else listOf(it) }
@@ -56,9 +52,9 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackend
}.filter { !(it is IrField && it.isConstant() && !it.isExported(context)) } }.filter { !(it is IrField && it.isConstant() && !it.isExported(context)) }
} }
if (context.hasTests) rootDeclarations += context.testContainer rootDeclarations += context.testRoots.values
if (mainFunction != null) { JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction ->
rootDeclarations += mainFunction rootDeclarations += mainFunction
if (mainFunction.isSuspend) { if (mainFunction.isSuspend) {
rootDeclarations += context.coroutineEmptyContinuation.owner rootDeclarations += context.coroutineEmptyContinuation.owner
@@ -92,8 +92,12 @@ class JsIrBackendContext(
private val internalPackageFragmentDescriptor = EmptyPackageFragmentDescriptor(builtIns.builtInsModule, FqName("kotlin.js.internal")) private val internalPackageFragmentDescriptor = EmptyPackageFragmentDescriptor(builtIns.builtInsModule, FqName("kotlin.js.internal"))
val implicitDeclarationFile = run { val implicitDeclarationFile = run {
IrFileImpl(object : SourceManager.FileEntry { syntheticFile("implicitDeclarations", irModuleFragment)
override val name = "<implicitDeclarations>" }
private fun syntheticFile(name: String, module: IrModuleFragment): IrFile {
return IrFileImpl(object : SourceManager.FileEntry {
override val name = "<$name>"
override val maxOffset = UNDEFINED_OFFSET override val maxOffset = UNDEFINED_OFFSET
override fun getSourceRangeInfo(beginOffset: Int, endOffset: Int) = override fun getSourceRangeInfo(beginOffset: Int, endOffset: Int) =
@@ -110,20 +114,24 @@ class JsIrBackendContext(
override fun getLineNumber(offset: Int) = UNDEFINED_OFFSET override fun getLineNumber(offset: Int) = UNDEFINED_OFFSET
override fun getColumnNumber(offset: Int) = UNDEFINED_OFFSET override fun getColumnNumber(offset: Int) = UNDEFINED_OFFSET
}, internalPackageFragmentDescriptor).also { }, internalPackageFragmentDescriptor).also {
irModuleFragment.files += it module.files += it
} }
} }
private var testContainerField: IrSimpleFunction? = null private val testContainerFuns = mutableMapOf<IrModuleFragment, IrSimpleFunction>()
val hasTests get() = testContainerField != null fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction {
return testContainerFuns.getOrPut(module) {
val testContainer: IrSimpleFunction val file = syntheticFile("tests", module)
get() = testContainerField ?: JsIrBuilder.buildFunction("test fun", irBuiltIns.unitType, implicitDeclarationFile).apply { JsIrBuilder.buildFunction("test fun", irBuiltIns.unitType, file).apply {
body = JsIrBuilder.buildBlockBody(emptyList()) body = JsIrBuilder.buildBlockBody(emptyList())
testContainerField = this file.declarations += this
implicitDeclarationFile.declarations += this }
} }
}
val testRoots: Map<IrModuleFragment, IrSimpleFunction>
get() = testContainerFuns
override val mapping = JsMapping() override val mapping = JsMapping()
override val declarationFactory = JsDeclarationFactory(mapping) override val declarationFactory = JsDeclarationFactory(mapping)
@@ -618,12 +618,6 @@ private val callsLoweringPhase = makeBodyLoweringPhase(
description = "Handle intrinsics" description = "Handle intrinsics"
) )
private val testGenerationPhase = makeJsModulePhase(
::TestGenerator,
name = "TestGenerationLowering",
description = "Generate invocations to kotlin.test suite and test functions"
).toModuleLowering()
private val staticMembersLoweringPhase = makeDeclarationTransformerPhase( private val staticMembersLoweringPhase = makeDeclarationTransformerPhase(
::StaticMembersLowering, ::StaticMembersLowering,
name = "StaticMembersLowering", name = "StaticMembersLowering",
@@ -657,7 +651,6 @@ private val cleanupLoweringPhase = makeBodyLoweringPhase(
val loweringList = listOf<Lowering>( val loweringList = listOf<Lowering>(
scriptRemoveReceiverLowering, scriptRemoveReceiverLowering,
validateIrBeforeLowering, validateIrBeforeLowering,
testGenerationPhase,
expectDeclarationsRemovingPhase, expectDeclarationsRemovingPhase,
stripTypeAliasDeclarationsPhase, stripTypeAliasDeclarationsPhase,
arrayConstructorPhase, arrayConstructorPhase,
@@ -14,12 +14,10 @@ import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.ir.backend.js.lower.generateTests import org.jetbrains.kotlin.ir.backend.js.lower.generateTests
import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.StageController import org.jetbrains.kotlin.ir.declarations.StageController
import org.jetbrains.kotlin.ir.declarations.stageController import org.jetbrains.kotlin.ir.declarations.stageController
import org.jetbrains.kotlin.ir.util.DeclarationStubGenerator
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.noUnboundLeft import org.jetbrains.kotlin.ir.util.noUnboundLeft
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
@@ -27,11 +25,13 @@ import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
class CompilerResult( class CompilerResult(
val jsCode: String?, val jsCode: JsCode?,
val dceJsCode: String?, val dceJsCode: JsCode?,
val tsDefinitions: String? = null val tsDefinitions: String? = null
) )
class JsCode(val mainModule: String, val dependencies: Iterable<Pair<String, String>> = emptyList())
fun compile( fun compile(
project: Project, project: Project,
mainModule: MainModule, mainModule: MainModule,
@@ -45,7 +45,9 @@ fun compile(
generateFullJs: Boolean = true, generateFullJs: Boolean = true,
generateDceJs: Boolean = false, generateDceJs: Boolean = false,
dceDriven: Boolean = false, dceDriven: Boolean = false,
es6mode: Boolean = false es6mode: Boolean = false,
multiModule: Boolean = false,
relativeRequirePath: Boolean = false
): CompilerResult { ): CompilerResult {
stageController = object : StageController {} stageController = object : StageController {}
@@ -54,19 +56,17 @@ fun compile(
val moduleDescriptor = moduleFragment.descriptor val moduleDescriptor = moduleFragment.descriptor
val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(moduleFragment)
val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, exportedDeclarations, configuration, es6mode = es6mode)
// Load declarations referenced during `context` initialization
val irProviders = listOf(deserializer)
ExternalDependenciesGenerator(symbolTable, irProviders, configuration.languageVersionSettings).generateUnboundSymbolsAsDependencies()
val allModules = when (mainModule) { val allModules = when (mainModule) {
is MainModule.SourceFiles -> dependencyModules + listOf(moduleFragment) is MainModule.SourceFiles -> dependencyModules + listOf(moduleFragment)
is MainModule.Klib -> dependencyModules is MainModule.Klib -> dependencyModules
} }
val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, allModules.first(), exportedDeclarations, configuration, es6mode = es6mode)
// Load declarations referenced during `context` initialization
val irProviders = listOf(deserializer)
ExternalDependenciesGenerator(symbolTable, irProviders, configuration.languageVersionSettings).generateUnboundSymbolsAsDependencies()
deserializer.postProcess() deserializer.postProcess()
symbolTable.noUnboundLeft("Unbound symbols at the end of linker") symbolTable.noUnboundLeft("Unbound symbols at the end of linker")
@@ -74,32 +74,42 @@ fun compile(
moveBodilessDeclarationsToSeparatePlace(context, module) moveBodilessDeclarationsToSeparatePlace(context, module)
} }
// TODO should be done incrementally
generateTests(context, allModules.last())
if (dceDriven) { if (dceDriven) {
// TODO we should only generate tests for the current module
// TODO should be done incrementally
allModules.forEach { module ->
generateTests(context, module)
}
val controller = MutableController(context, pirLowerings) val controller = MutableController(context, pirLowerings)
stageController = controller stageController = controller
controller.currentStage = controller.lowerings.size + 1 controller.currentStage = controller.lowerings.size + 1
eliminateDeadDeclarations(allModules, context, mainFunction) eliminateDeadDeclarations(allModules, context)
// TODO investigate whether this is needed anymore // TODO investigate whether this is needed anymore
stageController = object : StageController { stageController = object : StageController {
override val currentStage: Int = controller.currentStage override val currentStage: Int = controller.currentStage
} }
val transformer = IrModuleToJsTransformer(context, mainFunction, mainArguments) val transformer = IrModuleToJsTransformer(
return transformer.generateModule(allModules, fullJs = true, dceJs = false) context,
mainArguments,
fullJs = true,
dceJs = false,
multiModule = multiModule,
relativeRequirePath = relativeRequirePath
)
return transformer.generateModule(allModules)
} else { } else {
jsPhases.invokeToplevel(phaseConfig, context, allModules) jsPhases.invokeToplevel(phaseConfig, context, allModules)
val transformer = IrModuleToJsTransformer(context, mainFunction, mainArguments) val transformer = IrModuleToJsTransformer(
return transformer.generateModule(allModules, generateFullJs, generateDceJs) context,
mainArguments,
fullJs = generateFullJs,
dceJs = generateDceJs,
multiModule = multiModule,
relativeRequirePath = relativeRequirePath
)
return transformer.generateModule(allModules)
} }
} }
@@ -111,6 +121,6 @@ fun generateJsCode(
moveBodilessDeclarationsToSeparatePlace(context, moduleFragment) moveBodilessDeclarationsToSeparatePlace(context, moduleFragment)
jsPhases.invokeToplevel(PhaseConfig(jsPhases), context, listOf(moduleFragment)) jsPhases.invokeToplevel(PhaseConfig(jsPhases), context, listOf(moduleFragment))
val transformer = IrModuleToJsTransformer(context, null, null, true, nameTables) val transformer = IrModuleToJsTransformer(context, null, true, nameTables)
return transformer.generateModule(listOf(moduleFragment)).jsCode!! return transformer.generateModule(listOf(moduleFragment)).jsCode!!.mainModule
} }
@@ -8,11 +8,13 @@ package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.serialization.js.ModuleKind
sealed class ExportedDeclaration sealed class ExportedDeclaration
data class ExportedModule( data class ExportedModule(
val name: String, val name: String,
val moduleKind: ModuleKind,
val declarations: List<ExportedDeclaration> val declarations: List<ExportedDeclaration>
) )
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase
import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase.* import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase.*
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.isJsExport import org.jetbrains.kotlin.ir.backend.js.utils.isJsExport
@@ -22,11 +21,12 @@ import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
class ExportModelGenerator(val context: JsIrBackendContext) { class ExportModelGenerator(val context: JsIrBackendContext) {
private fun generateExport(file: IrPackageFragment): List<ExportedDeclaration> { fun generateExport(file: IrPackageFragment): List<ExportedDeclaration> {
val namespaceFqName = file.fqName val namespaceFqName = file.fqName
val exports = file.declarations.flatMap { declaration -> listOfNotNull(exportDeclaration(declaration)) } val exports = file.declarations.flatMap { declaration -> listOfNotNull(exportDeclaration(declaration)) }
return when { return when {
@@ -38,7 +38,8 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
fun generateExport(modules: Iterable<IrModuleFragment>): ExportedModule = fun generateExport(modules: Iterable<IrModuleFragment>): ExportedModule =
ExportedModule( ExportedModule(
sanitizeName(context.configuration[CommonConfigurationKeys.MODULE_NAME]!!), context.configuration[CommonConfigurationKeys.MODULE_NAME]!!,
context.configuration[JSConfigurationKeys.MODULE_KIND]!!,
(context.externalPackageFragment.values + modules.flatMap { it.files }).flatMap { (context.externalPackageFragment.values + modules.flatMap { it.files }).flatMap {
generateExport(it) generateExport(it)
} }
@@ -5,12 +5,14 @@
package org.jetbrains.kotlin.ir.backend.js.export package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
// TODO: Support module kinds other than plain // TODO: Support module kinds other than plain
fun ExportedModule.toTypeScript(): String { fun ExportedModule.toTypeScript(): String {
val prefix = " type Nullable<T> = T | null | undefined\n" val prefix = " type Nullable<T> = T | null | undefined\n"
val body = declarations.joinToString("\n") { it.toTypeScript(" ") } val body = declarations.joinToString("\n") { it.toTypeScript(" ") }
return "declare namespace $name {\n$prefix$body\n}\n" return "declare namespace ${sanitizeName(name)} {\n$prefix$body\n}\n"
} }
fun List<ExportedDeclaration>.toTypeScript(indent: String): String = fun List<ExportedDeclaration>.toTypeScript(indent: String): String =
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.classOrNull import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -28,7 +29,6 @@ import org.jetbrains.kotlin.name.Name
class PropertyReferenceLowering(private val context: JsIrBackendContext) : BodyLoweringPass { class PropertyReferenceLowering(private val context: JsIrBackendContext) : BodyLoweringPass {
private val implicitDeclarationFile = context.implicitDeclarationFile
private val referenceBuilderSymbol = context.kpropertyBuilder private val referenceBuilderSymbol = context.kpropertyBuilder
private val localDelegateBuilderSymbol = context.klocalDelegateBuilder private val localDelegateBuilderSymbol = context.klocalDelegateBuilder
private val jsClassSymbol = context.intrinsics.jsClass private val jsClassSymbol = context.intrinsics.jsClass
@@ -40,7 +40,11 @@ class PropertyReferenceLowering(private val context: JsIrBackendContext) : BodyL
override fun lower(irBody: IrBody, container: IrDeclaration) { override fun lower(irBody: IrBody, container: IrDeclaration) {
newDeclarations.clear() newDeclarations.clear()
irBody.transformChildrenVoid(PropertyReferenceTransformer()) irBody.transformChildrenVoid(PropertyReferenceTransformer())
implicitDeclarationFile.declarations.addAll(newDeclarations) if (!newDeclarations.isEmpty()) {
val file = container.file
newDeclarations.forEach { it.parent = file }
file.declarations.addAll(newDeclarations)
}
} }
private inner class PropertyReferenceTransformer : IrElementTransformerVoid() { private inner class PropertyReferenceTransformer : IrElementTransformerVoid() {
@@ -55,8 +59,6 @@ class PropertyReferenceLowering(private val context: JsIrBackendContext) : BodyL
name = Name.identifier("${property.name.asString()}\$factory") name = Name.identifier("${property.name.asString()}\$factory")
} }
factoryDeclaration.parent = implicitDeclarationFile
val boundArguments = listOfNotNull(reference.dispatchReceiver, reference.extensionReceiver) val boundArguments = listOfNotNull(reference.dispatchReceiver, reference.extensionReceiver)
val valueParameters = ArrayList<IrValueParameter>(boundArguments.size) val valueParameters = ArrayList<IrValueParameter>(boundArguments.size)
@@ -22,14 +22,14 @@ import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
fun generateTests(context: JsIrBackendContext, moduleFragment: IrModuleFragment) { fun generateTests(context: JsIrBackendContext, moduleFragment: IrModuleFragment) {
val generator = TestGenerator(context) val generator = TestGenerator(context) { context.createTestContainerFun(moduleFragment) }
moduleFragment.files.forEach { moduleFragment.files.toList().forEach {
generator.lower(it) generator.lower(it)
} }
} }
class TestGenerator(val context: JsIrBackendContext) : FileLoweringPass { class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: () -> IrSimpleFunction) : FileLoweringPass {
override fun lower(irFile: IrFile) { override fun lower(irFile: IrFile) {
irFile.declarations.forEach { irFile.declarations.forEach {
@@ -44,7 +44,7 @@ class TestGenerator(val context: JsIrBackendContext) : FileLoweringPass {
private val packageSuites = mutableMapOf<FqName, FunctionWithBody>() private val packageSuites = mutableMapOf<FqName, FunctionWithBody>()
private fun suiteForPackage(fqName: FqName) = packageSuites.getOrPut(fqName) { private fun suiteForPackage(fqName: FqName) = packageSuites.getOrPut(fqName) {
context.suiteFun!!.createInvocation(fqName.asString(), context.testContainer) context.suiteFun!!.createInvocation(fqName.asString(), testContainerFactory())
} }
private data class FunctionWithBody(val function: IrSimpleFunction, val body: IrBlockBody) private data class FunctionWithBody(val function: IrSimpleFunction, val body: IrBlockBody)
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.ir.backend.js.CompilerResult import org.jetbrains.kotlin.ir.backend.js.CompilerResult
import org.jetbrains.kotlin.ir.backend.js.JsCode
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.eliminateDeadDeclarations
import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator
@@ -26,16 +26,17 @@ import org.jetbrains.kotlin.utils.DFS
class IrModuleToJsTransformer( class IrModuleToJsTransformer(
private val backendContext: JsIrBackendContext, private val backendContext: JsIrBackendContext,
private val mainFunction: IrSimpleFunction?,
private val mainArguments: List<String>?, private val mainArguments: List<String>?,
private val generateScriptModule: Boolean = false, private val generateScriptModule: Boolean = false,
var namer: NameTables = NameTables(emptyList()) var namer: NameTables = NameTables(emptyList()),
private val fullJs: Boolean = true,
private val dceJs: Boolean = false,
private val multiModule: Boolean = false,
private val relativeRequirePath: Boolean = false
) { ) {
val moduleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!!
private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!!
private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS) private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS)
fun generateModule(modules: Iterable<IrModuleFragment>, fullJs: Boolean = true, dceJs: Boolean = false): CompilerResult { fun generateModule(modules: Iterable<IrModuleFragment>): CompilerResult {
val additionalPackages = with(backendContext) { val additionalPackages = with(backendContext) {
externalPackageFragment.values + listOf( externalPackageFragment.values + listOf(
bodilessBuiltInsPackageFragment, bodilessBuiltInsPackageFragment,
@@ -50,6 +51,10 @@ class IrModuleToJsTransformer(
module.files.forEach { StaticMembersLowering(backendContext).lower(it) } module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
} }
if (multiModule) {
breakCrossModuleFieldAccess(backendContext, modules)
}
modules.forEach { module -> modules.forEach { module ->
namer.merge(module.files, additionalPackages) namer.merge(module.files, additionalPackages)
} }
@@ -57,7 +62,7 @@ class IrModuleToJsTransformer(
val jsCode = if (fullJs) generateWrappedModuleBody(modules, exportedModule, namer) else null val jsCode = if (fullJs) generateWrappedModuleBody(modules, exportedModule, namer) else null
val dceJsCode = if (dceJs) { val dceJsCode = if (dceJs) {
eliminateDeadDeclarations(modules, backendContext, mainFunction) eliminateDeadDeclarations(modules, backendContext)
// Use a fresh namer for DCE so that we could compare the result with DCE-driven // Use a fresh namer for DCE so that we could compare the result with DCE-driven
// TODO: is this mode relevant for scripting? If yes, refactor so that the external name tables are used here when needed. // TODO: is this mode relevant for scripting? If yes, refactor so that the external name tables are used here when needed.
val namer = NameTables(emptyList()) val namer = NameTables(emptyList())
@@ -68,11 +73,63 @@ class IrModuleToJsTransformer(
return CompilerResult(jsCode, dceJsCode, dts) return CompilerResult(jsCode, dceJsCode, dts)
} }
private fun generateWrappedModuleBody(modules: Iterable<IrModuleFragment>, exportedModule: ExportedModule, namer: NameTables): String { private fun generateWrappedModuleBody(modules: Iterable<IrModuleFragment>, exportedModule: ExportedModule, namer: NameTables): JsCode {
val program = JsProgram() if (multiModule) {
val nameGenerator = IrNamerImpl( val refInfo = buildCrossModuleReferenceInfo(modules)
newNameTables = namer
val rM = modules.reversed()
val main = rM.first()
val others = rM.drop(1)
val mainModule = generateWrappedModuleBody2(
listOf(main),
others,
exportedModule,
namer,
refInfo
)
val dependencies = others.mapIndexed { index, module ->
val moduleName = sanitizeName(module.safeName)
val exportedDeclarations = ExportModelGenerator(backendContext).let { module.files.flatMap { file -> it.generateExport(file) } }
moduleName to generateWrappedModuleBody2(
listOf(module),
others.drop(index + 1),
ExportedModule(moduleName, exportedModule.moduleKind, exportedDeclarations),
namer,
refInfo
)
}.reversed()
return JsCode(mainModule, dependencies)
} else {
return JsCode(
generateWrappedModuleBody2(
modules,
emptyList(),
exportedModule,
namer,
EmptyCrossModuleReferenceInfo
)
)
}
}
private fun generateWrappedModuleBody2(
modules: Iterable<IrModuleFragment>,
dependencies: Iterable<IrModuleFragment>,
exportedModule: ExportedModule,
namer: NameTables,
refInfo: CrossModuleReferenceInfo
): String {
val nameGenerator = refInfo.withReferenceTracking(
IrNamerImpl(newNameTables = namer),
modules
) )
val staticContext = JsStaticContext( val staticContext = JsStaticContext(
backendContext = backendContext, backendContext = backendContext,
@@ -83,9 +140,6 @@ class IrModuleToJsTransformer(
staticContext = staticContext staticContext = staticContext
) )
val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function")
val internalModuleName = JsName("_")
val (importStatements, importedJsModules) = val (importStatements, importedJsModules) =
generateImportStatements( generateImportStatements(
getNameForExternalDeclaration = { rootContext.getNameForStaticDeclaration(it) }, getNameForExternalDeclaration = { rootContext.getNameForStaticDeclaration(it) },
@@ -93,40 +147,96 @@ class IrModuleToJsTransformer(
) )
val moduleBody = generateModuleBody(modules, rootContext) val moduleBody = generateModuleBody(modules, rootContext)
val exportStatements = ExportModelToJsStatements(internalModuleName, namer)
.generateModuleExport(exportedModule)
val internalModuleName = JsName("_")
val exportStatements = ExportModelToJsStatements(internalModuleName, namer).generateModuleExport(exportedModule)
val callToMain = generateCallToMain(modules, rootContext)
val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(nameGenerator, modules, dependencies, { JsName(sanitizeName(it)) })
val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName)
val program = JsProgram()
if (generateScriptModule) { if (generateScriptModule) {
with(program.globalBlock) { with(program.globalBlock) {
statements.addWithComment("block: imports", importStatements) statements.addWithComment("block: imports", importStatements + crossModuleImports)
statements += moduleBody statements += moduleBody
statements.addWithComment("block: exports", exportStatements) statements.addWithComment("block: exports", exportStatements + crossModuleExports)
} }
} else { } else {
with(rootFunction) { val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply {
parameters += JsParameter(internalModuleName) parameters += JsParameter(internalModuleName)
parameters += importedJsModules.map { JsParameter(it.internalName) } parameters += (importedJsModules + importedKotlinModules).map { JsParameter(it.internalName) }
with(body) { with(body) {
statements.addWithComment("block: imports", importStatements) statements.addWithComment("block: imports", importStatements + crossModuleImports)
statements += moduleBody statements += moduleBody
statements.addWithComment("block: exports", exportStatements) statements.addWithComment("block: exports", exportStatements + crossModuleExports)
statements += generateCallToMain(rootContext) statements += callToMain
statements += JsReturn(internalModuleName.makeRef()) statements += JsReturn(internalModuleName.makeRef())
} }
} }
program.globalBlock.statements += ModuleWrapperTranslation.wrap( program.globalBlock.statements += ModuleWrapperTranslation.wrap(
moduleName, exportedModule.name,
rootFunction, rootFunction,
importedJsModules, importedJsModules + importedKotlinModules,
program, program,
kind = moduleKind kind = exportedModule.moduleKind
) )
} }
return program.toString() return program.toString()
} }
private fun generateCrossModuleImports(
namerWithImports: IrNamerWithImports,
currentModules: Iterable<IrModuleFragment>,
allowedDependencies: Iterable<IrModuleFragment>,
declareFreshGlobal: (String) -> JsName
): Pair<MutableList<JsStatement>, List<JsImportedModule>> {
val imports = mutableListOf<JsStatement>()
val modules = mutableListOf<JsImportedModule>()
namerWithImports.imports().forEach { (module, names) ->
check(module in allowedDependencies) {
val deps = if (names.size > 10) "[${names.take(10).joinToString()}, ...]" else "$names"
"Module ${currentModules.map { it.name.asString() }} depend on module ${module.name.asString()} via $deps"
}
val moduleName = declareFreshGlobal(module.safeName)
modules += JsImportedModule(moduleName.ident, moduleName, moduleName.makeRef(), relativeRequirePath)
names.forEach {
imports += JsVars(JsVars.JsVar(JsName(it), JsNameRef(it, JsNameRef("\$crossModule\$", moduleName.makeRef()))))
}
}
return imports to modules
}
private fun generateCrossModuleExports(
modules: Iterable<IrModuleFragment>,
refInfo: CrossModuleReferenceInfo,
internalModuleName: JsName
): List<JsStatement> {
return modules.flatMap {
refInfo.exports(it).map {
jsAssignment(
JsNameRef(it, JsNameRef("\$crossModule\$", internalModuleName.makeRef())),
JsNameRef(it)
).makeStmt()
}
}.let {
if (!it.isEmpty()) {
val createExportBlock = jsAssignment(
JsNameRef("\$crossModule\$", internalModuleName.makeRef()),
JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral())
).makeStmt()
return listOf(createExportBlock) + it
} else it
}
}
private fun generateModuleBody(modules: Iterable<IrModuleFragment>, context: JsGenerationContext): List<JsStatement> { private fun generateModuleBody(modules: Iterable<IrModuleFragment>, context: JsGenerationContext): List<JsStatement> {
val statements = mutableListOf<JsStatement>().also { val statements = mutableListOf<JsStatement>().also {
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt() if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
@@ -176,10 +286,12 @@ class IrModuleToJsTransformer(
statements.addWithComment("block: post-declaration", postDeclarationBlock.statements) statements.addWithComment("block: post-declaration", postDeclarationBlock.statements)
statements.addWithComment("block: init", context.staticContext.initializerBlock.statements) statements.addWithComment("block: init", context.staticContext.initializerBlock.statements)
if (backendContext.hasTests) { modules.forEach {
statements.startRegion("block: tests") backendContext.testRoots[it]?.let { testContainer ->
statements += JsInvocation(context.getNameForStaticFunction(backendContext.testContainer).makeRef()).makeStmt() statements.startRegion("block: tests")
statements.endRegion() statements += JsInvocation(context.getNameForStaticFunction(testContainer).makeRef()).makeStmt()
statements.endRegion()
}
} }
return statements return statements
@@ -198,8 +310,9 @@ class IrModuleToJsTransformer(
return listOfNotNull(mainArgumentsArray, continuation) return listOfNotNull(mainArgumentsArray, continuation)
} }
private fun generateCallToMain(rootContext: JsGenerationContext): List<JsStatement> { private fun generateCallToMain(modules: Iterable<IrModuleFragment>, rootContext: JsGenerationContext): List<JsStatement> {
if (mainArguments == null) return emptyList() // in case `NO_MAIN` and `main(..)` exists if (mainArguments == null) return emptyList() // in case `NO_MAIN` and `main(..)` exists
val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(modules.last())
return mainFunction?.let { return mainFunction?.let {
val jsName = rootContext.getNameForStaticFunction(it) val jsName = rootContext.getNameForStaticFunction(it)
listOf(JsInvocation(jsName.makeRef(), generateMainArguments(it, rootContext)).makeStmt()) listOf(JsInvocation(jsName.makeRef(), generateMainArguments(it, rootContext)).makeStmt())
@@ -77,7 +77,7 @@ object ModuleWrapperTranslation {
val scope = program.scope val scope = program.scope
val defineName = scope.declareName("define") val defineName = scope.declareName("define")
val invocationArgs = listOf( val invocationArgs = listOf(
JsArrayLiteral(listOf(JsStringLiteral("exports")) + importedModules.map { JsStringLiteral(it.externalName) }), JsArrayLiteral(listOf(JsStringLiteral("exports")) + importedModules.map { JsStringLiteral(it.requireName) }),
function function
) )
@@ -94,7 +94,7 @@ object ModuleWrapperTranslation {
val moduleName = scope.declareName("module") val moduleName = scope.declareName("module")
val requireName = scope.declareName("require") val requireName = scope.declareName("require")
val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), JsStringLiteral(it.externalName)) } val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), JsStringLiteral(it.requireName)) }
val invocation = JsInvocation(function, listOf(JsNameRef("exports", moduleName.makeRef())) + invocationArgs) val invocation = JsInvocation(function, listOf(JsNameRef("exports", moduleName.makeRef())) + invocationArgs)
return listOf(invocation.makeStmt()) return listOf(invocation.makeStmt())
} }
@@ -0,0 +1,217 @@
/*
* 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.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.IrNamer
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrGetField
import org.jetbrains.kotlin.ir.expressions.IrSetField
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.util.constructedClass
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.name.Name
interface CrossModuleReferenceInfo {
fun exports(module: IrModuleFragment): List<String>
fun withReferenceTracking(namer: IrNamer, excludedModules: Iterable<IrModuleFragment>): IrNamerWithImports
}
interface IrNamerWithImports : IrNamer {
fun imports(): List<Pair<IrModuleFragment, List<String>>>
}
object EmptyCrossModuleReferenceInfo : CrossModuleReferenceInfo {
override fun exports(module: IrModuleFragment): List<String> = emptyList()
override fun withReferenceTracking(namer: IrNamer, excludedModules: Iterable<IrModuleFragment>): IrNamerWithImports =
object : IrNamerWithImports, IrNamer by namer {
override fun imports() = emptyList<Pair<IrModuleFragment, List<String>>>()
}
}
private class CrossModuleReferenceInfoImpl(val topLevelDeclarationToModule: Map<IrDeclaration, IrModuleFragment>) :
CrossModuleReferenceInfo {
private val exportedNames: MutableMap<IrModuleFragment, MutableSet<String>> = mutableMapOf()
override fun exports(module: IrModuleFragment): List<String> {
return exportedNames[module]?.toList() ?: emptyList()
}
override fun withReferenceTracking(namer: IrNamer, excludedModules: Iterable<IrModuleFragment>): IrNamerWithImports {
val excludedModulesSet = excludedModules.toSet()
return object : IrNamerWithImports, IrNamer by namer {
override fun imports(): List<Pair<IrModuleFragment, List<String>>> {
return importedNames.entries.map { (module, names) -> module to names.toList() }
}
private val importedNames: MutableMap<IrModuleFragment, MutableSet<String>> = mutableMapOf()
private fun JsName.track(d: IrDeclaration): JsName {
topLevelDeclarationToModule[d]?.let { module ->
if (module !in excludedModulesSet) {
importedNames.getOrPut(module) { mutableSetOf() } += this.ident
exportedNames.getOrPut(module) { mutableSetOf() } += this.ident
}
}
return this
}
override fun getNameForConstructor(constructor: IrConstructor): JsName {
return namer.getNameForConstructor(constructor).track(constructor.constructedClass)
}
override fun getNameForField(field: IrField): JsName {
return namer.getNameForField(field).track(field)
}
override fun getNameForClass(klass: IrClass): JsName {
return namer.getNameForClass(klass).track(klass)
}
override fun getNameForStaticFunction(function: IrSimpleFunction): JsName {
return namer.getNameForStaticFunction(function).track(function)
}
override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName {
return namer.getNameForStaticDeclaration(declaration).track(declaration)
}
override fun getNameForProperty(property: IrProperty): JsName {
return namer.getNameForProperty(property).track(property)
}
}
}
}
fun buildCrossModuleReferenceInfo(modules: Iterable<IrModuleFragment>): CrossModuleReferenceInfo {
val map = mutableMapOf<IrDeclaration, IrModuleFragment>()
modules.forEach { module ->
module.files.forEach { file ->
file.declarations.forEach { declaration ->
map[declaration] = module
}
}
}
return CrossModuleReferenceInfoImpl(map)
}
@OptIn(ObsoleteDescriptorBasedAPI::class)
fun breakCrossModuleFieldAccess(
context: JsIrBackendContext,
modules: Iterable<IrModuleFragment>
) {
val fieldToGetter = mutableMapOf<IrField, IrSimpleFunction>()
fun IrField.getter(): IrSimpleFunction {
return fieldToGetter.getOrPut(this) {
val fieldName = name
val getter = buildFun {
name = Name.identifier("get-$fieldName")
returnType = type
}
getter.body = IrBlockBodyImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(
IrReturnImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, getter.symbol,
IrGetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, type)
)
)
)
getter.parent = parent
(parent as IrDeclarationContainer).declarations += getter
getter
}
}
val fieldToSetter = mutableMapOf<IrField, IrSimpleFunction>()
fun IrField.setter(): IrSimpleFunction {
return fieldToSetter.getOrPut(this) {
val fieldName = name
val setter = buildFun {
name = Name.identifier("set-$fieldName")
returnType = context.irBuiltIns.unitType
}
val param = setter.addValueParameter("value", type)
setter.body = IrBlockBodyImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(
IrSetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, type).apply {
value = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, param.symbol)
}
)
)
setter.parent = parent
(parent as IrDeclarationContainer).declarations += setter
setter
}
}
modules.reversed().forEach { module ->
val moduleFields = module.files.flatMap { it.declarations.filterIsInstance<IrField>() }.toSet()
fun IrField.transformAccess(fn: IrField.() -> IrCall): IrCall? {
if (parent !is IrPackageFragment || isEffectivelyExternal() || this in moduleFields) return null
return fn()
}
module.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitGetField(expression: IrGetField): IrExpression {
expression.transformChildrenVoid(this)
return expression.symbol.owner.transformAccess {
val getter = getter()
IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, getter.returnType, getter.symbol)
} ?: expression
}
override fun visitSetField(expression: IrSetField): IrExpression {
expression.transformChildrenVoid(this)
return expression.symbol.owner.transformAccess {
val setter = setter()
IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, setter.returnType, setter.symbol).apply {
putValueArgument(0, expression.value)
}
} ?: expression
}
})
}
}
val IrModuleFragment.safeName: String
get() {
var result = name.asString()
if (result.startsWith('<')) result = result.substring(1)
if (result.endsWith('>')) result = result.substring(0, result.length - 1)
return sanitizeName("kotlin-$result")
}
+1
View File
@@ -11,6 +11,7 @@ where advanced options include:
Print declarations' reachability info to stdout during performing DCE Print declarations' reachability info to stdout during performing DCE
-Xir-module-name=<name> Specify a compilation module name for IR backend -Xir-module-name=<name> Specify a compilation module name for IR backend
-Xir-only Disables pre-IR backend -Xir-only Disables pre-IR backend
-Xir-per-module Splits generated .js per-module
-Xir-produce-js Generates JS file using IR backend. Also disables pre-IR backend -Xir-produce-js Generates JS file using IR backend. Also disables pre-IR backend
-Xir-produce-klib-dir Generate unpacked KLIB into parent directory of output JS file. -Xir-produce-klib-dir Generate unpacked KLIB into parent directory of output JS file.
In combination with -meta-info generates both IR and pre-IR versions of library. In combination with -meta-info generates both IR and pre-IR versions of library.
@@ -16,8 +16,17 @@
package org.jetbrains.kotlin.js.backend.ast package org.jetbrains.kotlin.js.backend.ast
class JsImportedModule(val externalName: String, var internalName: JsName, val plainReference: JsExpression?) {
class JsImportedModule @JvmOverloads constructor(
val externalName: String,
var internalName: JsName,
val plainReference: JsExpression?,
val relativeRequirePath: Boolean = false
) {
val key = JsImportedModuleKey(externalName, plainReference?.toString()) val key = JsImportedModuleKey(externalName, plainReference?.toString())
} }
val JsImportedModule.requireName: String
get() = if (relativeRequirePath) "./$externalName.js" else externalName
data class JsImportedModuleKey(val baseName: String, val plainName: String?) data class JsImportedModuleKey(val baseName: String, val plainName: String?)
@@ -129,6 +129,7 @@ abstract class BasicBoxTest(
val expectActualLinker = EXPECT_ACTUAL_LINKER.matcher(fileContent).find() val expectActualLinker = EXPECT_ACTUAL_LINKER.matcher(fileContent).find()
val skipDceDriven = SKIP_DCE_DRIVEN.matcher(fileContent).find() val skipDceDriven = SKIP_DCE_DRIVEN.matcher(fileContent).find()
val splitPerModule = SPLIT_PER_MODULE.matcher(fileContent).find()
TestFileFactoryImpl(coroutinesPackage).use { testFactory -> TestFileFactoryImpl(coroutinesPackage).use { testFactory ->
val inputFiles = TestFiles.createTestFiles( val inputFiles = TestFiles.createTestFiles(
@@ -173,7 +174,7 @@ abstract class BasicBoxTest(
testFactory.tmpDir, testFactory.tmpDir,
file.parent, module, outputFileName, dceOutputFileName, pirOutputFileName, dependencies, allDependencies, friends, modules.size > 1, file.parent, module, outputFileName, dceOutputFileName, pirOutputFileName, dependencies, allDependencies, friends, modules.size > 1,
!SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), outputPrefixFile, outputPostfixFile, !SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), outputPrefixFile, outputPostfixFile,
actualMainCallParameters, testPackage, testFunction, needsFullIrRuntime, isMainModule, expectActualLinker, skipDceDriven actualMainCallParameters, testPackage, testFunction, needsFullIrRuntime, isMainModule, expectActualLinker, skipDceDriven, splitPerModule
) )
when { when {
@@ -395,7 +396,8 @@ abstract class BasicBoxTest(
needsFullIrRuntime: Boolean, needsFullIrRuntime: Boolean,
isMainModule: Boolean, isMainModule: Boolean,
expectActualLinker: Boolean, expectActualLinker: Boolean,
skipDceDriven: Boolean skipDceDriven: Boolean,
splitPerModule: Boolean
) { ) {
val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") } val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") }
val testFiles = kotlinFiles.map { it.fileName } val testFiles = kotlinFiles.map { it.fileName }
@@ -419,7 +421,7 @@ abstract class BasicBoxTest(
val incrementalData = IncrementalData() val incrementalData = IncrementalData()
translateFiles( translateFiles(
psiFiles.map(TranslationUnit::SourceFile), outputFile, dceOutputFile, pirOutputFile, config, outputPrefixFile, outputPostfixFile, psiFiles.map(TranslationUnit::SourceFile), outputFile, dceOutputFile, pirOutputFile, config, outputPrefixFile, outputPostfixFile,
mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, isMainModule, skipDceDriven mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, isMainModule, skipDceDriven, splitPerModule
) )
if (incrementalCompilationChecksEnabled && module.hasFilesToRecompile) { if (incrementalCompilationChecksEnabled && module.hasFilesToRecompile) {
@@ -471,7 +473,7 @@ abstract class BasicBoxTest(
translateFiles( translateFiles(
translationUnits, recompiledOutputFile, recompiledOutputFile, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, translationUnits, recompiledOutputFile, recompiledOutputFile, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile,
mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, false, true mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, false, true, false
) )
val originalOutput = FileUtil.loadFile(outputFile) val originalOutput = FileUtil.loadFile(outputFile)
@@ -544,7 +546,8 @@ abstract class BasicBoxTest(
testFunction: String, testFunction: String,
needsFullIrRuntime: Boolean, needsFullIrRuntime: Boolean,
isMainModule: Boolean, isMainModule: Boolean,
skipDceDriven: Boolean skipDceDriven: Boolean,
splitPerModule: Boolean
) { ) {
val translator = K2JSTranslator(config, false) val translator = K2JSTranslator(config, false)
val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters) val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters)
@@ -929,6 +932,7 @@ abstract class BasicBoxTest(
private val KJS_WITH_FULL_RUNTIME = Pattern.compile("^// *KJS_WITH_FULL_RUNTIME *\$", Pattern.MULTILINE) private val KJS_WITH_FULL_RUNTIME = Pattern.compile("^// *KJS_WITH_FULL_RUNTIME *\$", Pattern.MULTILINE)
private val EXPECT_ACTUAL_LINKER = Pattern.compile("^// EXPECT_ACTUAL_LINKER *$", Pattern.MULTILINE) private val EXPECT_ACTUAL_LINKER = Pattern.compile("^// EXPECT_ACTUAL_LINKER *$", Pattern.MULTILINE)
private val SKIP_DCE_DRIVEN = Pattern.compile("^// *SKIP_DCE_DRIVEN *$", Pattern.MULTILINE) private val SKIP_DCE_DRIVEN = Pattern.compile("^// *SKIP_DCE_DRIVEN *$", Pattern.MULTILINE)
private val SPLIT_PER_MODULE = Pattern.compile("^// *SPLIT_PER_MODULE *$", Pattern.MULTILINE)
@JvmStatic @JvmStatic
protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn") protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn")
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.js.facade.MainCallParameters
import org.jetbrains.kotlin.js.facade.TranslationUnit import org.jetbrains.kotlin.js.facade.TranslationUnit
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parsing.parseBoolean import org.jetbrains.kotlin.parsing.parseBoolean
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull
import java.io.File import java.io.File
@@ -56,6 +57,8 @@ abstract class BasicIrBoxTest(
val runEs6Mode: Boolean = getBoolean("kotlin.js.ir.es6", false) val runEs6Mode: Boolean = getBoolean("kotlin.js.ir.es6", false)
val perModule: Boolean = getBoolean("kotlin.js.ir.perModule")
private val osName: String = System.getProperty("os.name").toLowerCase() private val osName: String = System.getProperty("os.name").toLowerCase()
// TODO Design incremental compilation for IR and add test support // TODO Design incremental compilation for IR and add test support
@@ -63,8 +66,11 @@ abstract class BasicIrBoxTest(
private val compilationCache = mutableMapOf<String, String>() private val compilationCache = mutableMapOf<String, String>()
private val cachedDependencies = mutableMapOf<String, Collection<String>>()
override fun doTest(filePath: String, expectedResult: String, mainCallParameters: MainCallParameters, coroutinesPackage: String) { override fun doTest(filePath: String, expectedResult: String, mainCallParameters: MainCallParameters, coroutinesPackage: String) {
compilationCache.clear() compilationCache.clear()
cachedDependencies.clear()
super.doTest(filePath, expectedResult, mainCallParameters, coroutinesPackage) super.doTest(filePath, expectedResult, mainCallParameters, coroutinesPackage)
} }
@@ -86,7 +92,8 @@ abstract class BasicIrBoxTest(
testFunction: String, testFunction: String,
needsFullIrRuntime: Boolean, needsFullIrRuntime: Boolean,
isMainModule: Boolean, isMainModule: Boolean,
skipDceDriven: Boolean skipDceDriven: Boolean,
splitPerModule: Boolean
) { ) {
val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file } val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file }
@@ -135,18 +142,13 @@ abstract class BasicIrBoxTest(
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
generateFullJs = true, generateFullJs = true,
generateDceJs = runIrDce, generateDceJs = runIrDce,
es6mode = runEs6Mode es6mode = runEs6Mode,
multiModule = splitPerModule || perModule
) )
val wrappedCode = compiledModule.jsCode!!.writeTo(outputFile, config)
wrapWithModuleEmulationMarkers(compiledModule.jsCode!!, moduleId = config.moduleId, moduleKind = config.moduleKind)
outputFile.write(wrappedCode)
compiledModule.dceJsCode?.let { dceJsCode -> compiledModule.dceJsCode?.writeTo(dceOutputFile, config)
val dceWrappedCode =
wrapWithModuleEmulationMarkers(dceJsCode, moduleId = config.moduleId, moduleKind = config.moduleKind)
dceOutputFile.write(dceWrappedCode)
}
if (generateDts) { if (generateDts) {
val dtsFile = outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts") val dtsFile = outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts")
@@ -166,11 +168,9 @@ abstract class BasicIrBoxTest(
mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null }, mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null },
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
dceDriven = true, dceDriven = true,
es6mode = runEs6Mode es6mode = runEs6Mode,
).jsCode!!.let { pirCode -> multiModule = splitPerModule || perModule
val pirWrappedCode = wrapWithModuleEmulationMarkers(pirCode, moduleId = config.moduleId, moduleKind = config.moduleKind) ).jsCode!!.writeTo(pirOutputFile, config)
pirOutputFile.write(pirWrappedCode)
}
} }
} else { } else {
generateKLib( generateKLib(
@@ -188,6 +188,23 @@ abstract class BasicIrBoxTest(
} }
} }
private fun JsCode.writeTo(outputFile: File, config: JsConfig) {
val wrappedCode =
wrapWithModuleEmulationMarkers(mainModule, moduleId = config.moduleId, moduleKind = config.moduleKind)
outputFile.write(wrappedCode)
val dependencyPaths = mutableListOf<String>()
dependencies.forEach { (moduleId, code) ->
val wrappedCode = wrapWithModuleEmulationMarkers(code, config.moduleKind, moduleId)
val dependencyPath = outputFile.absolutePath.replace("_v5.js", "-${moduleId}_v5.js")
dependencyPaths += dependencyPath
File(dependencyPath).write(wrappedCode)
}
cachedDependencies[outputFile.absolutePath] = dependencyPaths
}
override fun dontRunOnSpecificPlatform(targetBackend: TargetBackend): Boolean { override fun dontRunOnSpecificPlatform(targetBackend: TargetBackend): Boolean {
if (targetBackend != TargetBackend.JS_IR_ES6) return false if (targetBackend != TargetBackend.JS_IR_ES6) return false
if (!runEs6Mode) return false if (!runEs6Mode) return false
@@ -210,7 +227,8 @@ abstract class BasicIrBoxTest(
// TODO: should we do anything special for module systems? // TODO: should we do anything special for module systems?
// TODO: return list of js from translateFiles and provide then to this function with other js files // TODO: return list of js from translateFiles and provide then to this function with other js files
testChecker.check(jsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) val allFiles = jsFiles.flatMap { file -> cachedDependencies[file]?.let { deps -> deps + file } ?: listOf(file) }
testChecker.check(allFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem)
} }
} }
@@ -797,6 +797,104 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
} }
} }
@TestMetadata("js/js.translator/testData/box/crossModuleRefIR")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class CrossModuleRefIR extends AbstractIrBoxJsES6Test {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath);
}
public void testAllFilesPresentInCrossModuleRefIR() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
}
@TestMetadata("callableObjectRef.kt")
public void testCallableObjectRef() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt");
}
@TestMetadata("constructor.kt")
public void testConstructor() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/constructor.kt");
}
@TestMetadata("export.kt")
public void testExport() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/export.kt");
}
@TestMetadata("inheritance.kt")
public void testInheritance() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inheritance.kt");
}
@TestMetadata("inlineJsModule.kt")
public void testInlineJsModule() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt");
}
@TestMetadata("inlineJsModuleNonIdentifier.kt")
public void testInlineJsModuleNonIdentifier() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt");
}
@TestMetadata("inlineJsModulePackage.kt")
public void testInlineJsModulePackage() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt");
}
@TestMetadata("inlineModule.kt")
public void testInlineModule() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt");
}
@TestMetadata("inlineModuleNonIndentifier.kt")
public void testInlineModuleNonIndentifier() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt");
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lambda.kt");
}
@TestMetadata("object.kt")
public void testObject() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/object.kt");
}
@TestMetadata("objectInInlineClosure.kt")
public void testObjectInInlineClosure() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt");
}
@TestMetadata("objectIsObject.kt")
public void testObjectIsObject() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt");
}
@TestMetadata("topLevelExtension.kt")
public void testTopLevelExtension() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt");
}
@TestMetadata("topLevelFunction.kt")
public void testTopLevelFunction() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt");
}
@TestMetadata("topLevelMutableProperty.kt")
public void testTopLevelMutableProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt");
}
@TestMetadata("topLevelProperty.kt")
public void testTopLevelProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt");
}
}
@TestMetadata("js/js.translator/testData/box/dataClass") @TestMetadata("js/js.translator/testData/box/dataClass")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -797,6 +797,104 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
} }
} }
@TestMetadata("js/js.translator/testData/box/crossModuleRefIR")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class CrossModuleRefIR extends AbstractIrBoxJsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
public void testAllFilesPresentInCrossModuleRefIR() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@TestMetadata("callableObjectRef.kt")
public void testCallableObjectRef() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt");
}
@TestMetadata("constructor.kt")
public void testConstructor() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/constructor.kt");
}
@TestMetadata("export.kt")
public void testExport() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/export.kt");
}
@TestMetadata("inheritance.kt")
public void testInheritance() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inheritance.kt");
}
@TestMetadata("inlineJsModule.kt")
public void testInlineJsModule() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt");
}
@TestMetadata("inlineJsModuleNonIdentifier.kt")
public void testInlineJsModuleNonIdentifier() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt");
}
@TestMetadata("inlineJsModulePackage.kt")
public void testInlineJsModulePackage() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt");
}
@TestMetadata("inlineModule.kt")
public void testInlineModule() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt");
}
@TestMetadata("inlineModuleNonIndentifier.kt")
public void testInlineModuleNonIndentifier() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt");
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lambda.kt");
}
@TestMetadata("object.kt")
public void testObject() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/object.kt");
}
@TestMetadata("objectInInlineClosure.kt")
public void testObjectInInlineClosure() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt");
}
@TestMetadata("objectIsObject.kt")
public void testObjectIsObject() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt");
}
@TestMetadata("topLevelExtension.kt")
public void testTopLevelExtension() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt");
}
@TestMetadata("topLevelFunction.kt")
public void testTopLevelFunction() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt");
}
@TestMetadata("topLevelMutableProperty.kt")
public void testTopLevelMutableProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt");
}
@TestMetadata("topLevelProperty.kt")
public void testTopLevelProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt");
}
}
@TestMetadata("js/js.translator/testData/box/dataClass") @TestMetadata("js/js.translator/testData/box/dataClass")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -797,6 +797,104 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
} }
} }
@TestMetadata("js/js.translator/testData/box/crossModuleRefIR")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class CrossModuleRefIR extends AbstractBoxJsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
}
public void testAllFilesPresentInCrossModuleRefIR() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true);
}
@TestMetadata("callableObjectRef.kt")
public void testCallableObjectRef() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt");
}
@TestMetadata("constructor.kt")
public void testConstructor() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/constructor.kt");
}
@TestMetadata("export.kt")
public void testExport() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/export.kt");
}
@TestMetadata("inheritance.kt")
public void testInheritance() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inheritance.kt");
}
@TestMetadata("inlineJsModule.kt")
public void testInlineJsModule() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt");
}
@TestMetadata("inlineJsModuleNonIdentifier.kt")
public void testInlineJsModuleNonIdentifier() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt");
}
@TestMetadata("inlineJsModulePackage.kt")
public void testInlineJsModulePackage() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt");
}
@TestMetadata("inlineModule.kt")
public void testInlineModule() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt");
}
@TestMetadata("inlineModuleNonIndentifier.kt")
public void testInlineModuleNonIndentifier() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt");
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lambda.kt");
}
@TestMetadata("object.kt")
public void testObject() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/object.kt");
}
@TestMetadata("objectInInlineClosure.kt")
public void testObjectInInlineClosure() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt");
}
@TestMetadata("objectIsObject.kt")
public void testObjectIsObject() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt");
}
@TestMetadata("topLevelExtension.kt")
public void testTopLevelExtension() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt");
}
@TestMetadata("topLevelFunction.kt")
public void testTopLevelFunction() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt");
}
@TestMetadata("topLevelMutableProperty.kt")
public void testTopLevelMutableProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt");
}
@TestMetadata("topLevelProperty.kt")
public void testTopLevelProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt");
}
}
@TestMetadata("js/js.translator/testData/box/dataClass") @TestMetadata("js/js.translator/testData/box/dataClass")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -0,0 +1,27 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1289
// MODULE: lib
// FILE: lib.kt
package lib
object O {
operator fun invoke() = "OK"
}
inline fun callO() = O()
// MODULE: main(lib)
// FILE: main.kt
package main
import lib.*
fun box(): String {
val a = O()
if (a != "OK") return "fail: simple: $a"
val b = callO()
if (b != "OK") return "fail: inline: $a"
return "OK"
}
@@ -0,0 +1,72 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1291
// MODULE: lib
// FILE: lib.kt
package lib
class A(val x: Int) {
constructor(a: Int, b: Int) : this(a + b)
}
external class B(x: Int) {
constructor(a: Int, b: Int)
val x: Int
}
// TODO: may be useful after implementing local classes in inline functions
/*
inline fun foo(p: Int, q: Int, r: Int): Pair<Int, Int> {
class C(val x : Int) {
constructor(a: Int, b: Int) : this(a + b)
}
return Pair(C(p).x, C(q, r).x)
}
*/
inline fun callPrimaryConstructor(x: Int) = A(x).x
inline fun callSecondaryConstructor(x: Int, y: Int) = A(x, y).x
// FILE: lib.js
function B(x, y) {
this.x = x;
if (typeof y !== 'undefined') {
this.x += y;
}
}
// MODULE: main(lib)
// FILE: main.kt
package main
import lib.*
fun box(): String {
val a = A(23).x
if (a != 23) return "fail: primary constructor: $a"
val b = A(40, 2).x
if (b != 42) return "fail: secondary constructor: $b"
val c = B(99).x
if (c != 99) return "fail: native primary constructor: $c"
val d = B(100, 11).x
if (d != 111) return "fail: native secondary constructor: $d"
/*
val (e, f) = foo(123, 320, 1)
if (e != 123) return "fail: local primary constructor: $e"
if (f != 321) return "fail: local secondary constructor: $f"
*/
val g = callPrimaryConstructor(55)
if (g != 55) return "fail: primary constructor from inline function: $g"
val h = callSecondaryConstructor(990, 9)
if (h != 999) return "fail: secondary constructor from inline function: $h"
return "OK"
}
@@ -0,0 +1,32 @@
// IGNORE_BACKEND: JS
// SPLIT_PER_MODULE
// RUN_PLAIN_BOX_FUNCTION
// EXPECTED_REACHABLE_NODES: 1316
// MODULE: lib1
// FILE: lib1.kt
@JsExport
fun O(): String = "O"
// MODULE: lib2(lib1)
// FILE: lib2.kt
@JsExport
fun K(): String = "K"
// MODULE: main(lib1, lib2)
// FILE: main.kt
@JsExport
fun test() = O() + K()
// FILE: test.js
function box() {
if (main.test() != "OK") return "fail 1";
return kotlin_lib1.O() + kotlin_lib2.K();
}
@@ -0,0 +1,27 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1292
// MODULE: lib
// FILE: lib.kt
package lib
open class A {
fun foo() = 23
}
// MODULE: main(lib)
// FILE: main.kt
package main
import lib.A
class B : A() {
fun bar() = foo() + 1
}
fun box(): String {
val result = B().bar()
if (result != 24) return "fail: $result"
return "OK"
}
@@ -0,0 +1,26 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1316
// MODULE: lib1
// FILE: lib1.js
define("lib1", [], function() {
return function() {
return "OK";
}
})
// MODULE: lib2(lib1)
// FILE: lib2.kt
// MODULE_KIND: AMD
@JsModule("lib1")
external fun foo(): String
// MODULE: lib3(lib2)
// FILE: lib3.kt
// MODULE_KIND: AMD
inline fun bar() = foo()
// MODULE: main(lib3)
// FILE: main.kt
// MODULE_KIND: AMD
fun box() = bar()
@@ -0,0 +1,26 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1316
// MODULE: lib-1
// FILE: lib-1.js
define("lib-1", [], function() {
return function() {
return "OK";
}
})
// MODULE: lib2(lib-1)
// FILE: lib2.kt
// MODULE_KIND: AMD
@JsModule("lib-1")
external fun foo(): String
// MODULE: lib3(lib2)
// FILE: lib3.kt
// MODULE_KIND: AMD
inline fun bar() = foo()
// MODULE: main(lib3)
// FILE: main.kt
// MODULE_KIND: AMD
fun box() = bar()
@@ -0,0 +1,29 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1284
// MODULE: lib1
// FILE: lib1.js
define("lib1", [], function() {
return {
foo: function() {
return "OK";
}
};
})
// MODULE: lib2(lib1)
// FILE: lib2.kt
// MODULE_KIND: AMD
@file:JsModule("lib1")
external fun foo(): String
// MODULE: lib3(lib2)
// FILE: lib3.kt
// MODULE_KIND: AMD
inline fun bar() = foo()
// MODULE: main(lib3)
// FILE: main.kt
// MODULE_KIND: AMD
fun box() = bar()
@@ -0,0 +1,16 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1283
// MODULE: lib1
// FILE: lib1.kt
fun foo() = "OK"
// MODULE: lib2(lib1)
// FILE: lib2.kt
inline fun bar() = foo()
// MODULE: main(lib1, lib2)
// FILE: main.kt
fun box() = bar()
@@ -0,0 +1,16 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1283
// MODULE: 1
// FILE: lib1.kt
fun foo() = "OK"
// MODULE: 2(1)
// FILE: lib2.kt
inline fun bar() = foo()
// MODULE: main(2)
// FILE: main.kt
fun box() = bar()
@@ -0,0 +1,19 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1283
// MODULE: lib
// FILE: lib.kt
package lib
fun bar(f: () -> String) = f()
inline fun foo(): String {
return bar { "OK" }
}
// MODULE: main(lib)
// FILE: main.kt
package main
import lib.*
fun box() = foo()
@@ -0,0 +1,37 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1289
// MODULE: lib
// FILE: lib.kt
package lib
var log = ""
object O {
init {
log += "O.init;"
}
fun result() = "OK"
}
fun getResult(): String {
log += "before;"
val result = O.result()
log += "after;"
return result
}
// MODULE: main(lib)
// FILE: main.kt
package main
import lib.*
fun box(): String {
val result = getResult()
if (result != "OK") return "fail: unexpected result: $result"
if (log != "before;O.init;after;") return "fail: wrong evaluation order: $log"
return "OK"
}
@@ -0,0 +1,27 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1382
// MODULE: lib
// FILE: lib.kt
package lib
object O {
val result = "OK"
inline fun foo(): String {
val o = object {
fun bar() = O
}
return fetch(o.bar())
}
}
fun fetch(o: O) = o.result
// MODULE: main(lib)
// FILE: main.kt
package main
import lib.*
fun box() = O.foo()
@@ -0,0 +1,21 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1284
// MODULE: lib
// FILE: lib.kt
package lib
object O
// MODULE: main(lib)
// FILE: main.kt
package main
import lib.*
fun box(): String {
var o: Any = O
if (o !is O) return "fail1"
if (!(o is O)) return "fail2"
return "OK"
}
@@ -0,0 +1,44 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1290
// MODULE: lib
// FILE: lib.kt
package lib
class A(val x: Int)
fun A.foo() = 23 + x
inline fun A.baz() = 99 + x
inline fun A.callFoo() = foo()
inline fun A.buzz(): Int {
val o = object {
fun f() = 111 + x
}
return o.f()
}
// MODULE: main(lib)
// FILE: main.kt
package main
import lib.*
fun box(): String {
val a = A(1).foo()
if (a != 24) return "fail: simple function: $a"
val c = A(1).baz()
if (c != 100) return "fail: inline function: $c"
val d = A(1).buzz()
if (d != 112) return "fail: inline function with object expression: $d"
val e = A(2).callFoo()
if (e != 25) return "fail: inline function calling another function: $e"
return "OK"
}
@@ -0,0 +1,51 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1287
// MODULE: lib
// FILE: lib.kt
package lib
fun foo() = 23
external fun bar(): Int = definedExternally
inline fun baz() = 99
inline fun callFoo() = foo()
inline fun buzz(): Int {
val o = object {
fun f() = 111
}
return o.f()
}
// FILE: lib.js
function bar() {
return 42;
}
// MODULE: main(lib)
// FILE: main.kt
package main
fun box(): String {
val a = lib.foo()
if (a != 23) return "fail: simple function: $a"
val b = lib.bar()
if (b != 42) return "fail: native function: $b"
val c = lib.baz()
if (c != 99) return "fail: inline function: $c"
val d = lib.buzz()
if (d != 111) return "fail: inline function with object expression: $d"
val e = lib.callFoo()
if (e != 23) return "fail: inline function calling another function: $e"
return "OK"
}
@@ -0,0 +1,38 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1286
// MODULE: lib
// FILE: lib.kt
package lib
var foo = 23
var bar: Int = 42
get() = field
set(value) {
field = value
}
@JsName("faz") var baz = 99
// MODULE: main(lib)
// FILE: lib.kt
package main
import lib.*
fun box(): String {
if (foo != 23) return "fail: simple property initial value: $foo"
foo = 24
if (foo != 24) return "fail: simple property new value: $foo"
if (bar != 42) return "fail: property with accessor initial value: $bar"
bar = 43
if (bar != 43) return "fail: property with accessor new value: $bar"
if (baz != 99) return "fail: renamed property initial value: $baz"
baz = 100
if (baz != 100) return "fail: renamed property new value: $baz"
return "OK"
}
@@ -0,0 +1,48 @@
// SPLIT_PER_MODULE
// EXPECTED_REACHABLE_NODES: 1287
// MODULE: lib
// FILE: lib.kt
package lib
val foo = 23
val boo: Int
get() = 42
external val bar: Int = definedExternally
external val far: Int
get() = definedExternally
// TODO: annotations like this are not serialized properly. Uncomment after KT-14529 gets fixed
/*
val fuzz: Int
@JsName("getBuzz") get() = 55
*/
inline fun fetchFoo() = foo
@JsName("fee")
val tee = 2525
// FILE: lib.js
var bar = 99
var far = 111
// MODULE: main(lib)
// FILE: lib.kt
package main
import lib.*
fun box(): String {
if (foo != 23) return "fail: simple property: $foo"
if (boo != 42) return "fail: property with accessor: $boo"
if (bar != 99) return "fail: native property: $bar"
if (far != 111) return "fail: native property with accessor: $far"
//if (fuzz != 55) return "fail: property with JsName on accessor: $fuzz"
if (tee != 2525) return "fail: native property with JsName: $tee"
return "OK"
}