From 06476402a2509ca983ff50287ca122a7de3ffcea Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 19 Oct 2016 13:15:59 +0300 Subject: [PATCH] JS: support enums in flat JS structure --- js/js.libraries/src/builtins/kotlin.kt | 4 + .../translate/declaration/ClassTranslator.kt | 69 +++++++++++---- .../declaration/DeclarationBodyVisitor.kt | 21 +++-- .../translate/declaration/EnumTranslator.kt | 84 +++++++++++++++++++ .../ClassInitializerTranslator.java | 41 +++++++-- .../kotlin/js/translate/utils/astUtils.kt | 6 +- js/js.translator/testData/kotlin_lib.js | 6 +- 7 files changed, 198 insertions(+), 33 deletions(-) create mode 100644 js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/EnumTranslator.kt diff --git a/js/js.libraries/src/builtins/kotlin.kt b/js/js.libraries/src/builtins/kotlin.kt index 93d9ca34825..c99ce26d86a 100644 --- a/js/js.libraries/src/builtins/kotlin.kt +++ b/js/js.libraries/src/builtins/kotlin.kt @@ -28,6 +28,10 @@ class Enum> : Comparable> { override fun compareTo(other: Enum) = ordinal.compareTo(other.ordinal) + override fun equals(other: Any?) = this === other + + override fun hashCode(): Int = js("Kotlin.identityHashCode(this)") + override fun toString() = name } 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 a47e213a200..74ee3fffd29 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 @@ -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() + 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 @@ -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) { + 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) { 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 6e200525f3a..c7b2ec9dd89 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 @@ -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() + val initializerStatements = mutableListOf() + val enumEntries = mutableListOf() 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++ diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/EnumTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/EnumTranslator.kt new file mode 100644 index 00000000000..cc2fb5d9aa2 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/EnumTranslator.kt @@ -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 +) : 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 } + } +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/ClassInitializerTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/ClassInitializerTranslator.java index f126b960b72..4dbb83a93e8 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/ClassInitializerTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/ClassInitializerTranslator.java @@ -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 superCall = getSuperCall(context.bindingContext(), classDeclaration); + JsExpression nameArg = context.program().getStringLiteral(classDeclaration.getName()); + JsExpression ordinalArg = context.program().getNumberLiteral(ordinal); + List 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 superCall = getSuperCall(bindingContext(), classDeclaration); if (superCall == null) { if (DescriptorUtils.isEnumEntry(classDescriptor)) { - addCallToSuperMethod(Collections.emptyList(), initializer); + addCallToSuperMethod(getAdditionalArgumentsForEnumConstructor(), initializer); } return; } if (classDeclaration instanceof KtEnumEntry) { JsExpression expression = CallTranslator.translate(context(), superCall, null); - JsExpression fixedInvocation = AstUtilsKt.toInvocationWith(expression, Collections.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 getAdditionalArgumentsForEnumConstructor() { + List additionalArguments = new ArrayList(); + additionalArguments.add(program().getStringLiteral(classDescriptor.getName().asString())); + additionalArguments.add(program().getNumberLiteral(ordinal)); + return additionalArguments; + } + private void addCallToSuperMethod(@NotNull List arguments, @NotNull JsFunction initializer) { if (initializer.getName() == null) { JsName ref = context().scope().declareName(Namer.CALLEE_NAME); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/astUtils.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/astUtils.kt index c96d44170b4..acf53ead861 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/astUtils.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/astUtils.kt @@ -56,7 +56,11 @@ fun JsNode.any(predicate: (JsNode) -> Boolean): Boolean { return visitor.matched } -fun JsExpression.toInvocationWith(leadingExtraArgs: List, parameterCount: Int, thisExpr: JsExpression): JsExpression { +fun JsExpression.toInvocationWith( + leadingExtraArgs: List, + parameterCount: Int, + thisExpr: JsExpression +): JsExpression { val qualifier: JsExpression fun padArguments(arguments: List) = arguments + (1..(parameterCount - arguments.size)) .map { Namer.getUndefinedExpression() } diff --git a/js/js.translator/testData/kotlin_lib.js b/js/js.translator/testData/kotlin_lib.js index 9e38aec4ca8..049d35ec9de 100644 --- a/js/js.translator/testData/kotlin_lib.js +++ b/js/js.translator/testData/kotlin_lib.js @@ -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);