diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index 5b9e203dfc4..60e223d180a 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -151,6 +151,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() { ) var irModuleName: String? by NullableStringFreezableVar(null) + @Argument(value = "-Xir-legacy-property-access", description = "Force property access via JS properties (requires -Xir-export-all)") + var irLegacyPropertyAccess: Boolean by FreezableVar(false) + @Argument(value = "-Xir-per-module", description = "Splits generated .js per-module") var irPerModule: Boolean by FreezableVar(false) diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 6f27f4362bb..67534c81b0c 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -273,9 +273,9 @@ class K2JsIrCompiler : CLICompiler() { multiModule = arguments.irPerModule, relativeRequirePath = true, propertyLazyInitialization = arguments.irPropertyLazyInitialization, + legacyPropertyAccess = arguments.irLegacyPropertyAccess, ) - val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!! outputFile.writeText(jsCode.mainModule) jsCode.dependencies.forEach { (name, content) -> diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt index 591b559d809..14197879a7b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt @@ -94,6 +94,11 @@ private fun buildRoots(modules: Iterable, context: JsIrBackend rootDeclarations += dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner } + if (context.legacyPropertyAccess) { + rootDeclarations += context.intrinsics.safePropertyGet.owner + rootDeclarations += context.intrinsics.safePropertySet.owner + } + JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction -> rootDeclarations += mainFunction if (mainFunction.isSuspend) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 4327be9078d..caba6e281a5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -315,6 +315,10 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC val readSharedBox = defineReadSharedBox() val writeSharedBox = defineWriteSharedBox() + + val safePropertyGet = getInternalFunction("safePropertyGet") + val safePropertySet = getInternalFunction("safePropertySet") + val jsUndefined = defineJsUndefinedIntrinsic() // Helpers: diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index 863d37e95d8..87d2be490a5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -47,6 +47,7 @@ class JsIrBackendContext( override val es6mode: Boolean = false, val dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null, val propertyLazyInitialization: Boolean = false, + val legacyPropertyAccess: Boolean = false, ) : JsCommonBackendContext { val fileToInitializationFuns: MutableMap = mutableMapOf() val fileToInitializerPureness: MutableMap = mutableMapOf() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index 7880c35cfe7..985a5323594 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -52,6 +52,7 @@ fun compile( multiModule: Boolean = false, relativeRequirePath: Boolean = false, propertyLazyInitialization: Boolean, + legacyPropertyAccess: Boolean = false, ): CompilerResult { val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = loadIr(project, mainModule, analyzer, configuration, allDependencies, friendDependencies, irFactory) @@ -73,6 +74,7 @@ fun compile( es6mode = es6mode, dceRuntimeDiagnostic = dceRuntimeDiagnostic, propertyLazyInitialization = propertyLazyInitialization, + legacyPropertyAccess = legacyPropertyAccess ) // Load declarations referenced during `context` initialization diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index 723ba2bb4e9..d0947108e68 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -110,7 +110,7 @@ fun translateCall( // @JsName-annotated external property accessors are translated as function calls if (function.getJsName() == null) { val property = function.correspondingPropertySymbol?.owner - if (property != null && property.isEffectivelyExternal()) { + if (property != null && (property.isEffectivelyExternal())) { val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver) return when (function) { property.getter -> nameRef @@ -220,7 +220,26 @@ fun translateCall( } } } else { - JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments) + val defaultResult = JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments) + + val alternativeResult = if (jsDispatchReceiver != null && jsExtensionReceiver == null && context.staticContext.backendContext.legacyPropertyAccess) { + val property = function.correspondingPropertySymbol?.owner + if (property != null) { + val propertyName = context.getNameForProperty(property) + val args = mutableListOf(jsDispatchReceiver, JsStringLiteral(symbolName.ident), JsStringLiteral(propertyName.ident)) + val fnName = when (function) { + property.getter -> context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertyGet.owner) + property.setter -> { + args += arguments + context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertySet.owner) + } + else -> error("Function must be an accessor of corresponding property") + } + JsInvocation(fnName.makeRef(), args) + } else null + } else null + + alternativeResult ?: defaultResult } } diff --git a/libraries/stdlib/js-ir/runtime/kotlinJsHacks.kt b/libraries/stdlib/js-ir/runtime/kotlinJsHacks.kt index 2754a6c4f60..c4d6699ffb6 100644 --- a/libraries/stdlib/js-ir/runtime/kotlinJsHacks.kt +++ b/libraries/stdlib/js-ir/runtime/kotlinJsHacks.kt @@ -13,6 +13,17 @@ internal fun > enumValuesIntrinsic(): Array = internal fun > enumValueOfIntrinsic(@Suppress("UNUSED_PARAMETER") name: String): T = throw IllegalStateException("Should be replaced by compiler") +@PublishedApi +internal fun safePropertyGet(self: dynamic, getterName: String, propName: String): dynamic { + val getter = self[getterName] + return if (getter != null) getter.call(self) else self[propName] +} + +@PublishedApi +internal fun safePropertySet(self: dynamic, setterName: String, propName: String, value: dynamic) { + val setter = self[setterName] + if (setter != null) setter.call(self, value) else self[propName] = value +} /** * Implements annotated function in JavaScript.