Compare commits

...

1 Commits

Author SHA1 Message Date
Anton Bannykh
4bcc4e1b45 JS IR: prototype for lazy module loading 2020-08-14 09:40:38 +03:00
50 changed files with 1421 additions and 98 deletions

View File

@@ -138,6 +138,13 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xir-per-module", description = "Splits generated .js per-module")
var irPerModule: Boolean by FreezableVar(false)
@Argument(
value = "-Xir-loaded-lazily",
valueDescription = "module1,module2,...moduleN",
description = "Names of lazy loaded modules"
)
var irLoadedLazily: String? by NullableStringFreezableVar(null)
@Argument(
value = "-Xinclude",
valueDescription = "<path>",

View File

@@ -48,7 +48,9 @@ import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull
import org.jetbrains.kotlin.utils.join
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.io.PrintWriter
enum class ProduceKind {
DEFAULT, // Determine what to produce based on js-v1 options
@@ -182,6 +184,9 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
it.libraryFile.absolutePath in friendAbsolutePaths
}
val loadedLazily = arguments.irLoadedLazily?.split(',') ?: emptyList()
config.configuration.put(JSConfigurationKeys.ASYNC_IMPORTS, loadedLazily)
if (arguments.irProduceKlibDir || arguments.irProduceKlibFile) {
val outputKlibPath =
if (arguments.irProduceKlibDir)
@@ -235,8 +240,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
generateFullJs = !arguments.irDce,
generateDceJs = arguments.irDce,
dceDriven = arguments.irDceDriven,
multiModule = arguments.irPerModule,
relativeRequirePath = true
multiModule = arguments.irPerModule
)
} catch (e: JsIrCompilationError) {
return COMPILATION_ERROR

View File

@@ -44,7 +44,7 @@ private fun IrField.isConstant(): Boolean {
private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackendContext): Iterable<IrDeclaration> {
val rootDeclarations =
(modules.flatMap { it.files } + context.packageLevelJsModules + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file ->
(modules.flatMap { it.files } + context.packageLevelJsModules.values.flatMap { it } + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file ->
file.declarations.flatMap { if (it is IrProperty) listOfNotNull(it.backingField, it.getter, it.setter) else listOf(it) }
.filter {
it is IrField && it.initializer != null && it.fqNameWhenAvailable?.asString()?.startsWith("kotlin") != true
@@ -56,6 +56,10 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackend
}
rootDeclarations += context.testRoots.values
rootDeclarations += context.loadModule.owner
rootDeclarations += context.setModuleLoader.owner
rootDeclarations += context.isLoaded.owner
rootDeclarations += context.reportLoaded.owner
JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction ->
rootDeclarations += mainFunction

View File

@@ -42,8 +42,14 @@ class JsIrBackendContext(
val additionalExportedDeclarations: Set<FqName>,
override val configuration: CompilerConfiguration, // TODO: remove configuration from backend context
override val scriptMode: Boolean = false,
override val es6mode: Boolean = false
override val es6mode: Boolean = false,
val lazyImportMap: Map<ModuleDescriptor, Iterable<ModuleDescriptor>> = emptyMap()
) : JsCommonBackendContext {
fun shouldLazyLoad(from: ModuleDescriptor, to: ModuleDescriptor): Boolean {
return to in (lazyImportMap[from] ?: emptySet())
}
override val transformedFunction
get() = error("Use Mapping.inlineClassMemberToStatic instead")
@@ -68,8 +74,8 @@ class JsIrBackendContext(
FqName("kotlin")
)
val packageLevelJsModules = mutableSetOf<IrFile>()
val declarationLevelJsModules = mutableListOf<IrDeclarationWithName>()
val packageLevelJsModules = mutableMapOf<IrModuleFragment, MutableSet<IrFile>>()
val declarationLevelJsModules = mutableMapOf<IrModuleFragment, MutableList<IrDeclarationWithName>>()
private val internalPackageFragmentDescriptor = EmptyPackageFragmentDescriptor(builtIns.builtInsModule, FqName("kotlin.js.internal"))
val implicitDeclarationFile = run {
@@ -304,6 +310,12 @@ class JsIrBackendContext(
val kpropertyBuilder = getFunctions(FqName("kotlin.js.getPropertyCallableRef")).single().let { symbolTable.referenceSimpleFunction(it) }
val klocalDelegateBuilder = getFunctions(FqName("kotlin.js.getLocalDelegateReference")).single().let { symbolTable.referenceSimpleFunction(it) }
val loadModule = getFunctions(FqName("kotlin.loadModule")).single().let { symbolTable.referenceSimpleFunction(it) }
val setModuleLoader = getFunctions(FqName("kotlin.setModuleLoader")).single().let { symbolTable.referenceSimpleFunction(it) }
val isLoaded = getFunctions(FqName("kotlin.isLoaded")).single().let { symbolTable.referenceSimpleFunction(it) }
val reportLoaded = getFunctions(FqName("kotlin.reportLoaded")).single().let { symbolTable.referenceSimpleFunction(it) }
val moduleNotLoaded = getClass(FqName("kotlin.js.ModuleNotLoaded")).let { symbolTable.referenceClass(it) }
private fun referenceOperators(): Map<Name, MutableMap<IrClassifierSymbol, IrSimpleFunctionSymbol>> {
val primitiveIrSymbols = irBuiltIns.primitiveIrTypes.map { it.classifierOrFail as IrClassSymbol }

View File

@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.backend.common.lower.optimizations.FoldConstantLower
import org.jetbrains.kotlin.backend.common.lower.optimizations.PropertyAccessorInlineLowering
import org.jetbrains.kotlin.backend.common.phaser.*
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.lower.calls.CallsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.cleanup.CleanupLowering
@@ -644,12 +645,19 @@ private val cleanupLoweringPhase = makeBodyLoweringPhase(
description = "Clean up IR before codegen"
)
private val triggerModuleLoadingPhase = makeBodyLoweringPhase(
::TriggerModuleLoading,
name = "TriggerModuleLowering",
description = "Trigger module loading"
)
val loweringList = listOf<Lowering>(
scriptRemoveReceiverLowering,
validateIrBeforeLowering,
expectDeclarationsRemovingPhase,
stripTypeAliasDeclarationsPhase,
arrayConstructorPhase,
triggerModuleLoadingPhase,
lateinitNullableFieldsPhase,
lateinitDeclarationLoweringPhase,
lateinitUsageLoweringPhase,
@@ -690,7 +698,7 @@ val loweringList = listOf<Lowering>(
propertyReferenceLoweringPhase,
interopCallableReferenceLoweringPhase,
returnableBlockLoweringPhase,
forLoopsLoweringPhase,
// forLoopsLoweringPhase,
primitiveCompanionLoweringPhase,
propertyAccessorInlinerLoweringPhase,
foldConstantLoweringPhase,

View File

@@ -48,11 +48,11 @@ fun compile(
dceDriven: Boolean = false,
es6mode: Boolean = false,
multiModule: Boolean = false,
relativeRequirePath: Boolean = false
lazyImportName: String = "import"
): CompilerResult {
stageController = StageController()
val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer, lazyImportMap) =
loadIr(project, mainModule, analyzer, configuration, allDependencies, friendDependencies, PersistentIrFactory)
val moduleDescriptor = moduleFragment.descriptor
@@ -62,7 +62,16 @@ fun compile(
is MainModule.Klib -> dependencyModules
}
val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, allModules.first(), exportedDeclarations, configuration, es6mode = es6mode)
val context = JsIrBackendContext(
moduleDescriptor,
irBuiltIns,
symbolTable,
allModules.first(),
exportedDeclarations,
configuration,
es6mode = es6mode,
lazyImportMap = lazyImportMap
)
// Load declarations referenced during `context` initialization
val irProviders = listOf(deserializer)
@@ -95,7 +104,7 @@ fun compile(
fullJs = true,
dceJs = false,
multiModule = multiModule,
relativeRequirePath = relativeRequirePath
lazyImportName = lazyImportName
)
return transformer.generateModule(allModules)
} else {
@@ -106,7 +115,7 @@ fun compile(
fullJs = generateFullJs,
dceJs = generateDceJs,
multiModule = multiModule,
relativeRequirePath = relativeRequirePath
lazyImportName = lazyImportName
)
return transformer.generateModule(allModules)
}

View File

@@ -70,14 +70,14 @@ private fun isBuiltInClass(declaration: IrDeclaration): Boolean =
declaration is IrClass && declaration.fqNameWhenAvailable in BODILESS_BUILTIN_CLASSES
fun moveBodilessDeclarationsToSeparatePlace(context: JsIrBackendContext, moduleFragment: IrModuleFragment) {
MoveBodilessDeclarationsToSeparatePlaceLowering(context).let { moveBodiless ->
MoveBodilessDeclarationsToSeparatePlaceLowering(context, moduleFragment).let { moveBodiless ->
moduleFragment.files.forEach {
moveBodiless.lower(it)
}
}
}
class MoveBodilessDeclarationsToSeparatePlaceLowering(private val context: JsIrBackendContext) : DeclarationTransformer {
class MoveBodilessDeclarationsToSeparatePlaceLowering(private val context: JsIrBackendContext, private val currentModule: IrModuleFragment) : DeclarationTransformer {
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
val irFile = declaration.parent as? IrFile ?: return null
@@ -94,7 +94,7 @@ class MoveBodilessDeclarationsToSeparatePlaceLowering(private val context: JsIrB
externalPackageFragment.declarations += declaration
declaration.parent = externalPackageFragment
context.packageLevelJsModules += externalPackageFragment
context.packageLevelJsModules.getOrPut(currentModule) { mutableSetOf() } += externalPackageFragment
declaration.collectAllExternalDeclarations()
@@ -109,7 +109,7 @@ class MoveBodilessDeclarationsToSeparatePlaceLowering(private val context: JsIrB
return emptyList()
} else if (d.isEffectivelyExternal()) {
if (d.getJsModule() != null)
context.declarationLevelJsModules.add(d)
context.declarationLevelJsModules.getOrPut(currentModule) { mutableListOf() }.add(d)
externalPackageFragment.declarations += d
d.parent = externalPackageFragment

View File

@@ -0,0 +1,219 @@
/*
* 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.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.ir.isTopLevel
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irIfThen
import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.backend.common.runOnFilePostfix
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
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.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.name.FqName
@OptIn(ObsoleteDescriptorBasedAPI::class)
class TriggerModuleLoading(val context: JsIrBackendContext): BodyLoweringPass {
override fun lower(irFile: IrFile) {
runOnFilePostfix(irFile)
}
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (container is IrSimpleFunction && container.isSuspend) {
insertModuleLoadingInSuspendFun(irBody, container)
} else {
irBody.accept(SpecialFunctionsFinder(), null)
}
}
private fun insertModuleLoadingInSuspendFun(irBody: IrBody, container: IrDeclaration) {
if (container !is IrSimpleFunction || !container.isSuspend) return
val currentModule = container.module
val lazyModules = irBody.collectReferencedModules(currentModule).filter { context.shouldLazyLoad(currentModule, it) }
.map { it.name.asString() }
val backendContext = context
val additionalStatements =
lazyModules.map { moduleName ->
context.createIrBuilder((container as IrSymbolDeclaration<*>).symbol).buildStatement(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
irCall(backendContext.loadModule).apply {
putValueArgument(0, irString(currentModule.name.asString()))
putValueArgument(1, irString(moduleName))
}
}
}
addStatements(irBody, container, additionalStatements)
}
private fun addStatements(irBody: IrBody, container: IrDeclaration, additionalStatements: List<IrStatement>) {
when (irBody) {
is IrBlockBody -> irBody.statements.addAll(0, additionalStatements)
is IrExpressionBody -> irBody.expression = context.createIrBuilder((container as IrSymbolDeclaration<*>).symbol).irComposite {
additionalStatements.forEach { +it }
+irBody.expression
}
}
}
private fun insertModuleLoadingInLambda(irBody: IrBody, container: IrFunction, triggerModuleLoading: Boolean) {
// TODO find a better way to avoid unit materialization
container.returnType = context.dynamicType
val currentModule = container.moduleOrNull ?: return
val lazyModules = irBody.collectReferencedModules(currentModule).filter { context.shouldLazyLoad(currentModule, it) }
.map { it.name.asString() }
val backendContext = context
val additionalStatements =
lazyModules.map { moduleName ->
context.createIrBuilder((container as IrSymbolDeclaration<*>).symbol).buildStatement(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
irIfThen(irNot(irCall(backendContext.isLoaded).apply {
putValueArgument(0, irString(currentModule.name.asString()))
putValueArgument(1, irString(moduleName))
putValueArgument(2, irBoolean(triggerModuleLoading))
}), irReturn(irGetObject(backendContext.moduleNotLoaded)))
}
}
addStatements(irBody, container, additionalStatements)
}
private val AllowAsyncRefsFqn = FqName("kotlin.js.AllowAsyncRefs")
private val TriggerModuleLoadingFqn = FqName("kotlin.js.TriggerModuleLoading")
private fun IrElement.collectReferencedModules(currentModule: ModuleDescriptor): Set<ModuleDescriptor> {
val result = mutableSetOf<ModuleDescriptor>()
accept(object : SpecialFunctionsFinder() {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitGetObjectValue(expression: IrGetObjectValue) {
super.visitGetObjectValue(expression)
expression.process()
}
override fun visitGetField(expression: IrGetField) {
super.visitGetField(expression)
expression.process()
}
override fun visitSetField(expression: IrSetField) {
super.visitSetField(expression)
expression.process()
}
override fun visitCall(expression: IrCall) {
super.visitCall(expression)
expression.process()
}
override fun visitCallableReference(expression: IrCallableReference<*>) {
super.visitCallableReference(expression)
expression.process()
}
override fun visitDeclarationReference(expression: IrDeclarationReference) {
super.visitDeclarationReference(expression)
expression.process()
}
private fun IrDeclarationReference.process() {
val declaration = symbol.owner as IrDeclaration
val declarationToCheck = when (declaration) {
is IrSimpleFunction -> declaration.correspondingPropertySymbol?.owner ?: declaration
is IrField -> declaration.correspondingPropertySymbol?.owner ?: declaration
else -> declaration
}
if (!declarationToCheck.isTopLevelDeclaration) return
val module = declaration.moduleOrNull ?: return
if (module == currentModule) return
result += module
}
}, null)
return result
}
private open inner class SpecialFunctionsFinder: IrElementVisitorVoid {
private val specialLambdas = mutableMapOf<IrFunctionExpression, Boolean>()
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitCall(expression: IrCall) {
expression.symbol.owner.valueParameters.forEachIndexed { index, irValueParameter ->
if (irValueParameter.annotations.any {
it.symbol.owner.constructedClass.fqNameWhenAvailable == AllowAsyncRefsFqn
}) {
specialLambdas[expression.getValueArgument(index) as IrFunctionExpression] = irValueParameter.annotations.any {
it.symbol.owner.constructedClass.fqNameWhenAvailable == TriggerModuleLoadingFqn
}
}
}
super.visitCall(expression)
}
override fun visitFunctionExpression(expression: IrFunctionExpression) {
specialLambdas[expression]?.let { triggerLoading ->
expression.function.let { fn ->
fn.body?.let { body ->
insertModuleLoadingInLambda(body, fn, triggerLoading)
return
}
}
}
super.visitFunctionExpression(expression)
}
override fun visitFunction(declaration: IrFunction) {
if (declaration.isSuspend) {
declaration.body?.let { body ->
insertModuleLoadingInSuspendFun(body, declaration)
return
}
}
super.visitFunction(declaration)
}
}
}
@ObsoleteDescriptorBasedAPI
private val IrDeclaration.moduleOrNull: ModuleDescriptor?
get() = try { this.module } catch (_: Throwable) { null }

View File

@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToJsStatements
import org.jetbrains.kotlin.ir.backend.js.export.ExportedModule
import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
import org.jetbrains.kotlin.ir.backend.js.lower.TriggerModuleLoading
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -23,6 +24,9 @@ import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.utils.DFS
import java.io.BufferedWriter
import java.io.FileWriter
import java.io.PrintWriter
class IrModuleToJsTransformer(
private val backendContext: JsIrBackendContext,
@@ -32,7 +36,7 @@ class IrModuleToJsTransformer(
private val fullJs: Boolean = true,
private val dceJs: Boolean = false,
private val multiModule: Boolean = false,
private val relativeRequirePath: Boolean = false
private val lazyImportName: String = "import"
) {
private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS)
@@ -41,7 +45,7 @@ class IrModuleToJsTransformer(
externalPackageFragment.values + listOf(
bodilessBuiltInsPackageFragment,
intrinsics.externalPackageFragment
) + packageLevelJsModules
) + packageLevelJsModules.values.flatMap { it }
}
val exportedModule = ExportModelGenerator(backendContext).generateExport(modules)
@@ -76,7 +80,7 @@ class IrModuleToJsTransformer(
private fun generateWrappedModuleBody(modules: Iterable<IrModuleFragment>, exportedModule: ExportedModule, namer: NameTables): JsCode {
if (multiModule) {
val refInfo = buildCrossModuleReferenceInfo(modules)
val refInfo = buildCrossModuleReferenceInfo(modules, backendContext)
val rM = modules.reversed()
@@ -94,7 +98,8 @@ class IrModuleToJsTransformer(
val dependencies = others.mapIndexed { index, module ->
val moduleName = sanitizeName(module.safeName)
val exportedDeclarations = ExportModelGenerator(backendContext).let { module.files.flatMap { file -> it.generateExport(file) } }
val exportedDeclarations =
ExportModelGenerator(backendContext).let { module.files.flatMap { file -> it.generateExport(file) } }
moduleName to generateWrappedModuleBody2(
listOf(module),
@@ -142,6 +147,7 @@ class IrModuleToJsTransformer(
val (importStatements, importedJsModules) =
generateImportStatements(
modules,
getNameForExternalDeclaration = { rootContext.getNameForStaticDeclaration(it) },
declareFreshGlobal = { JsName(sanitizeName(it)) } // TODO: Declare fresh name
)
@@ -153,7 +159,12 @@ class IrModuleToJsTransformer(
val callToMain = generateCallToMain(modules, rootContext)
val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(nameGenerator, modules, dependencies, { JsName(sanitizeName(it)) })
val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(
nameGenerator,
modules,
dependencies,
{ JsName(sanitizeName(it)) })
val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName)
val program = JsProgram()
@@ -197,17 +208,74 @@ class IrModuleToJsTransformer(
val imports = mutableListOf<JsStatement>()
val modules = mutableListOf<JsImportedModule>()
namerWithImports.getNameForStaticFunction(backendContext.setModuleLoader.owner)
namerWithImports.getNameForStaticFunction(backendContext.isLoaded.owner)
namerWithImports.getNameForStaticFunction(backendContext.reportLoaded.owner)
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)
if (module in currentModules || currentModules.any { !backendContext.shouldLazyLoad(it.descriptor, module.descriptor) }) {
val moduleName = declareFreshGlobal(module.safeName)
modules += JsImportedModule("./${moduleName.ident}.js", moduleName, moduleName.makeRef())
names.forEach {
imports += JsVars(JsVars.JsVar(JsName(it), JsNameRef(it, JsNameRef("\$crossModule\$", moduleName.makeRef()))))
names.forEach {
imports += JsVars(JsVars.JsVar(JsName(it), JsNameRef(it, JsNameRef("\$crossModule\$", moduleName.makeRef()))))
}
} else {
val moduleName = JsName(module.safeName)
imports += JsVars(JsVars.JsVar(moduleName))
names.forEach {
imports += JsVars(JsVars.JsVar(JsName(it)))
}
val loader = JsFunction(JsDynamicScope, "loader for ${module.name.asString()}")
loader.body = JsBlock(
JsReturn(
JsInvocation(
JsNameRef("then", JsInvocation(JsNameRef(lazyImportName), JsStringLiteral("./${module.safeName}.js"))),
JsFunction(JsDynamicScope, "initializer for ${module.name.asString()}").apply {
val moduleParam = JsName("module")
parameters += JsParameter(moduleParam)
val initIfNeeded = JsIf(
JsAstUtils.not(
JsInvocation(
namerWithImports.getNameForStaticFunction(backendContext.isLoaded.owner).makeRef(),
JsStringLiteral(currentModules.single().name.asString()),
JsStringLiteral(module.name.asString()),
JsBooleanLiteral(false)
)
),
JsBlock(listOf(
JsInvocation(
namerWithImports.getNameForStaticFunction(backendContext.reportLoaded.owner).makeRef(),
JsStringLiteral(currentModules.single().name.asString()),
JsStringLiteral(module.name.asString())
).makeStmt(),
JsAstUtils.assignment(moduleName.makeRef(), JsNameRef("default", moduleParam.makeRef())).makeStmt()
) + names.map {
JsAstUtils.assignment(
JsNameRef(it),
JsNameRef(it, JsNameRef("\$crossModule\$", moduleName.makeRef()))
).makeStmt()
})
)
body = JsBlock(initIfNeeded)
})
)
)
imports += JsInvocation(
namerWithImports.getNameForStaticFunction(backendContext.setModuleLoader.owner).makeRef(),
JsStringLiteral(currentModules.single().name.asString()),
JsStringLiteral(module.name.asString()),
loader
).makeStmt()
}
}
@@ -320,49 +388,55 @@ class IrModuleToJsTransformer(
}
private fun generateImportStatements(
modules: Iterable<IrModuleFragment>,
getNameForExternalDeclaration: (IrDeclarationWithName) -> JsName,
declareFreshGlobal: (String) -> JsName
): Pair<MutableList<JsStatement>, List<JsImportedModule>> {
val declarationLevelJsModules =
backendContext.declarationLevelJsModules.map { externalDeclaration ->
val declarationLevelJsModules = mutableListOf<JsImportedModule>()
modules.forEach {
backendContext.declarationLevelJsModules[it]?.forEach { externalDeclaration ->
val jsModule = externalDeclaration.getJsModule()!!
val name = getNameForExternalDeclaration(externalDeclaration)
JsImportedModule(jsModule, name, name.makeRef())
declarationLevelJsModules += JsImportedModule(jsModule, name, name.makeRef())
}
}
val packageLevelJsModules = mutableListOf<JsImportedModule>()
val importStatements = mutableListOf<JsStatement>()
for (file in backendContext.packageLevelJsModules) {
val jsModule = file.getJsModule()
val jsQualifier = file.getJsQualifier()
modules.mapNotNull { backendContext.packageLevelJsModules[it] }.forEach { jsModules ->
for (file in jsModules) {
val jsModule = file.getJsModule()
val jsQualifier = file.getJsQualifier()
assert(jsModule != null || jsQualifier != null)
assert(jsModule != null || jsQualifier != null)
val qualifiedReference: JsNameRef
val qualifiedReference: JsNameRef
if (jsModule != null) {
val internalName = declareFreshGlobal("\$module\$$jsModule")
packageLevelJsModules += JsImportedModule(jsModule, internalName, null)
if (jsModule != null) {
val internalName = declareFreshGlobal("\$module\$$jsModule")
packageLevelJsModules += JsImportedModule(jsModule, internalName, null)
qualifiedReference =
if (jsQualifier == null)
internalName.makeRef()
else
JsNameRef(jsQualifier, internalName.makeRef())
} else {
qualifiedReference = JsNameRef(jsQualifier!!)
}
file.declarations
.asSequence()
.filterIsInstance<IrDeclarationWithName>()
.forEach { declaration ->
val declName = getNameForExternalDeclaration(declaration)
importStatements.add(
JsVars(JsVars.JsVar(declName, JsNameRef(declName, qualifiedReference)))
)
qualifiedReference =
if (jsQualifier == null)
internalName.makeRef()
else
JsNameRef(jsQualifier, internalName.makeRef())
} else {
qualifiedReference = JsNameRef(jsQualifier!!)
}
file.declarations
.asSequence()
.filterIsInstance<IrDeclarationWithName>()
.forEach { declaration ->
val declName = getNameForExternalDeclaration(declaration)
importStatements.add(
JsVars(JsVars.JsVar(declName, JsNameRef(declName, qualifiedReference)))
)
}
}
}
val importedJsModules = (declarationLevelJsModules + packageLevelJsModules).distinctBy { it.key }

View File

@@ -77,7 +77,7 @@ object ModuleWrapperTranslation {
val scope = program.scope
val defineName = scope.declareName("define")
val invocationArgs = listOf(
JsArrayLiteral(listOf(JsStringLiteral("exports")) + importedModules.map { JsStringLiteral(it.requireName) }),
JsArrayLiteral(listOf(JsStringLiteral("exports")) + importedModules.map { JsStringLiteral(it.externalName) }),
function
)
@@ -94,7 +94,7 @@ object ModuleWrapperTranslation {
val moduleName = scope.declareName("module")
val requireName = scope.declareName("require")
val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), JsStringLiteral(it.requireName)) }
val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), JsStringLiteral(it.externalName)) }
val invocation = JsInvocation(function, listOf(JsNameRef("exports", moduleName.makeRef())) + invocationArgs)
return listOf(invocation.makeStmt())
}

View File

@@ -103,7 +103,7 @@ private class CrossModuleReferenceInfoImpl(val topLevelDeclarationToModule: Map<
}
fun buildCrossModuleReferenceInfo(modules: Iterable<IrModuleFragment>): CrossModuleReferenceInfo {
fun buildCrossModuleReferenceInfo(modules: Iterable<IrModuleFragment>, backendContext: JsIrBackendContext): CrossModuleReferenceInfo {
val map = mutableMapOf<IrDeclaration, IrModuleFragment>()
modules.forEach { module ->
@@ -112,6 +112,16 @@ fun buildCrossModuleReferenceInfo(modules: Iterable<IrModuleFragment>): CrossMod
map[declaration] = module
}
}
backendContext.packageLevelJsModules[module]?.forEach { file ->
file.declarations.forEach { declaration ->
map[declaration] = module
}
}
backendContext.declarationLevelJsModules[module]?.forEach { declaration ->
map[declaration] = module
}
}
return CrossModuleReferenceInfoImpl(map)
@@ -207,11 +217,13 @@ fun breakCrossModuleFieldAccess(
}
val IrModuleFragment.safeName: String
get() {
var result = name.asString()
get() = name.asString().transformToSafeModuleName()
if (result.startsWith('<')) result = result.substring(1)
if (result.endsWith('>')) result = result.substring(0, result.length - 1)
fun String.transformToSafeModuleName(): String {
var result = this
return sanitizeName("kotlin-$result")
}
if (result.startsWith('<')) result = result.substring(1)
if (result.endsWith('>')) result = result.substring(0, result.length - 1)
return sanitizeName("kotlin-$result")
}

View File

@@ -43,6 +43,7 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.konan.properties.propertyList
import org.jetbrains.kotlin.konan.util.KlibMetadataFactories
import org.jetbrains.kotlin.library.*
@@ -63,6 +64,9 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.utils.DFS
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter
import java.util.*
import org.jetbrains.kotlin.konan.file.File as KFile
val KotlinLibrary.moduleName: String
@@ -196,7 +200,8 @@ data class IrModuleInfo(
val allDependencies: List<IrModuleFragment>,
val bultins: IrBuiltIns,
val symbolTable: SymbolTable,
val deserializer: JsIrLinker
val deserializer: JsIrLinker,
val lazyImportMap: Map<ModuleDescriptor, Iterable<ModuleDescriptor>>
)
private fun sortDependencies(dependencies: List<KotlinLibrary>, mapping: Map<KotlinLibrary, ModuleDescriptor>): Collection<KotlinLibrary> {
@@ -220,6 +225,20 @@ fun loadIr(
val depsDescriptors = ModulesStructure(project, mainModule, analyzer, configuration, allDependencies, friendDependencies)
val deserializeFakeOverrides = configuration.getBoolean(CommonConfigurationKeys.DESERIALIZE_FAKE_OVERRIDES)
val klibToDescriptor = allDependencies.getFullList().associateWith {
depsDescriptors.getModuleDescriptor(it)
}
val moduleNameToDescriptor = klibToDescriptor.values.associateBy { it.name.asString() }
val lazyImportMap: MutableMap<ModuleDescriptor, Iterable<ModuleDescriptor>> =
klibToDescriptor.entries.associateByTo(mutableMapOf(), { (_, md) -> md }, { (klib, _) ->
klib.manifestProperties.propertyList(KLIB_PROPERTY_LAZY_IMPORT, escapeInQuotes = true).map {
moduleNameToDescriptor[it]
?: error("Lazy loading: cannot find module $it; available modules: ${moduleNameToDescriptor.keys}")
}
})
when (mainModule) {
is MainModule.SourceFiles -> {
val psi2IrContext: GeneratorContext = runAnalysisAndPreparePsi2Ir(depsDescriptors, irFactory)
@@ -246,7 +265,9 @@ fun loadIr(
irBuiltIns.knownBuiltins.forEach { it.acceptVoid(mangleChecker) }
return IrModuleInfo(moduleFragment, deserializedModuleFragments, irBuiltIns, symbolTable, irLinker)
lazyImportMap[moduleFragment.descriptor] = configuration.getList(JSConfigurationKeys.ASYNC_IMPORTS).map { moduleNameToDescriptor[it]!! }
return IrModuleInfo(moduleFragment, deserializedModuleFragments, irBuiltIns, symbolTable, irLinker, lazyImportMap)
}
is MainModule.Klib -> {
val moduleDescriptor = depsDescriptors.getModuleDescriptor(mainModule.lib)
@@ -282,7 +303,7 @@ fun loadIr(
ExternalDependenciesGenerator(symbolTable, listOf(irLinker), configuration.languageVersionSettings).generateUnboundSymbolsAsDependencies()
irLinker.postProcess()
return IrModuleInfo(moduleFragment, deserializedModuleFragments, irBuiltIns, symbolTable, irLinker)
return IrModuleInfo(moduleFragment, deserializedModuleFragments, irBuiltIns, symbolTable, irLinker, lazyImportMap)
}
}
}
@@ -377,6 +398,8 @@ private class ModulesStructure(
require(mainModule is MainModule.SourceFiles)
val files = mainModule.files
val asyncDeps = compilerConfiguration.getList(JSConfigurationKeys.ASYNC_IMPORTS).toSet()
analyzer.analyzeAndReport(files) {
TopDownAnalyzerFacadeForJSIR.analyzeFiles(
files,
@@ -384,6 +407,7 @@ private class ModulesStructure(
compilerConfiguration,
allDependencies.getFullList().map { getModuleDescriptor(it) },
friendModuleDescriptors = friendDependencies.map { getModuleDescriptor(it) },
lazyModuleDependencies = allDependencies.getFullList().map { getModuleDescriptor(it) }.filter { it.name.asString() in asyncDeps },
thisIsBuiltInsModule = builtInModuleDescriptor == null,
customBuiltInsModule = builtInModuleDescriptor
)
@@ -438,6 +462,8 @@ private class ModulesStructure(
null // null in case compiling builtInModule itself
}
const val KLIB_PROPERTY_LAZY_IMPORT = "lazyImport"
private fun getDescriptorForElement(
context: BindingContext,
element: PsiElement
@@ -524,12 +550,17 @@ fun serializeModuleIntoKlib(
irVersion = KlibIrVersion.INSTANCE.toString()
)
val properties = Properties().also { p ->
val lazyImports = configuration.getList(JSConfigurationKeys.ASYNC_IMPORTS).joinToString(" ")
p.setProperty(KLIB_PROPERTY_LAZY_IMPORT, lazyImports)
}
buildKotlinLibrary(
linkDependencies = dependencies,
ir = fullSerializedIr,
metadata = serializedMetadata,
dataFlowGraph = null,
manifestProperties = null,
manifestProperties = properties,
moduleName = moduleName,
nopack = nopack,
perFile = perFile,

View File

@@ -0,0 +1,26 @@
// MODULE: lib
// FILE: lib.kt
fun fn() {}
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: lib
fun test() {
(::<!WRONG_ASYNC_MODULE_REFERENCE!>fn<!>)()
}
suspend fun testSuspend() {
(::fn)()
}
fun testIntrinsics() {
runIfLoaded {
(::fn)()
}
runIfLoaded({ run {
(::fn)()
} }, {})
}

View File

@@ -0,0 +1,12 @@
// -- Module: <lib> --
package
public fun fn(): kotlin.Unit
// -- Module: <main> --
package
public fun test(): kotlin.Unit
public fun testIntrinsics(): kotlin.Unit
public suspend fun testSuspend(): kotlin.Unit

View File

@@ -0,0 +1,26 @@
// MODULE: lib
// FILE: lib.kt
class A {}
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: lib
fun test() {
<!WRONG_ASYNC_MODULE_REFERENCE!>A()<!>
}
suspend fun testSuspend() {
A()
}
fun testIntrinsics() {
runIfLoaded {
A()
}
runIfLoaded({ run {
A()
} }, {})
}

View File

@@ -0,0 +1,17 @@
// -- Module: <lib> --
package
public final class A {
public constructor A()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
// -- Module: <main> --
package
public fun test(): kotlin.Unit
public fun testIntrinsics(): kotlin.Unit
public suspend fun testSuspend(): kotlin.Unit

View File

@@ -0,0 +1,60 @@
// MODULE: lib
// FILE: lib.kt
open class C
interface I
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: lib
<!WRONG_ASYNC_MODULE_REFERENCE!>class CC : C()<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>class II : I<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>object OC<!> : C()
<!WRONG_ASYNC_MODULE_REFERENCE!>object OI<!> : I
fun test() {
<!WRONG_ASYNC_MODULE_REFERENCE!>class LC : C()<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>class LI : I<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>object<!> : C() {}
<!WRONG_ASYNC_MODULE_REFERENCE!>object<!> : I {}
}
suspend fun testSuspend() {
<!WRONG_ASYNC_MODULE_REFERENCE!>class LC : C()<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>class LI : I<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>object<!> : C() {}
<!WRONG_ASYNC_MODULE_REFERENCE!>object<!> : I {}
}
fun testIntrinsics() {
runIfLoaded {
<!WRONG_ASYNC_MODULE_REFERENCE!>class LC : C()<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>class LI : I<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>object<!> : C() {}
<!WRONG_ASYNC_MODULE_REFERENCE!>object<!> : I {}
}
runIfLoaded({ run {
<!WRONG_ASYNC_MODULE_REFERENCE!>class LC : C()<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>class LI : I<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>object<!> : C() {}
<!WRONG_ASYNC_MODULE_REFERENCE!>object<!> : I {}
} }, {})
}

View File

@@ -0,0 +1,51 @@
// -- Module: <lib> --
package
public open class C {
public constructor C()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface I {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
// -- Module: <main> --
package
public fun test(): kotlin.Unit
public fun testIntrinsics(): kotlin.Unit
public suspend fun testSuspend(): kotlin.Unit
public final class CC : C {
public constructor CC()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class II : I {
public constructor II()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object OC : C {
private constructor OC()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object OI : I {
private constructor OI()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}

View File

@@ -0,0 +1,48 @@
// MODULE: lib
// FILE: lib.kt
class C {
fun fn() {}
val p = 0
}
interface I {
fun fn()
val p: Int
}
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: lib
fun test(c: C, i: I) {
c.fn()
c.p
i.fn()
i.p
}
suspend fun testSuspend(c: C, i: I) {
c.fn()
c.p
i.fn()
i.p
}
fun testIntrinsics(c: C, i: I) {
runIfLoaded {
c.fn()
c.p
i.fn()
i.p
}
runIfLoaded({ run {
c.fn()
c.p
i.fn()
i.p
} }, {})
}

View File

@@ -0,0 +1,27 @@
// -- Module: <lib> --
package
public final class C {
public constructor C()
public final val p: kotlin.Int = 0
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun fn(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface I {
public abstract val p: kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public abstract fun fn(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
// -- Module: <main> --
package
public fun test(/*0*/ c: C, /*1*/ i: I): kotlin.Unit
public fun testIntrinsics(/*0*/ c: C, /*1*/ i: I): kotlin.Unit
public suspend fun testSuspend(/*0*/ c: C, /*1*/ i: I): kotlin.Unit

View File

@@ -0,0 +1,52 @@
// MODULE: lib
// FILE: lib.kt
object O {
fun fn() {}
}
class A {
companion object {
fun cfn() {}
}
}
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: lib
fun test() {
<!WRONG_ASYNC_MODULE_REFERENCE!>O<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>O<!>.fn()
<!WRONG_ASYNC_MODULE_REFERENCE!>A<!>
<!WRONG_ASYNC_MODULE_REFERENCE, WRONG_ASYNC_MODULE_REFERENCE!>A<!>.cfn()
}
suspend fun testSuspend() {
O
O.fn()
A
A.cfn()
}
val prop = <!WRONG_ASYNC_MODULE_REFERENCE!>O<!>
val prop2 = <!WRONG_ASYNC_MODULE_REFERENCE!>O<!>.fn()
val prop3 = <!WRONG_ASYNC_MODULE_REFERENCE!>A<!>
val prop4 = <!WRONG_ASYNC_MODULE_REFERENCE, WRONG_ASYNC_MODULE_REFERENCE!>A<!>.cfn()
fun testIntrinsics() {
runIfLoaded {
O
O.fn()
A
A.cfn()
}
runIfLoaded({
run {
O
O.fn()
A
A.cfn()
} }, {})
}

View File

@@ -0,0 +1,37 @@
// -- Module: <lib> --
package
public final class A {
public constructor A()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public companion object Companion {
private constructor Companion()
public final fun cfn(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
public object O {
private constructor O()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun fn(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
// -- Module: <main> --
package
public val prop: O
public val prop2: kotlin.Unit
public val prop3: A.Companion
public val prop4: kotlin.Unit
public fun test(): kotlin.Unit
public fun testIntrinsics(): kotlin.Unit
public suspend fun testSuspend(): kotlin.Unit

View File

@@ -0,0 +1,32 @@
// MODULE: lib
// FILE: lib.kt
fun fn() {}
val p = 0
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: lib
fun test() {
<!WRONG_ASYNC_MODULE_REFERENCE!>fn()<!>
<!WRONG_ASYNC_MODULE_REFERENCE!>p<!>
}
suspend fun testSuspend() {
fn()
p
}
fun testIntrinsics() {
runIfLoaded {
fn()
p
}
runIfLoaded({ run {
fn()
p
} }, {})
}

View File

@@ -0,0 +1,13 @@
// -- Module: <lib> --
package
public val p: kotlin.Int = 0
public fun fn(): kotlin.Unit
// -- Module: <main> --
package
public fun test(): kotlin.Unit
public fun testIntrinsics(): kotlin.Unit
public suspend fun testSuspend(): kotlin.Unit

View File

@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.util.*
import java.util.regex.Pattern
abstract class AbstractDiagnosticsTestWithJsStdLib : AbstractDiagnosticsTest() {
private var lazyConfig: Lazy<JsConfig>? = lazy(LazyThreadSafetyMode.NONE) {
@@ -56,6 +57,8 @@ abstract class AbstractDiagnosticsTestWithJsStdLib : AbstractDiagnosticsTest() {
// TODO: support LANGUAGE directive in JS diagnostic tests
moduleTrace.record<ModuleDescriptor, ModuleKind>(MODULE_KIND, moduleContext.module, getModuleKind(files))
config.configuration.languageVersionSettings = languageVersionSettings
val module = moduleContext.module as ModuleDescriptorImpl
module.setLazyDependencies(getAsyncDependencies(module, files).toSet())
return TopDownAnalyzerFacadeForJS.analyzeFilesWithGivenTrace(files, moduleTrace, moduleContext, config.configuration)
}
@@ -78,6 +81,25 @@ abstract class AbstractDiagnosticsTestWithJsStdLib : AbstractDiagnosticsTest() {
return kind
}
private val ASYNC_IMPORT = Pattern.compile("^// *ASYNC_IMPORT: *(.+)$", Pattern.MULTILINE)
private fun getAsyncDependencies(module: ModuleDescriptorImpl, ktFiles: List<KtFile>): List<ModuleDescriptorImpl> {
val dependencies = module.allDependencyModules.associateBy { it.name.asString() }
val result = mutableListOf<ModuleDescriptorImpl>()
for (file in ktFiles) {
val text = file.text
val matcher = ASYNC_IMPORT.matcher(text)
if (matcher.find()) {
result += matcher.group(1).split(',').map { dependencies["<${it.trim()}>"] as ModuleDescriptorImpl }
}
}
return result
}
override fun getAdditionalDependencies(module: ModuleDescriptorImpl): List<ModuleDescriptorImpl> =
config.moduleDescriptors

View File

@@ -115,7 +115,8 @@ abstract class AbstractIrGeneratorTestCase : CodegenTestCase() {
TopDownAnalyzerFacadeForJS.analyzeFiles(
ktFilesToAnalyze, environment.project, environment.configuration,
moduleDescriptors = emptyList(),
friendModuleDescriptors = emptyList()
friendModuleDescriptors = emptyList(),
lazyModuleDependencies = emptyList()
),
psi2ir, ktFilesToAnalyze, GeneratorExtensions()
)

View File

@@ -461,6 +461,49 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
}
}
@TestMetadata("compiler/testData/diagnostics/testsWithJsStdLib/lazyImports")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LazyImports extends AbstractDiagnosticsTestWithJsStdLib {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInLazyImports() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithJsStdLib/lazyImports"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("callableRef.kt")
public void testCallableRef() throws Exception {
runTest("compiler/testData/diagnostics/testsWithJsStdLib/lazyImports/callableRef.kt");
}
@TestMetadata("constructors.kt")
public void testConstructors() throws Exception {
runTest("compiler/testData/diagnostics/testsWithJsStdLib/lazyImports/constructors.kt");
}
@TestMetadata("inheritance.kt")
public void testInheritance() throws Exception {
runTest("compiler/testData/diagnostics/testsWithJsStdLib/lazyImports/inheritance.kt");
}
@TestMetadata("memberCall.kt")
public void testMemberCall() throws Exception {
runTest("compiler/testData/diagnostics/testsWithJsStdLib/lazyImports/memberCall.kt");
}
@TestMetadata("objectReference.kt")
public void testObjectReference() throws Exception {
runTest("compiler/testData/diagnostics/testsWithJsStdLib/lazyImports/objectReference.kt");
}
@TestMetadata("simpleCall.kt")
public void testSimpleCall() throws Exception {
runTest("compiler/testData/diagnostics/testsWithJsStdLib/lazyImports/simpleCall.kt");
}
}
@TestMetadata("compiler/testData/diagnostics/testsWithJsStdLib/module")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)

View File

@@ -61,4 +61,6 @@ interface ModuleDescriptor : DeclarationDescriptor {
val isValid: Boolean
fun assertValid()
fun shouldLazyLoad(targetModule: ModuleDescriptor): Boolean = false
}

View File

@@ -124,6 +124,21 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
setDependencies(ModuleDependenciesImpl(descriptors, friends, emptyList()))
}
fun setDependencies(descriptors: List<ModuleDescriptorImpl>, friends: Set<ModuleDescriptorImpl>, lazyDependencies: Set<ModuleDescriptorImpl>) {
setDependencies(ModuleDependenciesImpl(descriptors, friends, emptyList()))
this.lazyDependencies = lazyDependencies
}
fun setLazyDependencies(lazyDependencies: Set<ModuleDescriptorImpl>) {
this.lazyDependencies = lazyDependencies
}
private var lazyDependencies: Set<ModuleDescriptorImpl> = emptySet()
override fun shouldLazyLoad(targetModule: ModuleDescriptor): Boolean {
return targetModule in lazyDependencies
}
override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor): Boolean {
if (this == targetModule) return true
if (targetModule in dependencies!!.modulesWhoseInternalsAreVisible) return true

View File

@@ -53,6 +53,11 @@ public class ErrorUtils {
static {
ERROR_MODULE = new ModuleDescriptor() {
@Override
public boolean shouldLazyLoad(@NotNull ModuleDescriptor targetModule) {
return false;
}
@Nullable
@Override
public <T> T getCapability(@NotNull Capability<T> capability) {

View File

@@ -17,16 +17,12 @@
package org.jetbrains.kotlin.js.backend.ast
class JsImportedModule @JvmOverloads constructor(
class JsImportedModule(
val externalName: String,
var internalName: JsName,
val plainReference: JsExpression?,
val relativeRequirePath: Boolean = false
val plainReference: JsExpression?
) {
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?)

View File

@@ -44,6 +44,7 @@ abstract class AbstractTopDownAnalyzerFacadeForJS {
configuration: CompilerConfiguration,
moduleDescriptors: List<ModuleDescriptorImpl>,
friendModuleDescriptors: List<ModuleDescriptorImpl>,
lazyModuleDependencies: List<ModuleDescriptorImpl>,
thisIsBuiltInsModule: Boolean = false,
customBuiltInsModule: ModuleDescriptorImpl? = null
): JsAnalysisResult {
@@ -73,7 +74,7 @@ abstract class AbstractTopDownAnalyzerFacadeForJS {
}
val dependencies = mutableSetOf(context.module) + moduleDescriptors + builtIns.builtInsModule
context.module.setDependencies(dependencies.toList(), friendModuleDescriptors.toSet())
context.module.setDependencies(dependencies.toList(), friendModuleDescriptors.toSet(), lazyModuleDependencies.toSet())
val moduleKind = configuration.get(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
@@ -147,6 +148,6 @@ object TopDownAnalyzerFacadeForJS : AbstractTopDownAnalyzerFacadeForJS() {
config: JsConfig
): JsAnalysisResult {
config.init()
return analyzeFiles(files, config.project, config.configuration, config.moduleDescriptors, config.friendModuleDescriptors)
return analyzeFiles(files, config.project, config.configuration, config.moduleDescriptors, config.friendModuleDescriptors, config.lazyDependencyDescriptors)
}
}

View File

@@ -86,4 +86,7 @@ public class JSConfigurationKeys {
public static final CompilerConfigurationKey<Boolean> DISABLE_FAKE_OVERRIDE_VALIDATOR =
CompilerConfigurationKey.create("disable IR fake override validator");
public static final CompilerConfigurationKey<List<String>> ASYNC_IMPORTS =
CompilerConfigurationKey.create("dependencies, loaded asynchronously");
}

View File

@@ -62,6 +62,7 @@ public class JsConfig {
private List<ModuleDescriptorImpl> moduleDescriptors;
private List<ModuleDescriptorImpl> friendModuleDescriptors;
private List<ModuleDescriptorImpl> lazyDependencyDescriptors;
private boolean initialized = false;
@@ -279,6 +280,12 @@ public class JsConfig {
return friendModuleDescriptors;
}
@NotNull
public List<ModuleDescriptorImpl> getLazyDependencyDescriptors() {
init();
return lazyDependencyDescriptors;
}
public void init() {
if (!initialized) {
JsConfig.Reporter reporter = new Reporter() {
@@ -298,6 +305,17 @@ public class JsConfig {
if (friendModuleDescriptors == null) {
friendModuleDescriptors = CollectionsKt.map(friends, this::createModuleDescriptor);
}
if (lazyDependencyDescriptors == null) {
Set<String> asyncImports = new HashSet<>(configuration.getList(JSConfigurationKeys.ASYNC_IMPORTS));
lazyDependencyDescriptors = CollectionsKt.filter(moduleDescriptors, (ModuleDescriptorImpl m) -> asyncImports.contains(m.getName().asString()));
if (lazyDependencyDescriptors.size() != asyncImports.size()) {
List<String> moduleNames = CollectionsKt.map(moduleDescriptors, (ModuleDescriptorImpl m) -> m.getName().asString());
List<String> wrongModules = CollectionsKt.filter(asyncImports, (String name) -> !moduleNames.contains(name));
throw new Error("Cannot build the lazy loaded module set. Available modules: " + moduleNames + "; wrong async module references: " + wrongModules);
}
}
}
private final IdentityHashMap<KotlinJavascriptMetadata, ModuleDescriptorImpl> factoryMap = new IdentityHashMap<>();

View File

@@ -17,22 +17,25 @@ import org.jetbrains.kotlin.resolve.deprecation.CoroutineCompatibilitySupport
import org.jetbrains.kotlin.types.DynamicTypesAllowed
object JsPlatformConfigurator : PlatformConfiguratorBase(
DynamicTypesAllowed(),
additionalDeclarationCheckers = listOf(
NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(),
JsNameChecker, JsModuleChecker, JsExternalFileChecker,
JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker,
JsRuntimeAnnotationChecker,
JsDynamicDeclarationChecker,
JsExportAnnotationChecker,
JsExportDeclarationChecker
),
additionalCallCheckers = listOf(
JsModuleCallChecker,
JsDynamicCallChecker,
JsDefinedExternallyCallChecker,
),
identifierChecker = JsIdentifierChecker
DynamicTypesAllowed(),
additionalDeclarationCheckers = listOf(
NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(),
JsNameChecker, JsModuleChecker, JsExternalFileChecker,
JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker,
JsRuntimeAnnotationChecker,
JsDynamicDeclarationChecker,
JsExportAnnotationChecker,
JsExportDeclarationChecker,
JsLazyModuleDeclarationChecker,
),
additionalCallCheckers = listOf(
JsModuleCallChecker,
JsDynamicCallChecker,
JsDefinedExternallyCallChecker,
JsLazyModuleReferenceChecker,
),
identifierChecker = JsIdentifierChecker,
additionalClassifierUsageCheckers = listOf(JsLazyModuleClassifierUsageChecker)
) {
override fun configureModuleComponents(container: StorageComponentContainer) {
container.useInstance(NameSuggestion())

View File

@@ -104,6 +104,8 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
put(ErrorsJs.WRONG_EXPORTED_DECLARATION, "Declaration of such kind ({0}) can't be exported to JS", Renderers.STRING)
put(ErrorsJs.NON_EXPORTABLE_TYPE, "Exported declaration uses non-exportable {0} type: {1}", Renderers.STRING, RENDER_TYPE)
put(ErrorsJs.WRONG_ASYNC_MODULE_REFERENCE, "Wrong async module reference", Renderers.STRING)
this
}
}

View File

@@ -109,6 +109,8 @@ public interface ErrorsJs {
DiagnosticFactory1<KtExpression, String> WRONG_EXPORTED_DECLARATION = DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory2<PsiElement, String, KotlinType> NON_EXPORTABLE_TYPE = DiagnosticFactory2.create(WARNING, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory1<PsiElement, String> WRONG_ASYNC_MODULE_REFERENCE = DiagnosticFactory1.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{

View File

@@ -0,0 +1,119 @@
/*
* 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.js.resolve.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypes
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.checkers.findEnclosingSuspendFunction
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.checkers.*
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object JsLazyModuleReferenceChecker : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val currentModule = context.moduleDescriptor as? ModuleDescriptorImpl ?: return
val targetModule = resolvedCall.resultingDescriptor.original.module
if (currentModule.shouldLazyLoad(targetModule)) {
val callElement = resolvedCall.call.callElement as? KtExpression ?: return
if (isLazyModuleAccessRestricted(context, callElement)) {
val descriptor = resolvedCall.resultingDescriptor.original
if (descriptor.dispatchReceiverParameter == null) {
context.trace.report(ErrorsJs.WRONG_ASYNC_MODULE_REFERENCE.on(callElement, "cannot reference async module"))
}
}
}
}
}
object JsLazyModuleClassifierUsageChecker: ClassifierUsageChecker {
override fun check(targetDescriptor: ClassifierDescriptor, element: PsiElement, context: ClassifierUsageCheckerContext) {
val currentModule = context.moduleDescriptor as? ModuleDescriptorImpl ?: return
val targetModule = targetDescriptor.module
if (currentModule.shouldLazyLoad(targetModule)) {
if (!isInsideSpecialLambda(context.trace.bindingContext, element) && !isInsidgeSuspendFunction(context.trace.bindingContext, element)) {
if (element.getParentOfType<KtExpression>(true) is KtDotQualifiedExpression && targetDescriptor is ClassDescriptor) {
context.trace.report(ErrorsJs.WRONG_ASYNC_MODULE_REFERENCE.on(element, "cannot reference async module"))
}
}
}
}
}
object JsLazyModuleDeclarationChecker : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
val currentModule = context.moduleDescriptor as? ModuleDescriptorImpl ?: return
if (descriptor is ClassDescriptor &&
descriptor.defaultType.immediateSupertypes()
.any { it.constructor.declarationDescriptor?.module?.let { currentModule.shouldLazyLoad(it) } ?: false }
) {
context.trace.report(ErrorsJs.WRONG_ASYNC_MODULE_REFERENCE.on(declaration, "cannot inherit from an async module declaration"))
}
}
}
private fun isInsidgeSuspendFunction(bindingContext: BindingContext, e: PsiElement): Boolean {
generateSequence(e) { it.getParentOfType<KtFunction>(true) }.forEach { p ->
val d = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, p.getParentOfType<KtFunction>(true)) as? FunctionDescriptor
if (d?.isSuspend == true) return true
}
return false
}
private fun isLazyModuleAccessRestricted(context: CallCheckerContext, e: KtExpression): Boolean {
return findEnclosingSuspendFunction(context, e) == null && !isInsideSpecialLambda(context.trace.bindingContext, e)
}
private val AllowAsyncRefsFqn = FqName("kotlin.js.AllowAsyncRefs")
private fun isInsideSpecialLambda(bindingContext: BindingContext, e: PsiElement): Boolean {
var lastArgument: KtValueArgument? = null
generateSequence(e) { it.getParentOfTypes(true, KtValueArgument::class.java, KtCallExpression::class.java) }.forEach { p ->
when (p) {
is KtValueArgument -> lastArgument = p
is KtCallExpression -> {
val resolvedCall = p.getResolvedCall(bindingContext)
if (resolvedCall?.resultingDescriptor is FunctionDescriptor) {
for ((d, v) in resolvedCall.valueArguments.entries) {
val argument = v.safeAs<ExpressionValueArgument>()?.valueArgument ?: continue
if (lastArgument != argument) continue
if (d.annotations.any { it.fqName == AllowAsyncRefsFqn }) return true
}
}
}
}
}
return false
}

View File

@@ -436,6 +436,7 @@ class GenerateIrRuntime {
configuration,
emptyList(),
friendModuleDescriptors = emptyList(),
lazyModuleDependencies = emptyList(),
thisIsBuiltInsModule = true,
customBuiltInsModule = null
)

View File

@@ -165,6 +165,7 @@ abstract class BasicBoxTest(
val dependencies = module.dependenciesSymbols.map { modules[it]?.outputFileName(outputDir) + ".meta.js" }
val allDependencies = module.allTransitiveDependencies().map { modules[it]?.outputFileName(outputDir) + ".meta.js" }
val friends = module.friendsSymbols.map { modules[it]?.outputFileName(outputDir) + ".meta.js" }
val asyncImports = module.asyncDependencies
val outputFileName = module.outputFileName(outputDir) + ".js"
val dceOutputFileName = module.outputFileName(dceOutputDir) + ".js"
@@ -172,7 +173,7 @@ abstract class BasicBoxTest(
val isMainModule = mainModuleName == module.name
generateJavaScriptFile(
testFactory.tmpDir,
file.parent, module, outputFileName, dceOutputFileName, pirOutputFileName, dependencies, allDependencies, friends, modules.size > 1,
file.parent, module, outputFileName, dceOutputFileName, pirOutputFileName, dependencies, allDependencies, friends, asyncImports, modules.size > 1,
!SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), outputPrefixFile, outputPostfixFile,
actualMainCallParameters, testPackage, testFunction, needsFullIrRuntime, isMainModule, expectActualLinker, skipDceDriven, splitPerModule
)
@@ -386,6 +387,7 @@ abstract class BasicBoxTest(
dependencies: List<String>,
allDependencies: List<String>,
friends: List<String>,
asyncImports: List<String>,
multiModule: Boolean,
remap: Boolean,
outputPrefixFile: File?,
@@ -413,7 +415,7 @@ abstract class BasicBoxTest(
val psiFiles = createPsiFiles(allSourceFiles.sortedBy { it.canonicalPath }.map { it.canonicalPath })
val sourceDirs = (testFiles + additionalFiles).map { File(it).parent }.distinct()
val config = createConfig(sourceDirs, module, dependencies, allDependencies, friends, multiModule, tmpDir, incrementalData = null, expectActualLinker = expectActualLinker)
val config = createConfig(sourceDirs, module, dependencies, allDependencies, friends, asyncImports, multiModule, tmpDir, incrementalData = null, expectActualLinker = expectActualLinker)
val outputFile = File(outputFileName)
val dceOutputFile = File(dceOutputFileName)
val pirOutputFile = File(pirOutputFileName)
@@ -468,7 +470,7 @@ abstract class BasicBoxTest(
.sortedBy { it.canonicalPath }
.map { sourceToTranslationUnit[it]!! }
val recompiledConfig = createConfig(sourceDirs, module, dependencies, allDependencies, friends, multiModule, tmpDir, incrementalData, expectActualLinker)
val recompiledConfig = createConfig(sourceDirs, module, dependencies, allDependencies, friends, emptyList(), multiModule, tmpDir, incrementalData, expectActualLinker)
val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js")
translateFiles(
@@ -683,7 +685,7 @@ abstract class BasicBoxTest(
private fun createPsiFiles(fileNames: List<String>): List<KtFile> = fileNames.map(this::createPsiFile)
private fun createConfig(
sourceDirs: List<String>, module: TestModule, dependencies: List<String>, allDependencies: List<String>, friends: List<String>,
sourceDirs: List<String>, module: TestModule, dependencies: List<String>, allDependencies: List<String>, friends: List<String>, asyncImports: List<String>,
multiModule: Boolean, tmpDir: File, incrementalData: IncrementalData?, expectActualLinker: Boolean
): JsConfig {
val configuration = environment.configuration.copy()
@@ -703,6 +705,7 @@ abstract class BasicBoxTest(
configuration.put(JSConfigurationKeys.LIBRARIES, libraries)
configuration.put(JSConfigurationKeys.TRANSITIVE_LIBRARIES, allDependencies)
configuration.put(JSConfigurationKeys.FRIEND_PATHS, friends)
configuration.put(JSConfigurationKeys.ASYNC_IMPORTS, asyncImports)
configuration.put(CommonConfigurationKeys.MODULE_NAME, module.name.removeSuffix(OLD_MODULE_SUFFIX))
configuration.put(JSConfigurationKeys.MODULE_KIND, module.moduleKind)
@@ -889,6 +892,21 @@ abstract class BasicBoxTest(
var sourceMapSourceEmbedding = SourceMapSourceEmbedding.NEVER
val hasFilesToRecompile get() = files.any { it.recompile }
val asyncDependencies: List<String>
get() {
val result = mutableListOf<String>()
files.forEach {
val text = File(it.fileName).readText()
val matcher = ASYNC_IMPORT.matcher(text)
if (matcher.find()) {
result += matcher.group(1).split(',').map { it.trim() }
}
}
return result
}
}
override fun createEnvironment() =
@@ -933,6 +951,8 @@ abstract class BasicBoxTest(
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 SPLIT_PER_MODULE = Pattern.compile("^// *SPLIT_PER_MODULE *$", Pattern.MULTILINE)
private val ASYNC_IMPORT = Pattern.compile("^// *ASYNC_IMPORT: *(.+)$", Pattern.MULTILINE)
@JvmStatic
protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn")

View File

@@ -101,6 +101,8 @@ abstract class BasicIrBoxTest(
val transitiveLibraries = config.configuration[JSConfigurationKeys.TRANSITIVE_LIBRARIES]!!.map { File(it).name }
val lazyImports = config.configuration[JSConfigurationKeys.ASYNC_IMPORTS]!!
val allKlibPaths = (runtimeKlibs + transitiveLibraries.map {
compilationCache[it] ?: error("Can't find compiled module for dependency $it")
}).map { File(it).absolutePath }
@@ -143,7 +145,8 @@ abstract class BasicIrBoxTest(
generateFullJs = true,
generateDceJs = runIrDce,
es6mode = runEs6Mode,
multiModule = splitPerModule || perModule
multiModule = splitPerModule || perModule,
lazyImportName = "import2" // avoid breaking test runner
)
compiledModule.jsCode!!.writeTo(outputFile, config)
@@ -169,7 +172,8 @@ abstract class BasicIrBoxTest(
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
dceDriven = true,
es6mode = runEs6Mode,
multiModule = splitPerModule || perModule
multiModule = splitPerModule || perModule,
lazyImportName = "import2" // avoid breaking test runner
).jsCode!!.writeTo(pirOutputFile, config)
}
} else {

View File

@@ -893,6 +893,34 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
public void testTopLevelProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt");
}
@TestMetadata("js/js.translator/testData/box/crossModuleRefIR/lazyLoading")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LazyLoading extends AbstractIrBoxJsES6Test {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath);
}
public void testAllFilesPresentInLazyLoading() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR/lazyLoading"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
}
@TestMetadata("callableReference.kt")
public void testCallableReference() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lazyLoading/callableReference.kt");
}
@TestMetadata("fn.kt")
public void testFn() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lazyLoading/fn.kt");
}
@TestMetadata("prop.kt")
public void testProp() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lazyLoading/prop.kt");
}
}
}
@TestMetadata("js/js.translator/testData/box/dataClass")

View File

@@ -893,6 +893,34 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
public void testTopLevelProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt");
}
@TestMetadata("js/js.translator/testData/box/crossModuleRefIR/lazyLoading")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LazyLoading extends AbstractIrBoxJsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
public void testAllFilesPresentInLazyLoading() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR/lazyLoading"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@TestMetadata("callableReference.kt")
public void testCallableReference() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lazyLoading/callableReference.kt");
}
@TestMetadata("fn.kt")
public void testFn() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lazyLoading/fn.kt");
}
@TestMetadata("prop.kt")
public void testProp() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/lazyLoading/prop.kt");
}
}
}
@TestMetadata("js/js.translator/testData/box/dataClass")

View File

@@ -893,6 +893,19 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
public void testTopLevelProperty() throws Exception {
runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt");
}
@TestMetadata("js/js.translator/testData/box/crossModuleRefIR/lazyLoading")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LazyLoading extends AbstractBoxJsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
}
public void testAllFilesPresentInLazyLoading() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR/lazyLoading"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true);
}
}
}
@TestMetadata("js/js.translator/testData/box/dataClass")

View File

@@ -0,0 +1,38 @@
// SPLIT_PER_MODULE
// DONT_TARGET_EXACT_BACKEND: JS
// MODULE: lib
// FILE: lib.kt
// MODULE_KIND: COMMON_JS
package lib
fun fn() = "OK"
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: <lib>
// MODULE_KIND: COMMON_JS
// CALL_MAIN
package main
import lib.*
val beforeLoad = runIfLoaded { ::fn }
var fnRef = { "FAIL" }
suspend fun main() {
fnRef = ::fn
}
fun box(): String {
if (beforeLoad?.invoke() != null) return "fail 1"
if (fnRef() != "OK") return "fail 2"
val afterLoad = runIfLoaded { ::fn }
if (afterLoad?.invoke() != "OK") return "fail 3: $afterLoad"
return "OK"
}

View File

@@ -0,0 +1,38 @@
// SPLIT_PER_MODULE
// DONT_TARGET_EXACT_BACKEND: JS
// MODULE: lib
// FILE: lib.kt
// MODULE_KIND: COMMON_JS
package lib
fun fn() = "OK"
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: <lib>
// MODULE_KIND: COMMON_JS
// CALL_MAIN
package main
import lib.*
val beforeLoad = runIfLoaded { fn() }
var fnCopy = "FAIL"
suspend fun main() {
fnCopy = fn()
}
fun box(): String {
if (beforeLoad != null) return "fail 1: $beforeLoad"
if (fnCopy != "OK") return "fail 2: $fnCopy"
val afterLoad = runIfLoaded { fn() }
if (afterLoad != "OK") return "fail 3: $afterLoad"
return "OK"
}

View File

@@ -0,0 +1,38 @@
// SPLIT_PER_MODULE
// DONT_TARGET_EXACT_BACKEND: JS
// MODULE: lib
// FILE: lib.kt
// MODULE_KIND: COMMON_JS
package lib
var prop = "OK"
// MODULE: main(lib)
// FILE: main.kt
// ASYNC_IMPORT: <lib>
// MODULE_KIND: COMMON_JS
// CALL_MAIN
package main
import lib.*
val beforeLoad = runIfLoaded { prop }
var propCopy = "FAIL"
suspend fun main() {
propCopy = prop
}
fun box(): String {
if (beforeLoad != null) return "fail 1"
if (propCopy != "OK") return "fail 2: $propCopy"
val afterLoad = runIfLoaded { prop }
if (afterLoad != "OK") return "fail 3: $afterLoad"
return "OK"
}

View File

@@ -9,6 +9,10 @@ function require(moduleId) {
return emulatedModules[moduleId];
}
function import2(moduleId) {
return Promise.resolve({ default: emulatedModules[moduleId] })
}
var $kotlin_test_internal$ = {
require: require,
beginModule : function () {
@@ -16,6 +20,7 @@ var $kotlin_test_internal$ = {
},
endModule : function(moduleId) {
emulatedModules[moduleId] = module.exports;
emulatedModules['./' + moduleId + '.js'] = module.exports;
},
setModuleId: function(moduleId) {
currentModuleId = moduleId;
@@ -39,5 +44,6 @@ function define(moduleId, dependencies, body) {
if (result != null) {
emulatedModules[moduleId] = result;
}
emulatedModules['./' + moduleId + '.js'] = emulatedModules[moduleId];
}
define.amd = {};

View File

@@ -5,6 +5,10 @@
package kotlin
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@PublishedApi
internal fun throwUninitializedPropertyAccessException(name: String): Nothing =
throw UninitializedPropertyAccessException("lateinit property $name has not been initialized")
@@ -31,4 +35,38 @@ internal fun THROW_IAE(msg: String): Nothing {
throw IllegalArgumentException(msg)
}
internal fun <T:Any> ensureNotNull(v: T?): T = if (v == null) THROW_NPE() else v
internal fun <T:Any> ensureNotNull(v: T?): T = if (v == null) THROW_NPE() else v
internal suspend fun loadModule(forModule: String, module: String) {
val loaderInfo: LoaderInfo = moduleInitializers["$forModule:$module"] ?: return
if (loaderInfo.loaded) return
suspendCoroutine<Unit> { cont ->
loaderInfo.loader().then({ cont.resume(Unit) }, { t -> cont.resumeWithException(Error("Could not load module $module", t))})
}
}
private val moduleInitializers = js("{}")
internal class LoaderInfo(
val loader: () -> dynamic,
var loaded: Boolean = false
)
internal fun setModuleLoader(forModule: String, module: String, loader: () -> dynamic) {
moduleInitializers["$forModule:$module"] = LoaderInfo(loader)
}
internal fun isLoaded(forModule: String, module: String, triggerLoading: Boolean): Boolean {
val loaderInfo: LoaderInfo = moduleInitializers["$forModule:$module"] ?: return true
val loaded = loaderInfo.loaded
if (triggerLoading && !loaded) {
loaderInfo.loader() // should run async
}
return loaded
}
internal fun reportLoaded(forModule: String, module: String) {
val loaderInfo: LoaderInfo = moduleInitializers["$forModule:$module"] ?: return
loaderInfo.loaded = true
}

View File

@@ -0,0 +1,58 @@
/*
* 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 kotlin.js
import kotlin.coroutines.js.internal.EmptyContinuation
import kotlin.coroutines.startCoroutine
internal object ModuleNotLoaded
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class AllowAsyncRefs
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class TriggerModuleLoading
internal fun <T> runIfLoadedImpl(fn: () -> T?, fallback: () -> T): T {
val result = fn().asDynamic()
if (ModuleNotLoaded === result) return fallback()
return result.unsafeCast<T>()
}
/**
* PROTOTYPE
*/
fun <T> runIfLoaded(@AllowAsyncRefs fn: () -> T, fallback: () -> T): T = runIfLoadedImpl(fn, fallback)
/**
* PROTOTYPE
*/
fun <T> runIfLoadedAndTriggerLoading(@AllowAsyncRefs @TriggerModuleLoading fn: () -> T, fallback: () -> T): T = runIfLoadedImpl(fn, fallback)
/**
* PROTOTYPE
*/
fun <T> runIfLoaded(@AllowAsyncRefs fn: () -> T): T? = runIfLoadedImpl(fn) { null }
/**
* PROTOTYPE
*/
fun <T> runIfLoadedAndTriggerLoading(@AllowAsyncRefs @TriggerModuleLoading fn: () -> T): T? = runIfLoadedImpl(fn) { null }
/**
* PROTOTYPE
*
* We need a way to run simple suspend code without kotlinx.coroutines
*/
fun launchAndForget(fn: suspend () -> Unit) {
fn.startCoroutine(EmptyContinuation)
}