From 8fe96664b7c128831d293188b1fb21c4b15bbfff Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 21 Mar 2017 13:09:50 +0300 Subject: [PATCH] Fix initialization order of enum with companion object in JS BE Make enum entries initialize before companion object. This helps in situation when companion object initializer refers to enum fields. JVM be generates method which first initializes all enum fields and then runs companion object initializer. This commit introduces the similar behaviour in JS BE. The old behaviour was: initialize companion object in constructor. In enum, constructor is called to initialize enum fields, so previously companion object was initialized first, which is incorrect. See KT-16745 --- .../codegen/box/enum/enumCompanionInit.kt | 46 +++++++++++++++++++ .../light-analysis/enum/enumCompanionInit.txt | 42 +++++++++++++++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 6 +++ .../codegen/BlackBoxCodegenTestGenerated.java | 6 +++ .../semantics/JsCodegenBoxTestGenerated.java | 6 +++ .../declaration/AbstractDeclarationVisitor.kt | 6 ++- .../translate/declaration/ClassTranslator.kt | 18 +++++--- .../declaration/DeclarationBodyVisitor.kt | 5 +- .../expression/ExpressionVisitor.java | 2 +- 9 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 compiler/testData/codegen/box/enum/enumCompanionInit.kt create mode 100644 compiler/testData/codegen/light-analysis/enum/enumCompanionInit.txt diff --git a/compiler/testData/codegen/box/enum/enumCompanionInit.kt b/compiler/testData/codegen/box/enum/enumCompanionInit.kt new file mode 100644 index 00000000000..e45b666ec6f --- /dev/null +++ b/compiler/testData/codegen/box/enum/enumCompanionInit.kt @@ -0,0 +1,46 @@ +var result = "" + +enum class E(a: String) { + X("x"), + Y("y"); + + init { + result += "E.init($a);" + } + + companion object { + init { + result += "E.companion.init;" + val value = E.values()[0].name + result += "$value;" + } + } +} + +enum class F(a: String) { + X("x"), + Y("y"); + + init { + result += "F.init($a);" + } + + companion object { + init { + result += "F.companion.init;" + } + + fun foo() { + result += "F.foo();$X;" + } + } +} + +fun box(): String { + val y = E.Y + result += "${y.name};" + F.foo() + if (result != "E.init(x);E.init(y);E.companion.init;X;Y;F.init(x);F.init(y);F.companion.init;F.foo();X;") return "fail: $result" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/light-analysis/enum/enumCompanionInit.txt b/compiler/testData/codegen/light-analysis/enum/enumCompanionInit.txt new file mode 100644 index 00000000000..8e2dbd024ea --- /dev/null +++ b/compiler/testData/codegen/light-analysis/enum/enumCompanionInit.txt @@ -0,0 +1,42 @@ +@kotlin.Metadata +public enum class E { + public final static field Companion: E.Companion + public final static field X: E + public final static field Y: E + inner class E/Companion + protected method (@java.lang.Synthetic p0: java.lang.String, @java.lang.Synthetic p1: int, @org.jetbrains.annotations.NotNull p2: java.lang.String): void + public static method valueOf(p0: java.lang.String): E + public static method values(): E[] +} + +@kotlin.Metadata +public final static class E/Companion { + inner class E/Companion + private method (): void +} + +@kotlin.Metadata +public final class EnumCompanionInitKt { + private static @org.jetbrains.annotations.NotNull field result: java.lang.String + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static @org.jetbrains.annotations.NotNull method getResult(): java.lang.String + public final static method setResult(@org.jetbrains.annotations.NotNull p0: java.lang.String): void +} + +@kotlin.Metadata +public enum class F { + public final static field Companion: F.Companion + public final static field X: F + public final static field Y: F + inner class F/Companion + protected method (@java.lang.Synthetic p0: java.lang.String, @java.lang.Synthetic p1: int, @org.jetbrains.annotations.NotNull p2: java.lang.String): void + public static method valueOf(p0: java.lang.String): F + public static method values(): F[] +} + +@kotlin.Metadata +public final static class F/Companion { + inner class F/Companion + private method (): void + public final method foo(): void +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 58e570e0f66..25a974b525e 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -7319,6 +7319,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("enumCompanionInit.kt") + public void testEnumCompanionInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumCompanionInit.kt"); + doTest(fileName); + } + @TestMetadata("enumInheritedFromTrait.kt") public void testEnumInheritedFromTrait() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index f36b03f301c..62ad59aabc1 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -7319,6 +7319,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("enumCompanionInit.kt") + public void testEnumCompanionInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumCompanionInit.kt"); + doTest(fileName); + } + @TestMetadata("enumInheritedFromTrait.kt") public void testEnumInheritedFromTrait() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index df5b352f201..e080416474d 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -8154,6 +8154,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("enumCompanionInit.kt") + public void testEnumCompanionInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumCompanionInit.kt"); + doTest(fileName); + } + @TestMetadata("enumInheritedFromTrait.kt") public void testEnumInheritedFromTrait() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt index f2cbb7cd14b..8ad8e0553d6 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.isOverridable import org.jetbrains.kotlin.js.backend.ast.JsExpression +import org.jetbrains.kotlin.js.backend.ast.JsName import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters import org.jetbrains.kotlin.js.translate.expression.translateFunction @@ -34,8 +35,11 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty abstract class AbstractDeclarationVisitor : TranslatorVisitor() { override fun emptyResult(context: TranslationContext) { } + open val enumInitializerName: JsName? + get() = null + override fun visitClassOrObject(classOrObject: KtClassOrObject, context: TranslationContext) { - ClassTranslator.translate(classOrObject, context) + ClassTranslator.translate(classOrObject, context, enumInitializerName) val descriptor = BindingUtils.getClassDescriptor(context.bindingContext(), classOrObject) context.export(descriptor) } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/ClassTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/ClassTranslator.kt index 720a9afc901..d92530e7882 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/ClassTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/ClassTranslator.kt @@ -92,6 +92,12 @@ class ClassTranslator private constructor( constructorFunction.body.statements += bodyVisitor.initializerStatements delegationTranslator.generateDelegated() + val companionDescriptor = descriptor.companionObjectDescriptor + if (enumInitFunction != null && companionDescriptor != null) { + val initInvocation = JsInvocation(JsAstUtils.pureFqn(context().getNameForObjectInstance(companionDescriptor), null)) + enumInitFunction.body.statements += JsAstUtils.asSyntheticStatement(initInvocation) + } + translatePrimaryConstructor(constructorFunction, context, delegationTranslator) addMetadataObject() addMetadataType() @@ -446,7 +452,10 @@ class ClassTranslator private constructor( val instanceFun = context().createRootScopedFunction("Instance function: " + descriptor) instanceFun.name = context().getNameForObjectInstance(descriptor) - if (enumInitializerName == null) { + if (enumInitializerName != null) { + instanceFun.body.statements += JsInvocation(pureFqn(enumInitializerName, null)).makeStmt() + } + if (descriptor.kind != ClassKind.ENUM_ENTRY) { val instanceCreatedCondition = JsAstUtils.equality(cachedInstanceName.makeRef(), JsLiteral.NULL) val instanceCreationBlock = JsBlock() val instanceCreatedGuard = JsIf(instanceCreatedCondition, instanceCreationBlock) @@ -455,9 +464,6 @@ class ClassTranslator private constructor( val objectRef = context().getInnerReference(descriptor) instanceCreationBlock.statements += JsNew(objectRef).makeStmt() } - else { - instanceFun.body.statements += JsInvocation(pureFqn(enumInitializerName, null)).makeStmt() - } instanceFun.body.statements += JsReturn(cachedInstanceName.makeRef()) @@ -534,8 +540,8 @@ class ClassTranslator private constructor( } companion object { - @JvmStatic fun translate(classDeclaration: KtClassOrObject, context: TranslationContext) { - return ClassTranslator(classDeclaration, context, null, null).translate() + @JvmStatic fun translate(classDeclaration: KtClassOrObject, context: TranslationContext, enumInitializerName: JsName?) { + return ClassTranslator(classDeclaration, context, enumInitializerName, null).translate() } @JvmStatic fun translate(classDeclaration: KtEnumEntry, context: TranslationContext, enumInitializerName: JsName, ordinal: Int) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DeclarationBodyVisitor.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DeclarationBodyVisitor.kt index 1e9ed86b5cf..f35e7237357 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DeclarationBodyVisitor.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/DeclarationBodyVisitor.kt @@ -40,11 +40,14 @@ class DeclarationBodyVisitor( val initializerStatements = mutableListOf() val enumEntries = mutableListOf() + override val enumInitializerName: JsName? + get() = enumInitializer?.name + override fun visitClassOrObject(classOrObject: KtClassOrObject, context: TranslationContext) { super.visitClassOrObject(classOrObject, context) if (classOrObject is KtObjectDeclaration) { - if (classOrObject.isCompanion()) { + if (classOrObject.isCompanion() && containingClass.kind != ClassKind.ENUM_CLASS) { val descriptor = BindingUtils.getDescriptorForElement(context.bindingContext(), classOrObject) as ClassDescriptor addInitializerStatement(JsInvocation(context.getNameForObjectInstance(descriptor).makeRef()).makeStmt()) } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java index f7f3705cf38..1d17970d2bb 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/ExpressionVisitor.java @@ -604,6 +604,6 @@ public final class ExpressionVisitor extends TranslatorVisitor { ) { JsScope scope = context.getScopeForDescriptor(descriptor); TranslationContext classContext = context.innerWithUsageTracker(scope, descriptor); - ClassTranslator.translate(declaration, classContext); + ClassTranslator.translate(declaration, classContext, null); } }