diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index d8824709d09..99404b4e095 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -67,6 +67,7 @@ class JsIrBackendContext( val fileToInitializerPureness: MutableMap = mutableMapOf() val fieldToInitializer: MutableMap = mutableMapOf() + val localClassNames: MutableMap = mutableMapOf() val extractedLocalClasses: MutableSet = hashSetOf() override val builtIns = module.builtIns diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 075e856c8c0..bf95cd3f918 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -149,6 +149,12 @@ val createScriptFunctionsPhase = makeJsModulePhase( description = "Create functions for initialize and evaluate script" ).toModuleLowering() +private val inventNamesForLocalClassesPhase = makeJsModulePhase( + ::JsInventNamesForLocalClasses, + name = "InventNamesForLocalClasses", + description = "Invent names for local classes and anonymous objects", +).toModuleLowering() + private val annotationInstantiationLowering = makeDeclarationTransformerPhase( ::JsAnnotationImplementationTransformer, name = "AnnotationImplementation", @@ -220,13 +226,15 @@ private val sharedVariablesLoweringPhase = makeBodyLoweringPhase( private val localClassesInInlineLambdasPhase = makeBodyLoweringPhase( ::LocalClassesInInlineLambdasLowering, name = "LocalClassesInInlineLambdasPhase", - description = "Extract local classes from inline lambdas" + description = "Extract local classes from inline lambdas", + prerequisite = setOf(inventNamesForLocalClassesPhase) ) private val localClassesInInlineFunctionsPhase = makeBodyLoweringPhase( ::LocalClassesInInlineFunctionsLowering, name = "LocalClassesInInlineFunctionsPhase", - description = "Extract local classes from inline functions" + description = "Extract local classes from inline functions", + prerequisite = setOf(inventNamesForLocalClassesPhase) ) private val localClassesExtractionFromInlineFunctionsPhase = makeBodyLoweringPhase( @@ -807,6 +815,7 @@ private val jsSuspendArityStorePhase = makeDeclarationTransformerPhase( val loweringList = listOf( scriptRemoveReceiverLowering, validateIrBeforeLowering, + inventNamesForLocalClassesPhase, annotationInstantiationLowering, expectDeclarationsRemovingPhase, stripTypeAliasDeclarationsPhase, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsInventNamesForLocalClasses.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsInventNamesForLocalClasses.kt new file mode 100644 index 00000000000..072e8014371 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsInventNamesForLocalClasses.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2021 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.lower.InventNamesForLocalClasses +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName +import org.jetbrains.kotlin.ir.declarations.IrAttributeContainer +import org.jetbrains.kotlin.ir.declarations.IrClass + +class JsInventNamesForLocalClasses(private val context: JsIrBackendContext) : InventNamesForLocalClasses(allowTopLevelCallables = true) { + override fun computeTopLevelClassName(clazz: IrClass): String = clazz.name.toString() + + override fun sanitizeNameIfNeeded(name: String): String = sanitizeName(name, withHash = false) + + override fun putLocalClassName(declaration: IrAttributeContainer, localClassName: String) { + if (declaration is IrClass) { + context.localClassNames[declaration] = localClassName + } + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt index 9a980fb4cd2..5c20f663eb6 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt @@ -21,7 +21,10 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext) : IrNamerBase( val nameMap = mutableMapOf() private fun IrDeclarationWithName.getName(prefix: String = ""): JsName { - return nameMap.getOrPut(this) { JsName(sanitizeName(prefix + getJsNameOrKotlinName().asString()), true) } + return nameMap.getOrPut(this) { + val name = (this as? IrClass)?.let { context.localClassNames[this] } ?: getJsNameOrKotlinName().asString() + JsName(sanitizeName(prefix + name), true) + } } val importedModules = mutableListOf() 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 f254474c8e9..55755d559ff 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 @@ -280,6 +280,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/classObject"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); } + @Test + @TestMetadata("contextDependentClassObjectName.kt") + public void testContextDependentClassObjectName() throws Exception { + runTest("js/js.translator/testData/box/classObject/contextDependentClassObjectName.kt"); + } + @Test @TestMetadata("defaultObjectSameNamesAsInOuter.kt") public void testDefaultObjectSameNamesAsInOuter() throws Exception { @@ -524,6 +530,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { runTest("js/js.translator/testData/box/closure/closureVarToScopeWithSameNameDeclaration.kt"); } + @Test + @TestMetadata("contextDependentClosureName.kt") + public void testContextDependentClosureName() throws Exception { + runTest("js/js.translator/testData/box/closure/contextDependentClosureName.kt"); + } + @Test @TestMetadata("deepInnerClassInLocalClass.kt") public void testDeepInnerClassInLocalClass() throws Exception { @@ -6691,6 +6703,22 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { } } + @Nested + @TestMetadata("js/js.translator/testData/box/local") + @TestDataPath("$PROJECT_ROOT") + public class Local { + @Test + public void testAllFilesPresentInLocal() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/local"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @Test + @TestMetadata("contextDependentLocalClassName.kt") + public void testContextDependentLocalClassName() throws Exception { + runTest("js/js.translator/testData/box/local/contextDependentLocalClassName.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/main") @TestDataPath("$PROJECT_ROOT") @@ -7788,6 +7816,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/objectDeclaration"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); } + @Test + @TestMetadata("contextDependentObjectName.kt") + public void testContextDependentObjectName() throws Exception { + runTest("js/js.translator/testData/box/objectDeclaration/contextDependentObjectName.kt"); + } + @Test @TestMetadata("dontPolluteObject.kt") public void testDontPolluteObject() 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 507dec73fdc..eb900823fe9 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 @@ -280,6 +280,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/classObject"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); } + @Test + @TestMetadata("contextDependentClassObjectName.kt") + public void testContextDependentClassObjectName() throws Exception { + runTest("js/js.translator/testData/box/classObject/contextDependentClassObjectName.kt"); + } + @Test @TestMetadata("defaultObjectSameNamesAsInOuter.kt") public void testDefaultObjectSameNamesAsInOuter() throws Exception { @@ -524,6 +530,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/closure/closureVarToScopeWithSameNameDeclaration.kt"); } + @Test + @TestMetadata("contextDependentClosureName.kt") + public void testContextDependentClosureName() throws Exception { + runTest("js/js.translator/testData/box/closure/contextDependentClosureName.kt"); + } + @Test @TestMetadata("deepInnerClassInLocalClass.kt") public void testDeepInnerClassInLocalClass() throws Exception { @@ -7075,6 +7087,22 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { } } + @Nested + @TestMetadata("js/js.translator/testData/box/local") + @TestDataPath("$PROJECT_ROOT") + public class Local { + @Test + public void testAllFilesPresentInLocal() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/local"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("contextDependentLocalClassName.kt") + public void testContextDependentLocalClassName() throws Exception { + runTest("js/js.translator/testData/box/local/contextDependentLocalClassName.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/main") @TestDataPath("$PROJECT_ROOT") @@ -8172,6 +8200,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/objectDeclaration"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); } + @Test + @TestMetadata("contextDependentObjectName.kt") + public void testContextDependentObjectName() throws Exception { + runTest("js/js.translator/testData/box/objectDeclaration/contextDependentObjectName.kt"); + } + @Test @TestMetadata("dontPolluteObject.kt") public void testDontPolluteObject() throws Exception { diff --git a/js/js.translator/testData/box/classObject/contextDependentClassObjectName.kt b/js/js.translator/testData/box/classObject/contextDependentClassObjectName.kt new file mode 100644 index 00000000000..7a100501d02 --- /dev/null +++ b/js/js.translator/testData/box/classObject/contextDependentClassObjectName.kt @@ -0,0 +1,15 @@ +class MyClass { + companion object { + fun ok(): String { + val okMaker = object { + fun getOk(): String = "OK" + } + return okMaker.getOk() + } + } +} + +// CHECK_FUNCTION_EXISTS: MyClass$Companion$ok$okMaker$1 TARGET_BACKENDS=JS_IR +fun box(): String { + return MyClass.ok() +} diff --git a/js/js.translator/testData/box/closure/contextDependentClosureName.kt b/js/js.translator/testData/box/closure/contextDependentClosureName.kt new file mode 100644 index 00000000000..e678fe8dbbf --- /dev/null +++ b/js/js.translator/testData/box/closure/contextDependentClosureName.kt @@ -0,0 +1,8 @@ +fun foo(a: () -> Unit) = a() + +// CHECK_CALLED_IN_SCOPE: scope=box function=box$lambda +fun box(): String { + var result = "FAILURE" + foo { result = "OK" } + return result +} diff --git a/js/js.translator/testData/box/local/contextDependentLocalClassName.kt b/js/js.translator/testData/box/local/contextDependentLocalClassName.kt new file mode 100644 index 00000000000..45db5f30d50 --- /dev/null +++ b/js/js.translator/testData/box/local/contextDependentLocalClassName.kt @@ -0,0 +1,13 @@ +// CHECK_FUNCTION_EXISTS: box$MyClass TARGET_BACKENDS=JS_IR +// CHECK_FUNCTION_EXISTS: box$MyClass$ok$InternalClass TARGET_BACKENDS=JS_IR +fun box(): String { + class MyClass { + fun ok(): String { + class InternalClass { + fun getOk(): String = "OK" + } + return InternalClass().getOk() + } + } + return MyClass().ok() +} diff --git a/js/js.translator/testData/box/objectDeclaration/contextDependentObjectName.kt b/js/js.translator/testData/box/objectDeclaration/contextDependentObjectName.kt new file mode 100644 index 00000000000..ad09d9f6292 --- /dev/null +++ b/js/js.translator/testData/box/objectDeclaration/contextDependentObjectName.kt @@ -0,0 +1,23 @@ +interface A { + fun run() +} + +// CHECK_FUNCTION_EXISTS: box$a$1 TARGET_BACKENDS=JS_IR +// CHECK_FUNCTION_EXISTS: box$a$1$run$b$1 TARGET_BACKENDS=JS_IR +fun box(): String { + var result = "FAILURE" + + val a: A = object : A { + override fun run() { + val b = object { + fun foo() { + result = "OK" + } + } + b.foo() + } + } + + a.run() + return result +}