JS: support enums in flat JS structure

This commit is contained in:
Alexey Andreev
2016-10-19 13:15:59 +03:00
parent 54925e7f83
commit 06476402a2
7 changed files with 198 additions and 33 deletions
+4
View File
@@ -28,6 +28,10 @@ class Enum<T : Enum<T>> : Comparable<Enum<T>> {
override fun compareTo(other: Enum<T>) = ordinal.compareTo(other.ordinal)
override fun equals(other: Any?) = this === other
override fun hashCode(): Int = js("Kotlin.identityHashCode(this)")
override fun toString() = name
}
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getSupertypesWi
import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getPrimaryConstructorParameters
import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.toInvocationWith
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtSecondaryConstructor
import org.jetbrains.kotlin.resolve.BindingContextUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -55,7 +56,9 @@ import org.jetbrains.kotlin.utils.identity
*/
class ClassTranslator private constructor(
private val classDeclaration: KtClassOrObject,
context: TranslationContext
context: TranslationContext,
private val enumInitializerName: JsName?,
private val ordinal: Int?
) : AbstractTranslator(context) {
private val descriptor = getClassDescriptor(context.bindingContext(), classDeclaration)
@@ -73,12 +76,13 @@ class ClassTranslator private constructor(
val context = context().newDeclaration(descriptor)
val constructorFunction = context.defineTopLevelFunction(descriptor)
val enumInitFunction = if (descriptor.kind == ClassKind.ENUM_CLASS) createEnumInitFunction() else null
val nonConstructorContext = context.innerWithUsageTracker(scope, descriptor)
nonConstructorContext.startDeclaration()
val delegationTranslator = DelegationTranslator(classDeclaration, nonConstructorContext)
translatePropertiesAsConstructorParameters(nonConstructorContext)
val bodyVisitor = DeclarationBodyVisitor(descriptor, nonConstructorContext)
val bodyVisitor = DeclarationBodyVisitor(descriptor, nonConstructorContext, enumInitFunction)
bodyVisitor.traverseContainer(classDeclaration, nonConstructorContext)
constructorFunction.body.statements += bodyVisitor.initializerStatements
delegationTranslator.generateDelegated()
@@ -104,6 +108,10 @@ class ClassTranslator private constructor(
if (isObjectLike()) {
addObjectMethods()
}
if (descriptor.kind == ClassKind.ENUM_CLASS) {
generateEnumStandardMethods(bodyVisitor.enumEntries)
}
}
private fun translatePrimaryConstructor(
@@ -116,8 +124,13 @@ class ClassTranslator private constructor(
if (isObjectLike()) {
addObjectCache(constructorFunction.body.statements)
}
ClassInitializerTranslator(classDeclaration, constructorContext, constructorFunction)
.generateInitializeMethod(delegationTranslator)
ClassInitializerTranslator(classDeclaration, constructorContext, constructorFunction).apply {
if (ordinal != null) {
setOrdinal(ordinal)
}
generateInitializeMethod(delegationTranslator)
}
primaryConstructor = ConstructorInfo(constructorFunction, constructorContext, descriptor)
if (descriptor.kind == ClassKind.ENUM_CLASS) {
@@ -126,10 +139,19 @@ class ClassTranslator private constructor(
}
}
private fun createEnumInitFunction(): JsFunction {
val function = context().createTopLevelAnonymousFunction(descriptor)
function.name = context().createGlobalName(StaticContext.getSuggestedName(descriptor) + "_initFields")
val emptyFunction = context().createTopLevelAnonymousFunction(descriptor)
function.body.statements += JsAstUtils.assignment(JsAstUtils.pureFqn(function.name, null), emptyFunction).makeStmt()
context().addDeclarationStatement(function.makeStmt())
return function
}
private fun addEnumClassParameters(constructorFunction: JsFunction) {
val nameParamName = constructorFunction.scope.declareFreshName("name")
val ordinalParamName = constructorFunction.scope.declareFreshName("ordinal")
constructorFunction.parameters += listOf(JsParameter(nameParamName), JsParameter(ordinalParamName))
constructorFunction.parameters.addAll(0, listOf(JsParameter(nameParamName), JsParameter(ordinalParamName)))
constructorFunction.body.statements += JsAstUtils.assignmentToThisField(Namer.ENUM_NAME_FIELD, nameParamName.makeRef())
constructorFunction.body.statements += JsAstUtils.assignmentToThisField(Namer.ENUM_ORDINAL_FIELD, ordinalParamName.makeRef())
@@ -200,6 +222,12 @@ class ClassTranslator private constructor(
// Add parameter for outer instance
val leadingArgs = mutableListOf<JsExpression>()
if (descriptor.kind == ClassKind.ENUM_CLASS) {
val nameParamName = constructorInitializer.scope.declareFreshName("name")
val ordinalParamName = constructorInitializer.scope.declareFreshName("ordinal")
constructorInitializer.parameters.addAll(0, listOf(JsParameter(nameParamName), JsParameter(ordinalParamName)))
leadingArgs += listOf(nameParamName.makeRef(), ordinalParamName.makeRef())
}
if (outerClassName != null) {
constructorInitializer.parameters.add(0, JsParameter(outerClassName))
leadingArgs += outerClassName.makeRef()
@@ -242,9 +270,7 @@ class ClassTranslator private constructor(
secondaryConstructors += ConstructorInfo(constructorInitializer, context, constructorDescriptor, compositeSuperCallGenerator)
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
context.export(constructorDescriptor)
}
context.export(constructorDescriptor)
}
private val allConstructors: Sequence<ConstructorInfo>
@@ -387,19 +413,28 @@ class ClassTranslator private constructor(
val instanceFun = JsFunction(context().rootFunction.scope, JsBlock(), "Instance function: " + descriptor)
instanceFun.name = context().getNameForObjectInstance(descriptor)
val instanceCreatedCondition = JsAstUtils.equality(cachedInstanceName.makeRef(), JsLiteral.NULL)
val instanceCreationBlock = JsBlock()
val instanceCreatedGuard = JsIf(instanceCreatedCondition, instanceCreationBlock)
instanceFun.body.statements += instanceCreatedGuard
if (enumInitializerName == null) {
val instanceCreatedCondition = JsAstUtils.equality(cachedInstanceName.makeRef(), JsLiteral.NULL)
val instanceCreationBlock = JsBlock()
val instanceCreatedGuard = JsIf(instanceCreatedCondition, instanceCreationBlock)
instanceFun.body.statements += instanceCreatedGuard
val objectRef = context().getInnerReference(descriptor)
instanceCreationBlock.statements += JsAstUtils.assignment(cachedInstanceName.makeRef(), JsNew(objectRef)).makeStmt()
val objectRef = context().getInnerReference(descriptor)
instanceCreationBlock.statements += JsAstUtils.assignment(cachedInstanceName.makeRef(), JsNew(objectRef)).makeStmt()
}
else {
instanceFun.body.statements += JsInvocation(pureFqn(enumInitializerName, null)).makeStmt()
}
instanceFun.body.statements += JsReturn(cachedInstanceName.makeRef())
context().addDeclarationStatement(instanceFun.makeStmt())
}
private fun generateEnumStandardMethods(entries: List<ClassDescriptor>) {
EnumTranslator(context(), descriptor, entries).generateStandardMethods()
}
private fun generatedBridgeMethods() {
if (isAnnotation()) return
@@ -450,7 +485,11 @@ class ClassTranslator private constructor(
companion object {
@JvmStatic fun translate(classDeclaration: KtClassOrObject, context: TranslationContext) {
return ClassTranslator(classDeclaration, context).translate()
return ClassTranslator(classDeclaration, context, null, null).translate()
}
@JvmStatic fun translate(classDeclaration: KtEnumEntry, context: TranslationContext, enumInitializerName: JsName, ordinal: Int) {
return ClassTranslator(classDeclaration, context, enumInitializerName, ordinal).translate()
}
@JvmStatic fun addInterfaceDefaultMembers(descriptor: ClassDescriptor, context: StaticContext) {
@@ -25,16 +25,19 @@ import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.initializer.ClassInitializerTranslator
import org.jetbrains.kotlin.js.translate.utils.*
import org.jetbrains.kotlin.js.translate.utils.BindingUtils.getClassDescriptor
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getSupertypesWithoutFakes
import org.jetbrains.kotlin.psi.*
import java.util.*
class DeclarationBodyVisitor(
private val containingClass: ClassDescriptor,
private val context: TranslationContext
private val context: TranslationContext,
private val enumInitializer: JsFunction?
) : AbstractDeclarationVisitor() {
private var enumEntryOrdinal: Int = 0
val initializerStatements = ArrayList<JsStatement>()
val initializerStatements = mutableListOf<JsStatement>()
val enumEntries = mutableListOf<ClassDescriptor>()
override fun visitClassOrObject(classOrObject: KtClassOrObject, context: TranslationContext) {
super.visitClassOrObject(classOrObject, context)
@@ -48,29 +51,31 @@ class DeclarationBodyVisitor(
}
override fun visitEnumEntry(enumEntry: KtEnumEntry, context: TranslationContext) {
val enumInitializer = this.enumInitializer!!
val descriptor = getClassDescriptor(context.bindingContext(), enumEntry)
val supertypes = getSupertypesWithoutFakes(descriptor)
enumEntries += descriptor
if (enumEntry.getBody() != null || supertypes.size > 1) {
ClassTranslator.translate(enumEntry, context)
ClassTranslator.translate(enumEntry, context, enumInitializer.name, enumEntryOrdinal)
enumInitializer.body.statements += JsNew(context.getInnerReference(descriptor)).makeStmt()
}
else {
// Simplify by omitting _getInstance() function
val enumName = context.getInnerNameForDescriptor(descriptor)
val enumInstanceName = context.createGlobalName(enumName.ident + "_instance")
assert(supertypes.size == 1) { "Simple Enum entry must have one supertype" }
val jsEnumEntryCreation = ClassInitializerTranslator
.generateEnumEntryInstanceCreation(context, supertypes[0], enumEntry, enumEntryOrdinal)
context.addDeclarationStatement(JsAstUtils.newVar(enumInstanceName, jsEnumEntryCreation))
val jsEnumEntryFunction = context.createTopLevelAnonymousFunction(descriptor)
jsEnumEntryFunction.body.statements.add(JsReturn(jsEnumEntryCreation))
context.addDeclarationStatement(JsAstUtils.newVar(enumInstanceName, null))
enumInitializer.body.statements += JsAstUtils.assignment(pureFqn(enumInstanceName, null), jsEnumEntryCreation).makeStmt()
val enumInstanceFunction = context.createTopLevelAnonymousFunction(descriptor)
enumInstanceFunction.name = context.getNameForObjectInstance(descriptor)
context.addDeclarationStatement(enumInstanceFunction.makeStmt())
enumInstanceFunction.body.statements.add(JsReturn(enumInstanceName.makeRef()))
enumInstanceFunction.body.statements += JsInvocation(pureFqn(enumInitializer.name, null)).makeStmt()
enumInstanceFunction.body.statements += JsReturn(enumInstanceName.makeRef())
}
enumEntryOrdinal++
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.declaration
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
class EnumTranslator(
context: TranslationContext,
val descriptor: ClassDescriptor,
val entries: List<ClassDescriptor>
) : AbstractTranslator(context) {
fun generateStandardMethods() {
generateValuesFunction()
generateValueOfFunction()
}
private fun generateValuesFunction() {
val function = createFunction(getEnumFunction(DescriptorUtils.ENUM_VALUES))
val values = entries.map { JsInvocation(JsAstUtils.pureFqn(context().getNameForObjectInstance(it), null)) }
function.body.statements += JsReturn(JsArrayLiteral(values))
}
private fun generateValueOfFunction() {
val function = createFunction(getEnumFunction(DescriptorUtils.ENUM_VALUE_OF))
val nameParam = function.scope.declareFreshName("name")
function.parameters += JsParameter(nameParam)
val clauses = entries.map { entry ->
JsCase().apply {
caseExpression = context().program().getStringLiteral(entry.name.asString())
statements += JsReturn(JsInvocation(JsAstUtils.pureFqn(context().getNameForObjectInstance(entry), null)))
}
}
if (clauses.isNotEmpty()) {
function.body.statements += JsSwitch(nameParam.makeRef(), clauses)
}
}
private fun createFunction(functionDescriptor: FunctionDescriptor): JsFunction {
val function = context().getFunctionObject(functionDescriptor)
function.name = context().getInnerNameForDescriptor(functionDescriptor)
context().addDeclarationStatement(function.makeStmt())
val classRef = context().getInnerReference(descriptor)
val functionRef = function.name.makeRef()
val assignment = JsAstUtils.assignment(JsNameRef(context().getNameForDescriptor(functionDescriptor), classRef), functionRef)
context().addDeclarationStatement(assignment.makeStmt())
return function
}
private fun getEnumFunction(name: Name): FunctionDescriptor {
val functions = descriptor.staticScope.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS)
return functions
.mapNotNull { (it as? FunctionDescriptor)?.original }
.first { it.name == name }
}
}
@@ -64,6 +64,8 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
@NotNull
private final ClassDescriptor classDescriptor;
private int ordinal;
public ClassInitializerTranslator(
@NotNull KtClassOrObject classDeclaration,
@NotNull TranslationContext context,
@@ -78,6 +80,10 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
fillInitFunction(classDeclaration, context);
}
public void setOrdinal(int ordinal) {
this.ordinal = ordinal;
}
@NotNull
@Override
protected TranslationContext context() {
@@ -153,15 +159,27 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
) {
ResolvedCall<FunctionDescriptor> superCall = getSuperCall(context.bindingContext(), classDeclaration);
JsExpression nameArg = context.program().getStringLiteral(classDeclaration.getName());
JsExpression ordinalArg = context.program().getNumberLiteral(ordinal);
List<JsExpression> additionalArgs = Arrays.asList(nameArg, ordinalArg);
if (superCall == null) {
ClassDescriptor classDescriptor = getClassDescriptorForType(enumClassType);
JsNameRef reference = context.getInnerReference(classDescriptor);
JsExpression nameArg = context.program().getStringLiteral(classDeclaration.getName());
JsExpression ordinalArg = context.program().getNumberLiteral(ordinal);
return new JsNew(reference, Arrays.asList(nameArg, ordinalArg));
return new JsNew(reference, additionalArgs);
}
return CallTranslator.translate(context, superCall);
JsExpression call = CallTranslator.translate(context, superCall);
if (call instanceof JsInvocation) {
JsInvocation invocation = (JsInvocation) call;
invocation.getArguments().addAll(0, additionalArgs);
}
else if (call instanceof JsNew) {
JsNew invocation = (JsNew) call;
invocation.getArguments().addAll(0, additionalArgs);
}
return call;
}
private void mayBeAddCallToSuperMethod(JsFunction initializer) {
@@ -172,15 +190,16 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
ResolvedCall<FunctionDescriptor> superCall = getSuperCall(bindingContext(), classDeclaration);
if (superCall == null) {
if (DescriptorUtils.isEnumEntry(classDescriptor)) {
addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
addCallToSuperMethod(getAdditionalArgumentsForEnumConstructor(), initializer);
}
return;
}
if (classDeclaration instanceof KtEnumEntry) {
JsExpression expression = CallTranslator.translate(context(), superCall, null);
JsExpression fixedInvocation = AstUtilsKt.toInvocationWith(expression, Collections.<JsExpression>emptyList(), 0,
JsLiteral.THIS);
JsExpression fixedInvocation = AstUtilsKt.toInvocationWith(
expression, getAdditionalArgumentsForEnumConstructor(), 0, JsLiteral.THIS);
initFunction.getBody().getStatements().add(fixedInvocation.makeStmt());
}
else {
@@ -235,6 +254,14 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
}
}
@NotNull
private List<JsExpression> getAdditionalArgumentsForEnumConstructor() {
List<JsExpression> additionalArguments = new ArrayList<JsExpression>();
additionalArguments.add(program().getStringLiteral(classDescriptor.getName().asString()));
additionalArguments.add(program().getNumberLiteral(ordinal));
return additionalArguments;
}
private void addCallToSuperMethod(@NotNull List<JsExpression> arguments, @NotNull JsFunction initializer) {
if (initializer.getName() == null) {
JsName ref = context().scope().declareName(Namer.CALLEE_NAME);
@@ -56,7 +56,11 @@ fun JsNode.any(predicate: (JsNode) -> Boolean): Boolean {
return visitor.matched
}
fun JsExpression.toInvocationWith(leadingExtraArgs: List<JsExpression>, parameterCount: Int, thisExpr: JsExpression): JsExpression {
fun JsExpression.toInvocationWith(
leadingExtraArgs: List<JsExpression>,
parameterCount: Int,
thisExpr: JsExpression
): JsExpression {
val qualifier: JsExpression
fun padArguments(arguments: List<JsExpression>) = arguments + (1..(parameterCount - arguments.size))
.map { Namer.getUndefinedExpression() }
+4 -2
View File
@@ -143,11 +143,11 @@
return type === "string" ||
type === "boolean" ||
Kotlin.isNumber(value) ||
Kotlin.isType(value, Kotlin.Comparable);
Kotlin.isType(value, Kotlin.kotlin.Comparable);
};
Kotlin.isCharSequence = function (value) {
return typeof value === "string" || Kotlin.isType(value, CharSequence);
return typeof value === "string" || Kotlin.isType(value, Kotlin.kotlin.CharSequence);
};
Kotlin.charInc = function (value) {
@@ -497,5 +497,7 @@
}
return obj1;
};
Kotlin.identityHashCode = getObjectHashCode;
})(Kotlin);