From caa1570e25b9ac5eaa40c96177a6cf80bb47d387 Mon Sep 17 00:00:00 2001 From: Alexander Korepanov Date: Wed, 20 Jul 2022 16:01:18 +0200 Subject: [PATCH] [JS IR] Fix clashes between bridge and delegated function call The patch fixes the js function signature rules to avoid clashes between bridge and delegated call. Use overridden symbols dfs of JsName annotation in order to get the correct bridge name. ^KT-52968 Fixed --- .../backend/js/lower/BridgesConstruction.kt | 4 +- .../ir/backend/js/utils/AnnotationUtils.kt | 16 +++- .../kotlin/js/test/BoxJsTestGenerated.java | 30 +++++++ .../kotlin/js/test/ir/FirJsTestGenerated.java | 30 +++++++ .../js/test/ir/IrBoxJsTestGenerated.java | 30 +++++++ .../box/jsName/methodOfAbstractClass.kt | 36 +++++++++ .../testData/box/jsName/methodOfInterface.kt | 37 +++++++++ .../jsName/methodOfInterfaceWithDefinition.kt | 43 ++++++++++ .../box/jsName/methodOfMultipleInterface.kt | 78 +++++++++++++++++++ .../testData/box/jsName/methodOfOpenClass.kt | 44 +++++++++++ 10 files changed, 345 insertions(+), 3 deletions(-) create mode 100644 js/js.translator/testData/box/jsName/methodOfAbstractClass.kt create mode 100644 js/js.translator/testData/box/jsName/methodOfInterface.kt create mode 100644 js/js.translator/testData/box/jsName/methodOfInterfaceWithDefinition.kt create mode 100644 js/js.translator/testData/box/jsName/methodOfMultipleInterface.kt create mode 100644 js/js.translator/testData/box/jsName/methodOfOpenClass.kt 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 c3e673252b6..b70388435c0 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 @@ -146,8 +146,10 @@ abstract class BridgesConstruction(val context: T) : copyReceiverParametersFrom(bridge, substitutionMap) copyValueParametersFrom(bridge, substitutionMap) annotations += bridge.annotations - overriddenSymbols += delegateTo.overriddenSymbols + // the js function signature building process (jsFunctionSignature()) uses dfs throught overriddenSymbols for getting js name, + // therefore it is very important to put bridge symbol at the beginning, it allows to get correct js function name overriddenSymbols += bridge.symbol + overriddenSymbols += delegateTo.overriddenSymbols } irFunction.body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt index e5281f476e7..0a3ee739b55 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt @@ -66,13 +66,25 @@ fun IrAnnotationContainer.isJsNativeInvoke(): Boolean = hasAnnotation(JsAnnotati fun IrAnnotationContainer.isAnnotatedWithJsFun(): Boolean = hasAnnotation(JsAnnotations.jsFunFqn) +private fun IrOverridableDeclaration<*>.dfsOverridableJsNameOrNull(): String? { + for (overriddenSymbol in overriddenSymbols) { + val symbolOwner = overriddenSymbol.owner + if (symbolOwner is IrAnnotationContainer) { + symbolOwner.getJsName()?.let { return it } + } + if (symbolOwner is IrOverridableDeclaration<*>) { + symbolOwner.dfsOverridableJsNameOrNull()?.let { return it } + } + } + return null +} + fun IrDeclarationWithName.getJsNameForOverriddenDeclaration(): String? { val jsName = getJsName() return when { jsName != null -> jsName - this is IrOverridableDeclaration<*> -> - overriddenSymbols.firstNotNullOfOrNull { (it.owner as? IrAnnotationContainer)?.getJsName() } + this is IrOverridableDeclaration<*> -> dfsOverridableJsNameOrNull() else -> null } } diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java index 0f6b108220e..7d9834761af 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java @@ -6570,6 +6570,36 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { runTest("js/js.translator/testData/box/jsName/jsTopLevelRenameReserved.kt"); } + @Test + @TestMetadata("methodOfAbstractClass.kt") + public void testMethodOfAbstractClass() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfAbstractClass.kt"); + } + + @Test + @TestMetadata("methodOfInterface.kt") + public void testMethodOfInterface() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfInterface.kt"); + } + + @Test + @TestMetadata("methodOfInterfaceWithDefinition.kt") + public void testMethodOfInterfaceWithDefinition() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfInterfaceWithDefinition.kt"); + } + + @Test + @TestMetadata("methodOfMultipleInterface.kt") + public void testMethodOfMultipleInterface() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfMultipleInterface.kt"); + } + + @Test + @TestMetadata("methodOfOpenClass.kt") + public void testMethodOfOpenClass() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfOpenClass.kt"); + } + @Test @TestMetadata("overriddenMethod.kt") public void testOverriddenMethod() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java index 8ba8296713d..6525ba1080e 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java @@ -7036,6 +7036,36 @@ public class FirJsTestGenerated extends AbstractFirJsTest { runTest("js/js.translator/testData/box/jsName/jsTopLevelRenameReserved.kt"); } + @Test + @TestMetadata("methodOfAbstractClass.kt") + public void testMethodOfAbstractClass() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfAbstractClass.kt"); + } + + @Test + @TestMetadata("methodOfInterface.kt") + public void testMethodOfInterface() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfInterface.kt"); + } + + @Test + @TestMetadata("methodOfInterfaceWithDefinition.kt") + public void testMethodOfInterfaceWithDefinition() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfInterfaceWithDefinition.kt"); + } + + @Test + @TestMetadata("methodOfMultipleInterface.kt") + public void testMethodOfMultipleInterface() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfMultipleInterface.kt"); + } + + @Test + @TestMetadata("methodOfOpenClass.kt") + public void testMethodOfOpenClass() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfOpenClass.kt"); + } + @Test @TestMetadata("overriddenMethod.kt") public void testOverriddenMethod() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index d42915fa64f..4e2cfa56be3 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -7036,6 +7036,36 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/jsName/jsTopLevelRenameReserved.kt"); } + @Test + @TestMetadata("methodOfAbstractClass.kt") + public void testMethodOfAbstractClass() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfAbstractClass.kt"); + } + + @Test + @TestMetadata("methodOfInterface.kt") + public void testMethodOfInterface() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfInterface.kt"); + } + + @Test + @TestMetadata("methodOfInterfaceWithDefinition.kt") + public void testMethodOfInterfaceWithDefinition() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfInterfaceWithDefinition.kt"); + } + + @Test + @TestMetadata("methodOfMultipleInterface.kt") + public void testMethodOfMultipleInterface() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfMultipleInterface.kt"); + } + + @Test + @TestMetadata("methodOfOpenClass.kt") + public void testMethodOfOpenClass() throws Exception { + runTest("js/js.translator/testData/box/jsName/methodOfOpenClass.kt"); + } + @Test @TestMetadata("overriddenMethod.kt") public void testOverriddenMethod() throws Exception { diff --git a/js/js.translator/testData/box/jsName/methodOfAbstractClass.kt b/js/js.translator/testData/box/jsName/methodOfAbstractClass.kt new file mode 100644 index 00000000000..a7f64a27211 --- /dev/null +++ b/js/js.translator/testData/box/jsName/methodOfAbstractClass.kt @@ -0,0 +1,36 @@ +@JsExport +abstract class TestOpenClass { + @JsName("testName") + abstract fun testFunction(): String + abstract fun testFunction(x: String): String +} + +abstract class TestOpenClassA : TestOpenClass() { + override abstract fun testFunction(): String + override abstract fun testFunction(x: String): String +} + +class TestClassA : TestOpenClassA() { + override fun testFunction(): String = "TestClassA" + override fun testFunction(x: String): String = "TestClassA: ${x}" +} + +fun testTestOpenClass1(x: TestOpenClass) = x.testFunction() +fun testTestOpenClass2(x: TestOpenClass) = x.testFunction("OK") + +fun testTestOpenClassA1(x: TestOpenClassA) = x.testFunction() +fun testTestOpenClassA2(x: TestOpenClassA) = x.testFunction("OK") + +fun testTestClassA1(x: TestClassA) = x.testFunction() +fun testTestClassA2(x: TestClassA) = x.testFunction("OK") + +fun box(): String { + assertEquals("TestClassA", testTestOpenClass1(TestClassA())) + assertEquals("TestClassA: OK", testTestOpenClass2(TestClassA())) + assertEquals("TestClassA", testTestOpenClassA1(TestClassA())) + assertEquals("TestClassA: OK", testTestOpenClassA2(TestClassA())) + assertEquals("TestClassA", testTestClassA1(TestClassA())) + assertEquals("TestClassA: OK", testTestClassA2(TestClassA())) + + return "OK" +} diff --git a/js/js.translator/testData/box/jsName/methodOfInterface.kt b/js/js.translator/testData/box/jsName/methodOfInterface.kt new file mode 100644 index 00000000000..235718b3a08 --- /dev/null +++ b/js/js.translator/testData/box/jsName/methodOfInterface.kt @@ -0,0 +1,37 @@ +interface TestInterface { + @JsName("testName") + fun testFunction(): String + fun testFunction(x: String): String +} + +interface TestInterfaceA : TestInterface { + override fun testFunction(): String + override fun testFunction(x: String): String +} + +class TestClassA : TestInterfaceA { + override fun testFunction(): String = "TestClassA" + override fun testFunction(x: String): String = "TestClassA: $x" +} + +fun testTestInterface1(x: TestInterface) = x.testFunction() +fun testTestInterface2(x: TestInterface) = x.testFunction("OK") + +fun testTestInterfaceA1(x: TestInterfaceA) = x.testFunction() +fun testTestInterfaceA2(x: TestInterfaceA) = x.testFunction("OK") + +fun testTestClassA1(x: TestClassA) = x.testFunction() +fun testTestClassA2(x: TestClassA) = x.testFunction("OK") + +fun box(): String { + assertEquals("TestClassA", testTestInterface1(TestClassA())) + assertEquals("TestClassA: OK", testTestInterface2(TestClassA())) + + assertEquals("TestClassA", testTestInterfaceA1(TestClassA())) + assertEquals("TestClassA: OK", testTestInterfaceA2(TestClassA())) + + assertEquals("TestClassA", testTestClassA1(TestClassA())) + assertEquals("TestClassA: OK", testTestClassA2(TestClassA())) + + return "OK" +} diff --git a/js/js.translator/testData/box/jsName/methodOfInterfaceWithDefinition.kt b/js/js.translator/testData/box/jsName/methodOfInterfaceWithDefinition.kt new file mode 100644 index 00000000000..c637f282e9b --- /dev/null +++ b/js/js.translator/testData/box/jsName/methodOfInterfaceWithDefinition.kt @@ -0,0 +1,43 @@ +interface TestInterface { + @JsName("testName") + open fun testFunction(): String = "TestInterface" + open fun testFunction(x: String): String = "TestInterface: ${x}" +} + +interface TestInterfaceA : TestInterface { + override fun testFunction(): String = "TestInterfaceA" + override fun testFunction(x: String): String = "TestInterfaceA: ${x}" +} + +class TestClassA : TestInterfaceA { + override fun testFunction(): String = "TestClassA" + override fun testFunction(x: String): String = "TestClassA: ${x}" +} + +fun testTestInterface1(x: TestInterface) = x.testFunction() +fun testTestInterface2(x: TestInterface) = x.testFunction("OK") + +fun testTestInterfaceA1(x: TestInterfaceA) = x.testFunction() +fun testTestInterfaceA2(x: TestInterfaceA) = x.testFunction("OK") + +fun testTestClassA1(x: TestClassA) = x.testFunction() +fun testTestClassA2(x: TestClassA) = x.testFunction("OK") + +fun box(): String { + assertEquals("TestInterface", testTestInterface1(object : TestInterface {})) + assertEquals("TestInterface: OK", testTestInterface2(object : TestInterface {})) + + assertEquals("TestInterfaceA", testTestInterface1(object : TestInterfaceA {})) + assertEquals("TestInterfaceA: OK", testTestInterface2(object : TestInterfaceA {})) + assertEquals("TestInterfaceA", testTestInterfaceA1(object : TestInterfaceA {})) + assertEquals("TestInterfaceA: OK", testTestInterfaceA2(object : TestInterfaceA {})) + + assertEquals("TestClassA", testTestInterface1(TestClassA())) + assertEquals("TestClassA: OK", testTestInterface2(TestClassA())) + assertEquals("TestClassA", testTestInterfaceA1(TestClassA())) + assertEquals("TestClassA: OK", testTestInterfaceA2(TestClassA())) + assertEquals("TestClassA", testTestClassA1(TestClassA())) + assertEquals("TestClassA: OK", testTestClassA2(TestClassA())) + + return "OK" +} diff --git a/js/js.translator/testData/box/jsName/methodOfMultipleInterface.kt b/js/js.translator/testData/box/jsName/methodOfMultipleInterface.kt new file mode 100644 index 00000000000..5ff474f34cb --- /dev/null +++ b/js/js.translator/testData/box/jsName/methodOfMultipleInterface.kt @@ -0,0 +1,78 @@ +interface TestInterfaceA { + @JsName("testNameA") + fun testFunction(): String +} +fun testTestInterfaceA(x: TestInterfaceA) = x.testFunction() + +interface TestInterfaceB { + @JsName("testNameB") + fun testFunction(): String +} +fun testTestInterfaceB(x: TestInterfaceB) = x.testFunction() + +interface TestInterfaceAA: TestInterfaceA { + override fun testFunction(): String +} +fun testTestInterfaceAA(x: TestInterfaceAA) = x.testFunction() + +interface TestInterfaceBAA : TestInterfaceB, TestInterfaceAA { + override fun testFunction(): String +} +fun testTestInterfaceBAA(x: TestInterfaceBAA) = x.testFunction() + +class TestClassBAA : TestInterfaceBAA { + override fun testFunction(): String = "TestClassBAA" +} +fun testTestClassBAA(x: TestClassBAA) = x.testFunction() + +interface TestInterfaceBB: TestInterfaceB { + override fun testFunction(): String +} +fun testTestInterfaceBB(x: TestInterfaceBB) = x.testFunction() + +interface TestInterfaceABB : TestInterfaceA, TestInterfaceBB { + override fun testFunction(): String +} +fun testTestInterfaceABB(x: TestInterfaceABB) = x.testFunction() + +class TestClassABB : TestInterfaceABB { + override fun testFunction(): String = "TestClassABB" +} +fun testTestClassABB(x: TestClassABB) = x.testFunction() + +class TestClassABBAA : TestInterfaceABB, TestInterfaceAA { + override fun testFunction(): String = "TestClassABBAA" +} +fun testTestClassABBAA(x: TestClassABBAA) = x.testFunction() + +fun box(): String { + TestClassBAA().also { + assertEquals("TestClassBAA", testTestInterfaceA(it)) + assertEquals("TestClassBAA", testTestInterfaceB(it)) + + assertEquals("TestClassBAA", testTestInterfaceAA(it)) + assertEquals("TestClassBAA", testTestInterfaceBAA(it)) + assertEquals("TestClassBAA", testTestClassBAA(it)) + } + + TestClassABB().also { + assertEquals("TestClassABB", testTestInterfaceA(it)) + assertEquals("TestClassABB", testTestInterfaceB(it)) + + assertEquals("TestClassABB", testTestInterfaceBB(it)) + assertEquals("TestClassABB", testTestInterfaceABB(it)) + assertEquals("TestClassABB", testTestClassABB(it)) + } + + TestClassABBAA().also { + assertEquals("TestClassABBAA", testTestInterfaceA(it)) + assertEquals("TestClassABBAA", testTestInterfaceB(it)) + + assertEquals("TestClassABBAA", testTestInterfaceAA(it)) + assertEquals("TestClassABBAA", testTestInterfaceBB(it)) + assertEquals("TestClassABBAA", testTestInterfaceABB(it)) + + assertEquals("TestClassABBAA", testTestClassABBAA(it)) + } + return "OK" +} diff --git a/js/js.translator/testData/box/jsName/methodOfOpenClass.kt b/js/js.translator/testData/box/jsName/methodOfOpenClass.kt new file mode 100644 index 00000000000..813c32593b5 --- /dev/null +++ b/js/js.translator/testData/box/jsName/methodOfOpenClass.kt @@ -0,0 +1,44 @@ +@JsExport +open class TestOpenClass { + @JsName("testName") + open fun testFunction(): String = "TestOpenClass" + open fun testFunction(x: String): String = "TestOpenClass: ${x}" +} + +open class TestOpenClassA : TestOpenClass() { + override fun testFunction(): String = "TestOpenClassA" + override fun testFunction(x: String): String = "TestOpenClassA: ${x}" +} + +class TestClassA : TestOpenClassA() { + override fun testFunction(): String = "TestClassA" + override fun testFunction(x: String): String = "TestClassA: ${x}" +} + +fun testTestOpenClass1(x: TestOpenClass) = x.testFunction() +fun testTestOpenClass2(x: TestOpenClass) = x.testFunction("OK") + +fun testTestOpenClassA1(x: TestOpenClassA) = x.testFunction() +fun testTestOpenClassA2(x: TestOpenClassA) = x.testFunction("OK") + +fun testTestClassA1(x: TestClassA) = x.testFunction() +fun testTestClassA2(x: TestClassA) = x.testFunction("OK") + +fun box(): String { + assertEquals("TestOpenClass", testTestOpenClass1(TestOpenClass())) + assertEquals("TestOpenClass: OK", testTestOpenClass2(TestOpenClass())) + + assertEquals("TestOpenClassA", testTestOpenClass1(TestOpenClassA())) + assertEquals("TestOpenClassA: OK", testTestOpenClass2(TestOpenClassA())) + assertEquals("TestOpenClassA", testTestOpenClassA1(TestOpenClassA())) + assertEquals("TestOpenClassA: OK", testTestOpenClassA2(TestOpenClassA())) + + assertEquals("TestClassA", testTestOpenClass1(TestClassA())) + assertEquals("TestClassA: OK", testTestOpenClass2(TestClassA())) + assertEquals("TestClassA", testTestOpenClassA1(TestClassA())) + assertEquals("TestClassA: OK", testTestOpenClassA2(TestClassA())) + assertEquals("TestClassA", testTestClassA1(TestClassA())) + assertEquals("TestClassA: OK", testTestClassA2(TestClassA())) + + return "OK" +}