[JS IR] add a flag which enable safe property access

If an accessor is not available at runtime we fall back
to the property access.

This is useful in cases when JS objects are casted to Kotlin
classes implicitly. This pattern did work in the old BE, which
lead to a significant amount of code which doesn't work anymore.
This commit is contained in:
Anton Bannykh
2020-09-04 13:30:39 +03:00
committed by TeamCityServer
parent 8b18818bcc
commit 6633a9edc0
8 changed files with 48 additions and 3 deletions
@@ -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)
@@ -273,9 +273,9 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
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) ->
@@ -94,6 +94,11 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, 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) {
@@ -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:
@@ -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<IrFile, IrSimpleFunction?> = mutableMapOf()
val fileToInitializerPureness: MutableMap<IrFile, Boolean> = mutableMapOf()
@@ -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
@@ -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
}
}
@@ -13,6 +13,17 @@ internal fun <T : Enum<T>> enumValuesIntrinsic(): Array<T> =
internal fun <T : Enum<T>> 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.