[JS IR] Generate stub for exported functions with default params

see https://youtrack.jetbrains.com/issue/KT-43407
This commit is contained in:
Shagen Ogandzhanian
2020-12-31 20:21:21 +01:00
parent bf3f6594d5
commit 96de9144de
14 changed files with 346 additions and 11 deletions
@@ -15,4 +15,5 @@ object JsLoweredDeclarationOrigin : IrDeclarationOrigin {
object JS_CLOSURE_BOX_CLASS_DECLARATION : IrDeclarationOriginImpl("JS_CLOSURE_BOX_CLASS_DECLARATION")
object BRIDGE_TO_EXTERNAL_FUNCTION : IrDeclarationOriginImpl("BRIDGE_TO_EXTERNAL_FUNCTION")
object OBJECT_GET_INSTANCE_FUNCTION : IrDeclarationOriginImpl("OBJECT_GET_INSTANCE_FUNCTION")
object JS_SHADOWED_EXPORT : IrDeclarationOriginImpl("JS_SHADOWED_EXPORT")
}
@@ -485,6 +485,13 @@ private val defaultParameterCleanerPhase = makeDeclarationTransformerPhase(
description = "Clean default parameters up"
)
private val exportedDefaultParameterStubPhase = makeDeclarationTransformerPhase(
::ExportedDefaultParameterStub,
name = "ExportedDefaultParameterStub",
description = "Generates default stub for exported entity and renames the non-default counterpart"
)
private val jsDefaultCallbackGeneratorPhase = makeBodyLoweringPhase(
::JsDefaultCallbackGenerator,
name = "JsDefaultCallbackGenerator",
@@ -701,7 +708,7 @@ private val cleanupLoweringPhase = makeBodyLoweringPhase(
description = "Clean up IR before codegen"
)
val loweringList = listOf<Lowering>(
private val loweringList = listOf<Lowering>(
scriptRemoveReceiverLowering,
validateIrBeforeLowering,
expectDeclarationsRemovingPhase,
@@ -759,6 +766,7 @@ val loweringList = listOf<Lowering>(
foldConstantLoweringPhase,
privateMembersLoweringPhase,
privateMemberUsagesLoweringPhase,
exportedDefaultParameterStubPhase,
defaultArgumentStubGeneratorPhase,
defaultArgumentPatchOverridesPhase,
defaultParameterInjectorPhase,
@@ -326,8 +326,9 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
return Exportability.NotNeeded
if (function.origin == IrDeclarationOrigin.BRIDGE ||
function.origin == JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION ||
function.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER ||
function.origin == JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION ||
function.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER
function.origin == JsLoweredDeclarationOrigin.JS_SHADOWED_EXPORT
) {
return Exportability.NotNeeded
}
@@ -335,7 +336,6 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
if (function.isFakeOverriddenFromAny())
return Exportability.NotNeeded
val nameString = function.name.asString()
if (nameString.endsWith("-impl"))
return Exportability.NotNeeded
@@ -0,0 +1,136 @@
/*
* 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.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.ir.copyParameterDeclarationsFrom
import org.jetbrains.kotlin.backend.common.ir.passTypeArgumentsFrom
import org.jetbrains.kotlin.backend.common.ir.remapTypeParameters
import org.jetbrains.kotlin.backend.common.lower.VariableRemapper
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlockBody
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.JsAnnotations
import org.jetbrains.kotlin.ir.backend.js.utils.hasStableJsName
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
private fun IrConstructorCall.isAnnotation(name: FqName): Boolean {
return symbol.owner.parentAsClass.fqNameWhenAvailable == name
}
class ExportedDefaultParameterStub(val context: JsIrBackendContext) : DeclarationTransformer {
private fun IrBuilderWithScope.createDefaultResolutionExpression(value: IrValueParameter): IrExpression? {
return value.defaultValue?.let { defaultValue ->
irIfThenElse(
value.type,
irEqeqeq(
irGet(value),
irCall(this@ExportedDefaultParameterStub.context.intrinsics.jsUndefined)
),
defaultValue.expression,
irGet(value)
)
}
}
private fun IrConstructor.introduceDefaultResolution(): IrConstructor {
val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset)
val variables = mutableMapOf<IrValueParameter, IrValueDeclaration>()
val defaultResolutionStatements = valueParameters.mapNotNull { valueParameter ->
irBuilder.createDefaultResolutionExpression(valueParameter)?.let { initializer ->
JsIrBuilder.buildVar(
valueParameter.type,
this@introduceDefaultResolution,
name = valueParameter.name.asString(),
initializer = initializer
).also {
variables[valueParameter] = it
}
}
}
if (variables.isNotEmpty()) {
body?.transformChildren(VariableRemapper(variables), null)
body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
statements += defaultResolutionStatements
statements += body?.statements ?: emptyList()
}
}
return this
}
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration !is IrFunction) {
return null
}
if (!declaration.hasStableJsName()) {
return null
}
if (!declaration.valueParameters.any { it.defaultValue != null }) {
return null
}
if (declaration is IrConstructor) {
return listOf(declaration.introduceDefaultResolution())
}
val exportedDefaultStubFun = context.irFactory.buildFun {
updateFrom(declaration)
name = declaration.name
origin = JsIrBuilder.SYNTHESIZED_DECLARATION
}
exportedDefaultStubFun.returnType = declaration.returnType.remapTypeParameters(declaration, exportedDefaultStubFun)
exportedDefaultStubFun.parent = declaration.parent
exportedDefaultStubFun.copyParameterDeclarationsFrom(declaration)
exportedDefaultStubFun.valueParameters.forEach { it.defaultValue = null }
declaration.origin = JsLoweredDeclarationOrigin.JS_SHADOWED_EXPORT
val irBuilder = context.createIrBuilder(exportedDefaultStubFun.symbol, exportedDefaultStubFun.startOffset, exportedDefaultStubFun.endOffset)
exportedDefaultStubFun.body = irBuilder.irBlockBody(exportedDefaultStubFun) {
+irReturn(irCall(declaration).apply {
passTypeArgumentsFrom(declaration)
dispatchReceiver = exportedDefaultStubFun.dispatchReceiverParameter?.let { irGet(it) }
extensionReceiver = exportedDefaultStubFun.extensionReceiverParameter?.let { irGet(it) }
declaration.valueParameters.forEachIndexed { index, irValueParameter ->
val value = createDefaultResolutionExpression(irValueParameter) ?: irGet(irValueParameter)
putValueArgument(index, value)
}
})
}
val (exportAnnotations, irrelevantAnnotations) = declaration.annotations.map { it.deepCopyWithSymbols(declaration as? IrDeclarationParent) }
.partition {
it.isAnnotation(JsAnnotations.jsExportFqn) || (it.isAnnotation(JsAnnotations.jsNameFqn))
}
declaration.annotations = irrelevantAnnotations
exportedDefaultStubFun.annotations = exportAnnotations
return listOf(exportedDefaultStubFun, declaration)
}
}
@@ -52,7 +52,7 @@ class JsErrorExpressionLowering(context: JsIrBackendContext) : ErrorExpressionLo
return buildThrowError(expression, description)
}
private fun buildThrowError(element: IrElement, description: String): IrExpression {
private fun buildThrowError(element: IrExpression, description: String): IrExpression {
require(errorSymbol != null) { "Should be non-null if errors are allowed" }
return element.run {
IrCallImpl(startOffset, endOffset, nothingType, errorSymbol, 0, 1, null, null).apply {
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
@@ -23,6 +24,13 @@ import org.jetbrains.kotlin.name.Name
fun TODO(element: IrElement): Nothing = TODO(element::class.java.simpleName + " is not supported yet here")
fun IrFunction.hasStableJsName(): Boolean {
if (
origin == JsLoweredDeclarationOrigin.JS_SHADOWED_EXPORT ||
origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER
) {
return false
}
val namedOrMissingGetter = when (this) {
is IrSimpleFunction -> {
val owner = correspondingPropertySymbol?.owner
@@ -35,7 +43,7 @@ fun IrFunction.hasStableJsName(): Boolean {
else -> true
}
return (isEffectivelyExternal() || getJsName() != null || parentClassOrNull?.isJsExport() == true) && namedOrMissingGetter
return (isEffectivelyExternal() || getJsName() != null || isJsExport() || parentClassOrNull?.isJsExport() == true) && namedOrMissingGetter
}
fun IrFunction.isEqualsInheritedFromAny() =