[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:
committed by
TeamCityServer
parent
8b18818bcc
commit
6633a9edc0
+3
@@ -151,6 +151,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
|
|||||||
)
|
)
|
||||||
var irModuleName: String? by NullableStringFreezableVar(null)
|
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")
|
@Argument(value = "-Xir-per-module", description = "Splits generated .js per-module")
|
||||||
var irPerModule: Boolean by FreezableVar(false)
|
var irPerModule: Boolean by FreezableVar(false)
|
||||||
|
|
||||||
|
|||||||
@@ -273,9 +273,9 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
|||||||
multiModule = arguments.irPerModule,
|
multiModule = arguments.irPerModule,
|
||||||
relativeRequirePath = true,
|
relativeRequirePath = true,
|
||||||
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
||||||
|
legacyPropertyAccess = arguments.irLegacyPropertyAccess,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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.mainModule)
|
outputFile.writeText(jsCode.mainModule)
|
||||||
jsCode.dependencies.forEach { (name, content) ->
|
jsCode.dependencies.forEach { (name, content) ->
|
||||||
|
|||||||
@@ -94,6 +94,11 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackend
|
|||||||
rootDeclarations += dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner
|
rootDeclarations += dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.legacyPropertyAccess) {
|
||||||
|
rootDeclarations += context.intrinsics.safePropertyGet.owner
|
||||||
|
rootDeclarations += context.intrinsics.safePropertySet.owner
|
||||||
|
}
|
||||||
|
|
||||||
JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction ->
|
JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction ->
|
||||||
rootDeclarations += mainFunction
|
rootDeclarations += mainFunction
|
||||||
if (mainFunction.isSuspend) {
|
if (mainFunction.isSuspend) {
|
||||||
|
|||||||
@@ -315,6 +315,10 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
|
|||||||
val readSharedBox = defineReadSharedBox()
|
val readSharedBox = defineReadSharedBox()
|
||||||
val writeSharedBox = defineWriteSharedBox()
|
val writeSharedBox = defineWriteSharedBox()
|
||||||
|
|
||||||
|
|
||||||
|
val safePropertyGet = getInternalFunction("safePropertyGet")
|
||||||
|
val safePropertySet = getInternalFunction("safePropertySet")
|
||||||
|
|
||||||
val jsUndefined = defineJsUndefinedIntrinsic()
|
val jsUndefined = defineJsUndefinedIntrinsic()
|
||||||
|
|
||||||
// Helpers:
|
// Helpers:
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class JsIrBackendContext(
|
|||||||
override val es6mode: Boolean = false,
|
override val es6mode: Boolean = false,
|
||||||
val dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null,
|
val dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null,
|
||||||
val propertyLazyInitialization: Boolean = false,
|
val propertyLazyInitialization: Boolean = false,
|
||||||
|
val legacyPropertyAccess: Boolean = false,
|
||||||
) : JsCommonBackendContext {
|
) : JsCommonBackendContext {
|
||||||
val fileToInitializationFuns: MutableMap<IrFile, IrSimpleFunction?> = mutableMapOf()
|
val fileToInitializationFuns: MutableMap<IrFile, IrSimpleFunction?> = mutableMapOf()
|
||||||
val fileToInitializerPureness: MutableMap<IrFile, Boolean> = mutableMapOf()
|
val fileToInitializerPureness: MutableMap<IrFile, Boolean> = mutableMapOf()
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ fun compile(
|
|||||||
multiModule: Boolean = false,
|
multiModule: Boolean = false,
|
||||||
relativeRequirePath: Boolean = false,
|
relativeRequirePath: Boolean = false,
|
||||||
propertyLazyInitialization: Boolean,
|
propertyLazyInitialization: Boolean,
|
||||||
|
legacyPropertyAccess: Boolean = false,
|
||||||
): CompilerResult {
|
): CompilerResult {
|
||||||
val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
|
val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
|
||||||
loadIr(project, mainModule, analyzer, configuration, allDependencies, friendDependencies, irFactory)
|
loadIr(project, mainModule, analyzer, configuration, allDependencies, friendDependencies, irFactory)
|
||||||
@@ -73,6 +74,7 @@ fun compile(
|
|||||||
es6mode = es6mode,
|
es6mode = es6mode,
|
||||||
dceRuntimeDiagnostic = dceRuntimeDiagnostic,
|
dceRuntimeDiagnostic = dceRuntimeDiagnostic,
|
||||||
propertyLazyInitialization = propertyLazyInitialization,
|
propertyLazyInitialization = propertyLazyInitialization,
|
||||||
|
legacyPropertyAccess = legacyPropertyAccess
|
||||||
)
|
)
|
||||||
|
|
||||||
// Load declarations referenced during `context` initialization
|
// Load declarations referenced during `context` initialization
|
||||||
|
|||||||
+21
-2
@@ -110,7 +110,7 @@ fun translateCall(
|
|||||||
// @JsName-annotated external property accessors are translated as function calls
|
// @JsName-annotated external property accessors are translated as function calls
|
||||||
if (function.getJsName() == null) {
|
if (function.getJsName() == null) {
|
||||||
val property = function.correspondingPropertySymbol?.owner
|
val property = function.correspondingPropertySymbol?.owner
|
||||||
if (property != null && property.isEffectivelyExternal()) {
|
if (property != null && (property.isEffectivelyExternal())) {
|
||||||
val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver)
|
val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver)
|
||||||
return when (function) {
|
return when (function) {
|
||||||
property.getter -> nameRef
|
property.getter -> nameRef
|
||||||
@@ -220,7 +220,26 @@ fun translateCall(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 =
|
internal fun <T : Enum<T>> enumValueOfIntrinsic(@Suppress("UNUSED_PARAMETER") name: String): T =
|
||||||
throw IllegalStateException("Should be replaced by compiler")
|
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.
|
* Implements annotated function in JavaScript.
|
||||||
|
|||||||
Reference in New Issue
Block a user