diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index 441065df63b..bc03b5295b3 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -115,7 +115,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer { val property = (declaration as? IrSimpleFunction)?.correspondingPropertySymbol?.owner if (property?.isExported(context) == true || property?.isEffectivelyExternal() == true) { - context.minimizedNameGenerator.reserveName(property.name.asString()) + context.minimizedNameGenerator.reserveName(property.getJsNameOrKotlinName().identifier) } if (declaration.hasStableJsName(context)) { val signature = jsFunctionSignature(declaration, context) @@ -126,7 +127,7 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi } declaration is IrProperty -> { if (declaration.isExported(context)) { - context.minimizedNameGenerator.reserveName(declaration.name.asString()) + context.minimizedNameGenerator.reserveName(declaration.getJsNameOrKotlinName().identifier) } } } @@ -138,12 +139,22 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi it.declarations.forEach { when { it is IrField -> { - val safeName = if (minimizedMemberNames) { - context.minimizedNameGenerator.generateNextName() - } else it.safeName() - val suffix = nameCnt.getOrDefault(safeName, 0) + 1 - nameCnt[safeName] = suffix - result[it] = safeName + "_$suffix" + val correspondingProperty = it.correspondingPropertySymbol?.owner + val hasStableName = correspondingProperty != null && + correspondingProperty.visibility.isPublicAPI && + (correspondingProperty.isExported(context) || correspondingProperty.getJsName() != null) && + correspondingProperty.isSimpleProperty + val safeName = when { + hasStableName -> (correspondingProperty ?: it).getJsNameOrKotlinName().identifier + minimizedMemberNames -> context.minimizedNameGenerator.generateNextName() + else -> it.safeName() + } + val resultName = if (!hasStableName) { + val suffix = nameCnt.getOrDefault(safeName, 0) + 1 + nameCnt[safeName] = suffix + safeName + "_$suffix" + } else safeName + result[it] = resultName } it is IrFunction && it.dispatchReceiverParameter != null -> { nameCnt[jsFunctionSignature(it, context)] = 1 // avoid clashes with member functions diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index a706fe96d7d..ff158d58c24 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -52,10 +52,13 @@ fun IrWhen.toJsNode( } fun jsElementAccess(name: String, receiver: JsExpression?): JsExpression = - if (receiver == null || name.isValidES5Identifier()) { - JsNameRef(JsName(name, false), receiver) + jsElementAccess(JsName(name, false), receiver) + +fun jsElementAccess(name: JsName, receiver: JsExpression?): JsExpression = + if (receiver == null || name.ident.isValidES5Identifier()) { + JsNameRef(name, receiver) } else { - JsArrayAccess(receiver, JsStringLiteral(name)) + JsArrayAccess(receiver, JsStringLiteral(name.ident)) } fun jsAssignment(left: JsExpression, right: JsExpression) = JsBinaryOperation(JsBinaryOperator.ASG, left, right) diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt index 3d3b06cfe91..cab0a3a56f4 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt @@ -192,6 +192,16 @@ fun IrValueParameter.createStubDefaultValue(): IrExpressionBody = IrErrorExpressionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, "Stub expression for default value of $name") ) +val IrProperty.isSimpleProperty: Boolean + get() { + val getterFun = getter + val setterFun = setter + return !isFakeOverride && + modality === Modality.FINAL && + (getterFun == null || getterFun.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) && + (setterFun == null || setterFun.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) + } + val IrClass.functions: Sequence get() = declarations.asSequence().filterIsInstance() 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 aff2a10d0fe..a6f6b6015ee 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 @@ -2112,6 +2112,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { runTest("js/js.translator/testData/box/export/exportAllFile.kt"); } + @Test + @TestMetadata("exportClassPropertiesInDifferentCombinations.kt") + public void testExportClassPropertiesInDifferentCombinations() throws Exception { + runTest("js/js.translator/testData/box/export/exportClassPropertiesInDifferentCombinations.kt"); + } + @Test @TestMetadata("exportClassWithInternal.kt") public void testExportClassWithInternal() 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 3a8728b966c..dc123e4bf16 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 @@ -2584,6 +2584,12 @@ public class FirJsTestGenerated extends AbstractFirJsTest { runTest("js/js.translator/testData/box/export/exportAllFile.kt"); } + @Test + @TestMetadata("exportClassPropertiesInDifferentCombinations.kt") + public void testExportClassPropertiesInDifferentCombinations() throws Exception { + runTest("js/js.translator/testData/box/export/exportClassPropertiesInDifferentCombinations.kt"); + } + @Test @TestMetadata("exportClassWithInternal.kt") public void testExportClassWithInternal() 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 73f7c164e06..043d87965a5 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 @@ -2584,6 +2584,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/export/exportAllFile.kt"); } + @Test + @TestMetadata("exportClassPropertiesInDifferentCombinations.kt") + public void testExportClassPropertiesInDifferentCombinations() throws Exception { + runTest("js/js.translator/testData/box/export/exportClassPropertiesInDifferentCombinations.kt"); + } + @Test @TestMetadata("exportClassWithInternal.kt") public void testExportClassWithInternal() throws Exception { diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt index 267673b4cf0..209af586e8a 100644 --- a/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt @@ -7,7 +7,7 @@ // FILE: lib.kt @JsExport -fun `@do something like-this`(): Int = 42 +fun `@do something like-this`(`test value`: Int = 42): Int = `test value` // FILE: test.js function box() { diff --git a/js/js.translator/testData/box/export/exportClassPropertiesInDifferentCombinations.kt b/js/js.translator/testData/box/export/exportClassPropertiesInDifferentCombinations.kt new file mode 100644 index 00000000000..df340cf5198 --- /dev/null +++ b/js/js.translator/testData/box/export/exportClassPropertiesInDifferentCombinations.kt @@ -0,0 +1,227 @@ +// EXPECTED_REACHABLE_NODES: 1252 +// IGNORE_BACKEND: JS +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE + +// MODULE: export_class_properties +// FILE: lib.kt + +@JsExport +interface IOwner { + val interfaceValOpen: String + + val interfaceGetterOnly: String + get() = "Interface with Getter" + + var interfaceVarOpen: String +} + +@JsExport +open class Owner { + open val classOpenReadonly: String = "Class Readonly Open" + open var classOpenVar: String = "Class Var Open" + + val classFinalReadonly: String = "Class Readonly Final" + var classFinalVar: String = "Class Default" + + val classGetterOnly: String + get() = "Class with Final Getter" + + open val classOpenGetterOnly: String + get() = "Class with Open Getter" + + var classSetterOnly: String = "Class setter only" + set(value) { classOpenVar = "Class Changed by class setter only $value" } + + open var classOpenSetterOnly: String = "Class open setter only" + set(value) { classOpenVar = "Class Changed by class open setter only $value" } + + var classGetterAndSetter: String = "Class Getter and Setter" + get() = field + " by class getter" + set(value) { field = value } + + open var classOpenGetterAndSetter: String = "Class Open Getter and Setter" + get() = field + " by class open getter" + set(value) { field = value } +} + +open class HiddenChild : Owner(), IOwner { + override val interfaceValOpen: String = "Hidden Child Open Val" + override var interfaceVarOpen: String = "Hidden Child Open Var" + + val hiddenDefaultVal: String = "Hidden Default Val" + val hiddenDefaultGetter: String get() = "Hidden Default Getter" + var hiddenDefaultVar: String = "Hidden Default Var" +} + +open class AnotherOneHiddenChild: HiddenChild() { + override val interfaceGetterOnly: String + get() = "Another One Hidden Child with Getter" +} + +@JsExport +class ExportedChild: AnotherOneHiddenChild() { + override val classOpenReadonly: String = "Exported Child Readonly Open" + override var classOpenVar: String = "Exported Child Var Open" + + override val classOpenGetterOnly: String + get() = "Exported Child with Open Getter" + + override var classOpenSetterOnly: String = super.classOpenSetterOnly + set(value) { classOpenVar = "Exported Child Changed by class open setter only $value" } + + override var classOpenGetterAndSetter: String = super.classOpenGetterAndSetter + get() = field + " by exported child open getter" + set(value) { field = value } +} + +@JsExport +fun generateHiddenChild() = HiddenChild() + +@JsExport +fun generateAnotherHiddenChild() = AnotherOneHiddenChild() + +// FILE: test.js + +function assertEquals(actualValue, expectedValue) { + if (actualValue !== expectedValue) { + throw new Error("Expected value is '" + expectedValue + "', but got '" + actualValue + "'") + } +} + +function testOwner(pkg) { + var owner = new pkg.Owner(); + + assertEquals(owner.classOpenReadonly, "Class Readonly Open") + assertEquals(owner.classOpenVar, "Class Var Open") + + assertEquals(owner.classFinalReadonly, "Class Readonly Final") + assertEquals(owner.classFinalVar, "Class Default") + + assertEquals(owner.classGetterOnly, "Class with Final Getter") + assertEquals(owner.classOpenGetterOnly, "Class with Open Getter") + + assertEquals(owner.classGetterAndSetter, "Class Getter and Setter by class getter") + assertEquals(owner.classOpenGetterAndSetter, "Class Open Getter and Setter by class open getter") + + assertEquals(owner.classSetterOnly, "Class setter only") + + owner.classSetterOnly = "test1" + assertEquals(owner.classOpenVar, "Class Changed by class setter only test1") + + assertEquals(owner.classOpenSetterOnly, "Class open setter only") + + owner.classOpenSetterOnly = "test2" + assertEquals(owner.classOpenVar, "Class Changed by class open setter only test2") +} + +function testHiddenChild(pkg) { + var owner = pkg.generateHiddenChild() + + assertEquals(owner.hiddenDefaultVal, undefined) + assertEquals(owner.hiddenDefaultGetter, undefined) + assertEquals(owner.hiddenDefaultVar, undefined) + + assertEquals(owner.interfaceValOpen, "Hidden Child Open Val") + assertEquals(owner.interfaceVarOpen, "Hidden Child Open Var") + + assertEquals(owner.interfaceGetterOnly, "Interface with Getter") + + assertEquals(owner.classOpenReadonly, "Class Readonly Open") + assertEquals(owner.classOpenVar, "Class Var Open") + + assertEquals(owner.classFinalReadonly, "Class Readonly Final") + assertEquals(owner.classFinalVar, "Class Default") + + assertEquals(owner.classGetterOnly, "Class with Final Getter") + assertEquals(owner.classOpenGetterOnly, "Class with Open Getter") + + assertEquals(owner.classGetterAndSetter, "Class Getter and Setter by class getter") + assertEquals(owner.classOpenGetterAndSetter, "Class Open Getter and Setter by class open getter") + + assertEquals(owner.classSetterOnly, "Class setter only") + + owner.classSetterOnly = "test1" + assertEquals(owner.classOpenVar, "Class Changed by class setter only test1") + + assertEquals(owner.classOpenSetterOnly, "Class open setter only") + + owner.classOpenSetterOnly = "test2" + assertEquals(owner.classOpenVar, "Class Changed by class open setter only test2") +} + +function testAnotherHiddenChild(pkg) { + var owner = pkg.generateAnotherHiddenChild() + + assertEquals(owner.hiddenDefaultVal, undefined) + assertEquals(owner.hiddenDefaultGetter, undefined) + assertEquals(owner.hiddenDefaultVar, undefined) + + assertEquals(owner.interfaceValOpen, "Hidden Child Open Val") + assertEquals(owner.interfaceVarOpen, "Hidden Child Open Var") + + assertEquals(owner.interfaceGetterOnly, "Another One Hidden Child with Getter") + + assertEquals(owner.classOpenReadonly, "Class Readonly Open") + assertEquals(owner.classOpenVar, "Class Var Open") + + assertEquals(owner.classFinalReadonly, "Class Readonly Final") + assertEquals(owner.classFinalVar, "Class Default") + + assertEquals(owner.classGetterOnly, "Class with Final Getter") + assertEquals(owner.classOpenGetterOnly, "Class with Open Getter") + + assertEquals(owner.classGetterAndSetter, "Class Getter and Setter by class getter") + assertEquals(owner.classOpenGetterAndSetter, "Class Open Getter and Setter by class open getter") + + owner.classSetterOnly = "test1" + assertEquals(owner.classOpenVar, "Class Changed by class setter only test1") + + owner.classOpenSetterOnly = "test2" + assertEquals(owner.classOpenVar, "Class Changed by class open setter only test2") +} + +function testExportedChild(pkg) { + var owner = pkg.generateAnotherHiddenChild() + + assertEquals(owner.hiddenDefaultVal, undefined) + assertEquals(owner.hiddenDefaultGetter, undefined) + assertEquals(owner.hiddenDefaultVar, undefined) + + assertEquals(owner.interfaceValOpen, "Hidden Child Open Val") + assertEquals(owner.interfaceVarOpen, "Hidden Child Open Var") + + assertEquals(owner.interfaceGetterOnly, "Another One Hidden Child with Getter") + + assertEquals(owner.classOpenReadonly, "Exported Child Readonly Open") + assertEquals(owner.classOpenVar, "Exported Child Var Open") + + assertEquals(owner.classFinalReadonly, "Class Readonly Final") + assertEquals(owner.classFinalVar, "Class Default") + + assertEquals(owner.classGetterOnly, "Class with Final Getter") + assertEquals(owner.classOpenGetterOnly, "Exported Child with Open Getter") + + assertEquals(owner.classGetterAndSetter, "Class Getter and Setter by class getter") + assertEquals(owner.classOpenGetterAndSetter, "Class Open Getter and Setter by exported child open getter") + + assertEquals(owner.classSetterOnly, "Class setter only") + + owner.classSetterOnly = "test1" + assertEquals(owner.classOpenVar, "Class Changed by class setter only test1") + + assertEquals(owner.classOpenSetterOnly, "Class open setter only") + + owner.classOpenSetterOnly = "test2" + assertEquals(owner.classOpenVar, "Exported Child Changed by class open setter only test2") +} + +function box() { + var pkg = this["export_class_properties"] + + testOwner(pkg) + testHiddenChild(pkg) + testAnotherHiddenChild(pkg) + + return "OK" +}