mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
[JS IR] Generate stub for exported functions with default params
see https://youtrack.jetbrains.com/issue/KT-43407
This commit is contained in:
@@ -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() =
|
||||
|
||||
@@ -5173,6 +5173,11 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
|
||||
runTest("js/js.translator/testData/box/jsExport/dataClass.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("exportedDefaultStub.kt")
|
||||
public void testExportedDefaultStub() throws Exception {
|
||||
runTest("js/js.translator/testData/box/jsExport/exportedDefaultStub.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("jsExportInClass.kt")
|
||||
public void testJsExportInClass() throws Exception {
|
||||
runTest("js/js.translator/testData/box/jsExport/jsExportInClass.kt");
|
||||
|
||||
@@ -5173,6 +5173,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/jsExport/dataClass.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("exportedDefaultStub.kt")
|
||||
public void testExportedDefaultStub() throws Exception {
|
||||
runTest("js/js.translator/testData/box/jsExport/exportedDefaultStub.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("jsExportInClass.kt")
|
||||
public void testJsExportInClass() throws Exception {
|
||||
runTest("js/js.translator/testData/box/jsExport/jsExportInClass.kt");
|
||||
|
||||
@@ -5188,6 +5188,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/jsExport/dataClass.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("exportedDefaultStub.kt")
|
||||
public void testExportedDefaultStub() throws Exception {
|
||||
runTest("js/js.translator/testData/box/jsExport/exportedDefaultStub.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("jsExportInClass.kt")
|
||||
public void testJsExportInClass() throws Exception {
|
||||
runTest("js/js.translator/testData/box/jsExport/jsExportInClass.kt");
|
||||
|
||||
@@ -5,7 +5,12 @@ module.exports = function() {
|
||||
var p = new Point(3, 7);
|
||||
|
||||
return {
|
||||
"res": p.copy(13, 11).toString()
|
||||
"copy00": p.copy().toString(),
|
||||
"copy01": p.copy(undefined, 11).toString(),
|
||||
"copy10": p.copy(15).toString(),
|
||||
"copy11": p.copy(13, 11).toString(),
|
||||
"component1": p.component1(),
|
||||
"component2": p.component2()
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// MODULE_KIND: COMMON_JS
|
||||
// SKIP_DCE_DRIVEN
|
||||
// SKIP_MINIFICATION
|
||||
|
||||
// FILE: api.kt
|
||||
@@ -14,17 +15,40 @@ data class AltPoint(val x: Int, val y: Int)
|
||||
|
||||
// FILE: main.kt
|
||||
external interface JsResult {
|
||||
val res: String
|
||||
val copy00: String
|
||||
val copy01: String
|
||||
val copy10: String
|
||||
val copy11: String
|
||||
val component1: Int
|
||||
val component2: Int
|
||||
}
|
||||
|
||||
@JsModule("lib")
|
||||
external fun jsBox(): JsResult
|
||||
|
||||
fun box(): String {
|
||||
val res = jsBox().res
|
||||
if (res != "[13::11]") {
|
||||
return "Fail1: ${res}"
|
||||
val res = jsBox()
|
||||
if (res.copy00 != "[3::7]") {
|
||||
return "Fail1: ${res.copy00}"
|
||||
}
|
||||
if (res.copy01 != "[3::11]") {
|
||||
return "Fail2: ${res.copy01}"
|
||||
}
|
||||
if (res.copy10 != "[15::7]") {
|
||||
return "Fail3: ${res.copy10}"
|
||||
}
|
||||
if (res.copy11 != "[13::11]") {
|
||||
return "Fail4: ${res.copy11}"
|
||||
}
|
||||
if (res.component1 != 3) {
|
||||
return "Fail5: ${res.component1}"
|
||||
}
|
||||
if (res.component2 != 7) {
|
||||
return "Fail6: ${res.component2}"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return "OK"
|
||||
}
|
||||
33
js/js.translator/testData/box/jsExport/exportedDefaultStub.js
vendored
Normal file
33
js/js.translator/testData/box/jsExport/exportedDefaultStub.js
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
$kotlin_test_internal$.beginModule();
|
||||
|
||||
module.exports = function() {
|
||||
var api = require("JS_TESTS").api;
|
||||
var ping = api.ping;
|
||||
var pong = api.pong;
|
||||
var transform = api.transform;
|
||||
var Ping = api.Ping;
|
||||
var Pong = api.Pong;
|
||||
|
||||
return {
|
||||
"ping00": ping(),
|
||||
"ping01": ping(undefined, 10),
|
||||
"ping10": ping("X"),
|
||||
"ping11": ping("Z", 5),
|
||||
|
||||
"pong00": pong(),
|
||||
"pong01": pong(undefined, 10),
|
||||
"pong10": pong("X"),
|
||||
"pong11": pong("Z", 5),
|
||||
|
||||
"transform00": transform(),
|
||||
"transform11": transform(-5, function(it) { return it * it * it }),
|
||||
|
||||
"Ping_ping00a": new Ping().ping(),
|
||||
"Ping_ping00b": new Ping(10).ping(),
|
||||
"Ping_ping11": new Ping().ping(-4, function(it) { return it * it * it }),
|
||||
|
||||
"Pong_ping00": new Pong().ping()
|
||||
};
|
||||
};
|
||||
|
||||
$kotlin_test_internal$.endModule("lib");
|
||||
106
js/js.translator/testData/box/jsExport/exportedDefaultStub.kt
vendored
Normal file
106
js/js.translator/testData/box/jsExport/exportedDefaultStub.kt
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// MODULE_KIND: COMMON_JS
|
||||
// SKIP_DCE_DRIVEN
|
||||
// SKIP_MINIFICATION
|
||||
package api
|
||||
|
||||
@JsExport
|
||||
fun ping(a: String = "A", b: Int = 1): String {
|
||||
return "$a::$b"
|
||||
}
|
||||
|
||||
@JsExport
|
||||
open class Ping(private val defaultSeed: Int = 3) {
|
||||
private fun calculate(n: Int) = n * n
|
||||
fun ping(s: Int = defaultSeed, c: (Int) -> Int = ::calculate): Int {
|
||||
return c(s)
|
||||
}
|
||||
}
|
||||
|
||||
@JsExport
|
||||
class Pong: Ping()
|
||||
|
||||
@JsExport
|
||||
@JsName("pong")
|
||||
fun bing(a: String = "A", b: Int = 1): String {
|
||||
return "$b::$a"
|
||||
}
|
||||
|
||||
@JsExport
|
||||
fun transform(i: Int = 10, t: (Int) -> Int = {it * it}): Int {
|
||||
return t(i)
|
||||
}
|
||||
|
||||
external interface JsResult {
|
||||
val ping00: String
|
||||
val ping01: String
|
||||
val ping10: String
|
||||
val ping11: String
|
||||
|
||||
val pong00: String
|
||||
val pong01: String
|
||||
val pong10: String
|
||||
val pong11: String
|
||||
|
||||
val transform00: Int
|
||||
val transform11: Int
|
||||
|
||||
val Ping_ping00a: Int
|
||||
val Ping_ping00b: Int
|
||||
val Ping_ping11: Int
|
||||
|
||||
val Pong_ping00: Int
|
||||
}
|
||||
|
||||
@JsModule("lib")
|
||||
external fun jsBox(): JsResult
|
||||
|
||||
fun box(): String {
|
||||
val res = jsBox()
|
||||
if (res.ping00 != "A::1") {
|
||||
return "fail0: ${res.ping00}"
|
||||
}
|
||||
if (res.ping01 != "A::10") {
|
||||
return "fail1: ${res.ping01}"
|
||||
}
|
||||
if (res.ping10 != "X::1") {
|
||||
return "fail2: ${res.ping10}"
|
||||
}
|
||||
if (res.ping11 != "Z::5") {
|
||||
return "fail3: ${res.ping11}"
|
||||
}
|
||||
|
||||
if (res.pong00 != "1::A") {
|
||||
return "fail4: ${res.pong00}"
|
||||
}
|
||||
if (res.pong01 != "10::A") {
|
||||
return "fail5: ${res.pong01}"
|
||||
}
|
||||
if (res.pong10 != "1::X") {
|
||||
return "fail6: ${res.pong10}"
|
||||
}
|
||||
if (res.pong11 != "5::Z") {
|
||||
return "fail7: ${res.pong11}"
|
||||
}
|
||||
|
||||
if (res.transform00 != 100) {
|
||||
return "fail8: ${res.transform00}"
|
||||
}
|
||||
if (res.transform11 != -125) {
|
||||
return "fail9: ${res.transform11}"
|
||||
}
|
||||
|
||||
if (res.Ping_ping00a != 9) {
|
||||
return "fail10: ${res.Ping_ping00a}"
|
||||
}
|
||||
if (res.Ping_ping00b != 100) {
|
||||
return "fail11: ${res.Ping_ping00b}"
|
||||
}
|
||||
if (res.Ping_ping11 != -64) {
|
||||
return "fail12: ${res.Ping_ping11}"
|
||||
}
|
||||
if (res.Pong_ping00 != 9) {
|
||||
return "fail13: ${res.Pong_ping00}"
|
||||
}
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// IGNORE_BACKEND: JS_IR_ES6
|
||||
// EXPECTED_REACHABLE_NODES: 1294
|
||||
package foo
|
||||
|
||||
Reference in New Issue
Block a user