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 <clinit> 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
This commit is contained in:
Alexey Andreev
2017-03-21 13:09:50 +03:00
parent 0606ebe0dc
commit 8fe96664b7
9 changed files with 128 additions and 9 deletions
+46
View File
@@ -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"
}
@@ -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 <init>(@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 <init>(): 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 <init>(@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 <init>(): void
public final method foo(): void
}
@@ -7319,6 +7319,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName); 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") @TestMetadata("enumInheritedFromTrait.kt")
public void testEnumInheritedFromTrait() throws Exception { public void testEnumInheritedFromTrait() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt");
@@ -7319,6 +7319,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName); 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") @TestMetadata("enumInheritedFromTrait.kt")
public void testEnumInheritedFromTrait() throws Exception { public void testEnumInheritedFromTrait() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt");
@@ -8154,6 +8154,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
doTest(fileName); 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") @TestMetadata("enumInheritedFromTrait.kt")
public void testEnumInheritedFromTrait() throws Exception { public void testEnumInheritedFromTrait() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/enum/enumInheritedFromTrait.kt");
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.isOverridable import org.jetbrains.kotlin.descriptors.isOverridable
import org.jetbrains.kotlin.js.backend.ast.JsExpression 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.context.TranslationContext
import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters
import org.jetbrains.kotlin.js.translate.expression.translateFunction import org.jetbrains.kotlin.js.translate.expression.translateFunction
@@ -34,8 +35,11 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
abstract class AbstractDeclarationVisitor : TranslatorVisitor<Unit>() { abstract class AbstractDeclarationVisitor : TranslatorVisitor<Unit>() {
override fun emptyResult(context: TranslationContext) { } override fun emptyResult(context: TranslationContext) { }
open val enumInitializerName: JsName?
get() = null
override fun visitClassOrObject(classOrObject: KtClassOrObject, context: TranslationContext) { override fun visitClassOrObject(classOrObject: KtClassOrObject, context: TranslationContext) {
ClassTranslator.translate(classOrObject, context) ClassTranslator.translate(classOrObject, context, enumInitializerName)
val descriptor = BindingUtils.getClassDescriptor(context.bindingContext(), classOrObject) val descriptor = BindingUtils.getClassDescriptor(context.bindingContext(), classOrObject)
context.export(descriptor) context.export(descriptor)
} }
@@ -92,6 +92,12 @@ class ClassTranslator private constructor(
constructorFunction.body.statements += bodyVisitor.initializerStatements constructorFunction.body.statements += bodyVisitor.initializerStatements
delegationTranslator.generateDelegated() 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) translatePrimaryConstructor(constructorFunction, context, delegationTranslator)
addMetadataObject() addMetadataObject()
addMetadataType() addMetadataType()
@@ -446,7 +452,10 @@ class ClassTranslator private constructor(
val instanceFun = context().createRootScopedFunction("Instance function: " + descriptor) val instanceFun = context().createRootScopedFunction("Instance function: " + descriptor)
instanceFun.name = context().getNameForObjectInstance(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 instanceCreatedCondition = JsAstUtils.equality(cachedInstanceName.makeRef(), JsLiteral.NULL)
val instanceCreationBlock = JsBlock() val instanceCreationBlock = JsBlock()
val instanceCreatedGuard = JsIf(instanceCreatedCondition, instanceCreationBlock) val instanceCreatedGuard = JsIf(instanceCreatedCondition, instanceCreationBlock)
@@ -455,9 +464,6 @@ class ClassTranslator private constructor(
val objectRef = context().getInnerReference(descriptor) val objectRef = context().getInnerReference(descriptor)
instanceCreationBlock.statements += JsNew(objectRef).makeStmt() instanceCreationBlock.statements += JsNew(objectRef).makeStmt()
} }
else {
instanceFun.body.statements += JsInvocation(pureFqn(enumInitializerName, null)).makeStmt()
}
instanceFun.body.statements += JsReturn(cachedInstanceName.makeRef()) instanceFun.body.statements += JsReturn(cachedInstanceName.makeRef())
@@ -534,8 +540,8 @@ class ClassTranslator private constructor(
} }
companion object { companion object {
@JvmStatic fun translate(classDeclaration: KtClassOrObject, context: TranslationContext) { @JvmStatic fun translate(classDeclaration: KtClassOrObject, context: TranslationContext, enumInitializerName: JsName?) {
return ClassTranslator(classDeclaration, context, null, null).translate() return ClassTranslator(classDeclaration, context, enumInitializerName, null).translate()
} }
@JvmStatic fun translate(classDeclaration: KtEnumEntry, context: TranslationContext, enumInitializerName: JsName, ordinal: Int) { @JvmStatic fun translate(classDeclaration: KtEnumEntry, context: TranslationContext, enumInitializerName: JsName, ordinal: Int) {
@@ -40,11 +40,14 @@ class DeclarationBodyVisitor(
val initializerStatements = mutableListOf<JsStatement>() val initializerStatements = mutableListOf<JsStatement>()
val enumEntries = mutableListOf<ClassDescriptor>() val enumEntries = mutableListOf<ClassDescriptor>()
override val enumInitializerName: JsName?
get() = enumInitializer?.name
override fun visitClassOrObject(classOrObject: KtClassOrObject, context: TranslationContext) { override fun visitClassOrObject(classOrObject: KtClassOrObject, context: TranslationContext) {
super.visitClassOrObject(classOrObject, context) super.visitClassOrObject(classOrObject, context)
if (classOrObject is KtObjectDeclaration) { if (classOrObject is KtObjectDeclaration) {
if (classOrObject.isCompanion()) { if (classOrObject.isCompanion() && containingClass.kind != ClassKind.ENUM_CLASS) {
val descriptor = BindingUtils.getDescriptorForElement(context.bindingContext(), classOrObject) as ClassDescriptor val descriptor = BindingUtils.getDescriptorForElement(context.bindingContext(), classOrObject) as ClassDescriptor
addInitializerStatement(JsInvocation(context.getNameForObjectInstance(descriptor).makeRef()).makeStmt()) addInitializerStatement(JsInvocation(context.getNameForObjectInstance(descriptor).makeRef()).makeStmt())
} }
@@ -604,6 +604,6 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
) { ) {
JsScope scope = context.getScopeForDescriptor(descriptor); JsScope scope = context.getScopeForDescriptor(descriptor);
TranslationContext classContext = context.innerWithUsageTracker(scope, descriptor); TranslationContext classContext = context.innerWithUsageTracker(scope, descriptor);
ClassTranslator.translate(declaration, classContext); ClassTranslator.translate(declaration, classContext, null);
} }
} }