diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt index 11074dfd0b5..4f1ac7a583e 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt @@ -397,7 +397,7 @@ fun IrClass.createImplicitParameterDeclarationWithWrappedDescriptor() { @Suppress("UNCHECKED_CAST") fun isElseBranch(branch: IrBranch) = branch is IrElseBranch || ((branch.condition as? IrConst)?.value == true) -fun IrSimpleFunction.isMethodOfAny() = +fun IrFunction.isMethodOfAny() = ((valueParameters.size == 0 && name.asString().let { it == "hashCode" || it == "toString" }) || (valueParameters.size == 1 && name.asString() == "equals" && valueParameters[0].type.isNullableAny())) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BridgesConstruction.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BridgesConstruction.kt index dd5a8bd9d08..5cf0486cc77 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BridgesConstruction.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BridgesConstruction.kt @@ -15,7 +15,7 @@ import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin -import org.jetbrains.kotlin.ir.backend.js.utils.getJsName +import org.jetbrains.kotlin.ir.backend.js.utils.hasStableJsName import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.buildFun @@ -118,7 +118,7 @@ abstract class BridgesConstruction(val context: JsCommonBackendContext) : Declar ): IrFunction { val origin = - if (bridge.isEffectivelyExternal() || bridge.getJsName() != null) + if (bridge.hasStableJsName()) JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION else IrDeclarationOrigin.BRIDGE diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt index 74f4522f30a..09fb29e1ce7 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt @@ -109,20 +109,13 @@ fun jsFunctionSignature(declaration: IrFunction): Signature { require(declaration.dispatchReceiverParameter != null) val declarationName = declaration.getJsNameOrKotlinName().asString() - val stableName = StableNameSignature(declarationName) - if (declaration.origin == JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION) { - return stableName - } - if (declaration.isEffectivelyExternal()) { - return stableName - } - if (declaration.getJsName() != null) { - return stableName - } - // Handle names for special functions - if (declaration is IrSimpleFunction && declaration.isMethodOfAny()) { - return stableName + val needsStableName = declaration.origin == JsLoweredDeclarationOrigin.BRIDGE_TO_EXTERNAL_FUNCTION || + declaration.hasStableJsName() || + (declaration as? IrSimpleFunction)?.isMethodOfAny() == true // Handle names for special functions + + if (needsStableName) { + return StableNameSignature(declarationName) } val nameBuilder = StringBuilder() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/misc.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/misc.kt index 3b88272b7ba..ec756c0aba6 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/misc.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/misc.kt @@ -15,11 +15,29 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.isNullableAny import org.jetbrains.kotlin.ir.types.isUnit +import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.isTopLevelDeclaration +import org.jetbrains.kotlin.ir.util.parentClassOrNull 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 { + val namedOrMissingGetter = when (this) { + is IrSimpleFunction -> { + val owner = correspondingPropertySymbol?.owner + if (owner == null) { + true + } else { + owner.getter?.getJsName() != null + } + } + else -> true + } + + return (isEffectivelyExternal() || getJsName() != null || parentClassOrNull?.isJsExport() == true) && namedOrMissingGetter +} + fun IrFunction.isEqualsInheritedFromAny() = name == Name.identifier("equals") && dispatchReceiverParameter != null && diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java index f7cd3292d61..02cf8144ea6 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java @@ -5167,6 +5167,16 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsExport"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true); } + @TestMetadata("dataClass.kt") + public void testDataClass() throws Exception { + runTest("js/js.translator/testData/box/jsExport/dataClass.kt"); + } + + @TestMetadata("jsExportInClass.kt") + public void testJsExportInClass() throws Exception { + runTest("js/js.translator/testData/box/jsExport/jsExportInClass.kt"); + } + @TestMetadata("recursiveExport.kt") public void testRecursiveExport() throws Exception { runTest("js/js.translator/testData/box/jsExport/recursiveExport.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index b60b98a4a55..931a9b475e8 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -5167,6 +5167,16 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsExport"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); } + @TestMetadata("dataClass.kt") + public void testDataClass() throws Exception { + runTest("js/js.translator/testData/box/jsExport/dataClass.kt"); + } + + @TestMetadata("jsExportInClass.kt") + public void testJsExportInClass() throws Exception { + runTest("js/js.translator/testData/box/jsExport/jsExportInClass.kt"); + } + @TestMetadata("recursiveExport.kt") public void testRecursiveExport() throws Exception { runTest("js/js.translator/testData/box/jsExport/recursiveExport.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 313c401b07b..39dd28a2554 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -5182,6 +5182,16 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsExport"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); } + @TestMetadata("dataClass.kt") + public void testDataClass() throws Exception { + runTest("js/js.translator/testData/box/jsExport/dataClass.kt"); + } + + @TestMetadata("jsExportInClass.kt") + public void testJsExportInClass() throws Exception { + runTest("js/js.translator/testData/box/jsExport/jsExportInClass.kt"); + } + @TestMetadata("recursiveExport.kt") public void testRecursiveExport() throws Exception { runTest("js/js.translator/testData/box/jsExport/recursiveExport.kt"); diff --git a/js/js.translator/testData/box/jsExport/dataClass.js b/js/js.translator/testData/box/jsExport/dataClass.js new file mode 100644 index 00000000000..fdf2d9174dd --- /dev/null +++ b/js/js.translator/testData/box/jsExport/dataClass.js @@ -0,0 +1,13 @@ +$kotlin_test_internal$.beginModule(); + +module.exports = function() { + var Point = require("JS_TESTS").api.Point; + var p = new Point(3, 7); + + return { + "res": p.copy(13, 11).toString() + }; +}; + +$kotlin_test_internal$.endModule("lib"); + diff --git a/js/js.translator/testData/box/jsExport/dataClass.kt b/js/js.translator/testData/box/jsExport/dataClass.kt new file mode 100644 index 00000000000..7652ed91e02 --- /dev/null +++ b/js/js.translator/testData/box/jsExport/dataClass.kt @@ -0,0 +1,30 @@ +// MODULE_KIND: COMMON_JS +// SKIP_MINIFICATION + +// FILE: api.kt +package api + +@JsExport +data class Point(val x: Int, val y: Int) { + override fun toString(): String = "[${x}::${y}]" +} + +// we need his class to make sure that there's more than one ping method in existence - due to peculiarities of current namer otherwise test can pass but JsExport won't be actually respected +data class AltPoint(val x: Int, val y: Int) + +// FILE: main.kt +external interface JsResult { + val res: String +} + +@JsModule("lib") +external fun jsBox(): JsResult + +fun box(): String { + val res = jsBox().res + if (res != "[13::11]") { + return "Fail1: ${res}" + } + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/jsExport/jsExportInClass.js b/js/js.translator/testData/box/jsExport/jsExportInClass.js new file mode 100644 index 00000000000..1836b97c6ad --- /dev/null +++ b/js/js.translator/testData/box/jsExport/jsExportInClass.js @@ -0,0 +1,13 @@ +$kotlin_test_internal$.beginModule(); + +module.exports = function() { + var A = require("JS_TESTS").api.A; + var B = require("JS_TESTS").api.B; + + return { + "res": (new A().ping()) + (new B().pong()) + }; +}; + +$kotlin_test_internal$.endModule("lib"); + diff --git a/js/js.translator/testData/box/jsExport/jsExportInClass.kt b/js/js.translator/testData/box/jsExport/jsExportInClass.kt new file mode 100644 index 00000000000..17ab65bf3b5 --- /dev/null +++ b/js/js.translator/testData/box/jsExport/jsExportInClass.kt @@ -0,0 +1,39 @@ +// MODULE_KIND: COMMON_JS +// SKIP_MINIFICATION + +// FILE: api.kt +package api + +@JsExport +class A() { + fun ping() = "ping" +} + +@JsExport +class B() { + @JsName("pong") + fun ping() = "pong" +} + +// we need his class to make sure that there's more than one ping method in existence - due to peculiarities of current namer otherwise test can pass but JsExport won't be actually respected +class C() { + fun ping() = "pong" +} + + +// FILE: main.kt +external interface JsResult { + val res: String +} + +@JsModule("lib") +external fun jsBox(): JsResult + +fun box(): String { + val res = jsBox().res + if (res != "pingpong") { + return "Fail: ${res}" + } + + return "OK" +} \ No newline at end of file