From adcb359c381f328a7afde6cb7122631260fad792 Mon Sep 17 00:00:00 2001 From: Erokhin Stanislav Date: Mon, 12 Aug 2013 20:26:15 +0400 Subject: [PATCH] JS backend: added enum class support. --- .../k2js/test/semantics/EnumTest.java | 34 ++++++++++++++ .../k2js/translate/context/Namer.java | 17 ++++++- .../translate/context/StandardClasses.java | 2 + .../declaration/ClassTranslator.java | 24 +++++++++- .../declaration/DeclarationBodyVisitor.java | 39 ++++++++++++++- .../ClassInitializerTranslator.java | 31 ++++++++++-- .../translate/utils/JsDescriptorUtils.java | 2 +- .../translate/utils/TranslationUtils.java | 5 ++ .../testFiles/enum/cases/simple.kt | 43 +++++++++++++++++ .../testFiles/enum/cases/standardMethods.kt | 47 +++++++++++++++++++ js/js.translator/testFiles/kotlin_lib.js | 44 ++++++++++++++++- 11 files changed, 279 insertions(+), 9 deletions(-) create mode 100644 js/js.tests/test/org/jetbrains/k2js/test/semantics/EnumTest.java create mode 100644 js/js.translator/testFiles/enum/cases/simple.kt create mode 100644 js/js.translator/testFiles/enum/cases/standardMethods.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/EnumTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/EnumTest.java new file mode 100644 index 00000000000..0114646a432 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/EnumTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2013 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.k2js.test.semantics; + +import org.jetbrains.k2js.test.SingleFileTranslationTest; + +public class EnumTest extends SingleFileTranslationTest { + public EnumTest() { + super("enum/"); + } + + public void testSimple() throws Exception { + checkFooBoxIsOk(); + } + + public void testStandardMethods() throws Exception { + checkFooBoxIsOk(); + } + +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java index 5492189607f..6a9cfeb656e 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java @@ -40,6 +40,7 @@ public final class Namer { private static final String CLASS_OBJECT_NAME = "createClass"; private static final String TRAIT_OBJECT_NAME = "createTrait"; private static final String OBJECT_OBJECT_NAME = "createObject"; + private static final String ENUM_ENTRIES_NAME = "createEnumEntries"; private static final String SETTER_PREFIX = "set_"; private static final String GETTER_PREFIX = "get_"; private static final String BACKING_FIELD_PREFIX = "$"; @@ -142,6 +143,8 @@ public final class Namer { private final JsExpression definePackage; @NotNull private final JsName objectName; + @NotNull + private final JsName enumEntriesName; @NotNull private final JsName isTypeName; @@ -154,6 +157,7 @@ public final class Namer { definePackage = kotlin("definePackage"); className = kotlinScope.declareName(CLASS_OBJECT_NAME); + enumEntriesName = kotlinScope.declareName(ENUM_ENTRIES_NAME); objectName = kotlinScope.declareName(OBJECT_OBJECT_NAME); isTypeName = kotlinScope.declareName("isType"); @@ -164,6 +168,11 @@ public final class Namer { return kotlin(className); } + @NotNull + public JsExpression enumEntriesCreationMethodReference() { + return kotlin(enumEntriesName); + } + @NotNull public JsExpression traitCreationMethodReference() { return kotlin(traitName); @@ -224,13 +233,19 @@ public final class Namer { switch (descriptor.getKind()) { case TRAIT: return new JsInvocation(traitCreationMethodReference()); + case OBJECT: - return new JsInvocation(objectCreationMethodReference()); case CLASS_OBJECT: + case ENUM_ENTRY: return new JsInvocation(objectCreationMethodReference()); default: return new JsInvocation(classCreationMethodReference()); } } + + @NotNull + public JsInvocation enumEntriesObjectCreateInvocation() { + return new JsInvocation(enumEntriesCreationMethodReference()); + } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/StandardClasses.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/StandardClasses.java index 4584b4c83e7..dc9af6c3154 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/StandardClasses.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/StandardClasses.java @@ -99,6 +99,8 @@ public final class StandardClasses { standardClasses.declare().forFQ("jet.IntProgression").kotlinClass("NumberProgression") .methods("iterator", "contains").properties("start", "end", "increment"); + + standardClasses.declare().forFQ("jet.Enum").kotlinClass("Enum"); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java index 4b18c333f17..1b955861990 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/ClassTranslator.java @@ -43,11 +43,13 @@ import java.util.List; import static org.jetbrains.jet.lang.resolve.DescriptorUtils.*; import static org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator.createPlace; +import static org.jetbrains.k2js.translate.initializer.InitializerUtils.createPropertyInitializer; import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor; import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter; import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass; import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters; import static org.jetbrains.k2js.translate.utils.TranslationUtils.getQualifiedReference; +import static org.jetbrains.k2js.translate.utils.TranslationUtils.simpleReturnFunction; /** * Generates a definition of a single class. @@ -173,7 +175,9 @@ public final class ClassTranslator extends AbstractTranslator { } translatePropertiesAsConstructorParameters(declarationContext, properties); - new DeclarationBodyVisitor(properties, staticProperties).traverseContainer(classDeclaration, declarationContext); + DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties); + bodyVisitor.traverseContainer(classDeclaration, declarationContext); + mayBeAddEnumEntry(bodyVisitor.getEnumEntryList(), staticProperties, declarationContext); if (isTopLevelDeclaration) { declarationContext.literalFunctionTranslator().setDefinitionPlace(null); @@ -198,6 +202,21 @@ public final class ClassTranslator extends AbstractTranslator { } } + private void mayBeAddEnumEntry(@NotNull List enumEntryList, + @NotNull List staticProperties, + @NotNull TranslationContext declarationContext + ) { + if (descriptor.getKind() == ClassKind.ENUM_CLASS) { + JsInvocation invocation = context().namer().enumEntriesObjectCreateInvocation(); + invocation.getArguments().add(new JsObjectLiteral(enumEntryList, true)); + + JsFunction fun = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), invocation); + staticProperties.add(createPropertyInitializer(Namer.getNamedForClassObjectInitializer(), fun, declarationContext)); + } else { + assert enumEntryList.isEmpty(): "Only enum class may have enum entry. Class kind is: " + descriptor.getKind(); + } + } + private void addSuperclassReferences(@NotNull JsInvocation jsClassDeclaration) { List superClassReferences = getSupertypesNameReferences(); if (superClassReferences.isEmpty()) { @@ -244,6 +263,9 @@ public final class ClassTranslator extends AbstractTranslator { } list.add(getClassReference(result)); break; + case ENUM_CLASS: + base = getClassReference(result); + break; default: throw new UnsupportedOperationException("unsupported super class kind " + result.getKind().name()); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DeclarationBodyVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DeclarationBodyVisitor.java index 4ed41f144ff..d31de4c1994 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DeclarationBodyVisitor.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/DeclarationBodyVisitor.java @@ -21,13 +21,18 @@ import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.name.Name; +import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.k2js.translate.context.Namer; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.Translation; import org.jetbrains.k2js.translate.general.TranslatorVisitor; +import org.jetbrains.k2js.translate.initializer.ClassInitializerTranslator; import org.jetbrains.k2js.translate.utils.BindingUtils; import org.jetbrains.k2js.translate.utils.JsAstUtils; +import org.jetbrains.k2js.translate.utils.TranslationUtils; +import java.util.Collection; import java.util.List; import static org.jetbrains.k2js.translate.initializer.InitializerUtils.createPropertyInitializer; @@ -36,6 +41,7 @@ import static org.jetbrains.k2js.translate.utils.BindingUtils.*; public class DeclarationBodyVisitor extends TranslatorVisitor { protected final List result; protected final List staticResult; + protected final List enumEntryList = new SmartList(); public DeclarationBodyVisitor() { this(new SmartList(), new SmartList()); @@ -51,11 +57,41 @@ public class DeclarationBodyVisitor extends TranslatorVisitor { return result; } + public List getEnumEntryList() { + return enumEntryList; + } + @Override public Void visitClass(@NotNull JetClass expression, @NotNull TranslationContext context) { return null; } + @Override + public Void visitEnumEntry( + final JetEnumEntry enumEntry, TranslationContext data + ) { + JsExpression jsEnumEntryCreation; + ClassDescriptor descriptor = getClassDescriptor(data.bindingContext(), enumEntry); + Collection supertypes = descriptor.getTypeConstructor().getSupertypes(); + if (enumEntry.getBody() != null || supertypes.size() > 1) { + jsEnumEntryCreation = ClassTranslator.generateClassCreation(enumEntry, descriptor, data); + } else { + assert supertypes.size() == 1 : "Simple Enum entry must have one supertype"; + jsEnumEntryCreation = new ClassInitializerTranslator(enumEntry, data).generateEnumEntryInstanceCreation(supertypes.iterator().next()); + } + Named named = new Named() { + @NotNull + @Override + public Name getName() { + String name = enumEntry.getName(); + assert name != null : "Enum entry name must be not null"; + return Name.identifier(name); + } + }; + enumEntryList.add(new JsPropertyInitializer(data.nameToLiteral(named), jsEnumEntryCreation)); + return null; + } + @Override public Void visitClassObject( JetClassObject classObject, TranslationContext context @@ -65,8 +101,7 @@ public class DeclarationBodyVisitor extends TranslatorVisitor { ClassDescriptor descriptor = getClassDescriptor(context.bindingContext(), declaration); JsExpression value = ClassTranslator.generateClassCreation(declaration, descriptor, context); - JsFunction fun = new JsFunction(context.getScopeForDescriptor(descriptor.getContainingDeclaration())); - fun.setBody(new JsBlock(new JsReturn(value))); + JsFunction fun = TranslationUtils.simpleReturnFunction(context.getScopeForDescriptor(descriptor), value); staticResult.add(createPropertyInitializer(Namer.getNamedForClassObjectInitializer(), fun, context)); return null; } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/initializer/ClassInitializerTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/initializer/ClassInitializerTranslator.java index e3e714e946b..48e63de630e 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/initializer/ClassInitializerTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/initializer/ClassInitializerTranslator.java @@ -27,16 +27,21 @@ import org.jetbrains.jet.lang.psi.JetClassOrObject; import org.jetbrains.jet.lang.psi.JetDelegationSpecifier; import org.jetbrains.jet.lang.psi.JetDelegatorToSuperCall; import org.jetbrains.jet.lang.psi.JetParameter; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.k2js.translate.context.Namer; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.AbstractTranslator; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType; import static org.jetbrains.k2js.translate.utils.BindingUtils.*; import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement; import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters; +import static org.jetbrains.k2js.translate.utils.TranslationUtils.getQualifiedReference; import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateArgumentList; public final class ClassInitializerTranslator extends AbstractTranslator { @@ -75,18 +80,38 @@ public final class ClassInitializerTranslator extends AbstractTranslator { return result; } + @NotNull + public JsExpression generateEnumEntryInstanceCreation(@NotNull JetType enumClassType) { + JetDelegatorToSuperCall superCall = getSuperCall(); + List arguments; + if (superCall != null) { + arguments = translateArguments(superCall); + } else { + arguments = Collections.emptyList(); + } + JsNameRef reference = getQualifiedReference(context(), getClassDescriptorForType(enumClassType)); + if(context().isEcma5()) { + return new JsInvocation(reference, arguments); + } else { + return new JsNew(reference, arguments); + } + } + private void mayBeAddCallToSuperMethod(JsFunction initializer) { + if (classDeclaration.hasModifier(JetTokens.ENUM_KEYWORD)) { + addCallToSuperMethod(Collections.emptyList(), initializer); + return; + } if (hasAncestorClass(bindingContext(), classDeclaration)) { JetDelegatorToSuperCall superCall = getSuperCall(); if (superCall == null) { return; } - addCallToSuperMethod(superCall, initializer); + addCallToSuperMethod(translateArguments(superCall), initializer); } } - private void addCallToSuperMethod(@NotNull JetDelegatorToSuperCall superCall, JsFunction initializer) { - List arguments = translateArguments(superCall); + private void addCallToSuperMethod(@NotNull List arguments, JsFunction initializer) { if (context().isEcma5()) { JsName ref = context().scope().declareName(Namer.CALLEE_NAME); initializer.setName(ref); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsDescriptorUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsDescriptorUtils.java index 7ecd7edbc15..f704cbc98cb 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsDescriptorUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsDescriptorUtils.java @@ -59,7 +59,7 @@ public final class JsDescriptorUtils { @Nullable public static ClassDescriptor findAncestorClass(@NotNull List superclassDescriptors) { for (ClassDescriptor descriptor : superclassDescriptors) { - if (descriptor.getKind() == ClassKind.CLASS) { + if (descriptor.getKind() == ClassKind.CLASS || descriptor.getKind() == ClassKind.ENUM_CLASS) { return descriptor; } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java index 04104b92d0d..06319675683 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java @@ -56,6 +56,11 @@ public final class TranslationUtils { } } + @NotNull + public static JsFunction simpleReturnFunction(@NotNull JsScope functionScope, @NotNull JsExpression returnExpression) { + return new JsFunction(functionScope, new JsBlock(new JsReturn(returnExpression))); + } + @NotNull private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsFunction function, @NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) { diff --git a/js/js.translator/testFiles/enum/cases/simple.kt b/js/js.translator/testFiles/enum/cases/simple.kt new file mode 100644 index 00000000000..17ae3a8891c --- /dev/null +++ b/js/js.translator/testFiles/enum/cases/simple.kt @@ -0,0 +1,43 @@ +package foo + +enum class B(open val bar: Int) { + val x = 1 + var y = 12; + a : B(0) { + override val bar = 3 + { + y = 0 + } + } + b : B(4){} + c : B(5) +} + +trait X { + val foo: Int + fun bar(): Int { + return foo; + } +} + +enum class Y(override val foo: Int) : X { + m:Y(3) + n:Y(6) +} + +fun box(): String { + if (B.a.x != 1) return "B.a.x != 1" + if (B.a.y != 0) return "B.a.y != 0" + if (B.a.bar != 3) return "B.a.bar != 3" + + if (B.b.bar != 4) return "B.b.bar != 4" + if (B.b.y != 12) return "B.b.y != 12" + + if (B.c.bar != 5) return "B.c.bar != 5" + if (B.c.y != 12) return "B.c.y != 12" + + if (Y.m.bar() != 3) return "Y.m.bar() != 3" + if (Y.n.bar() != 6) return "Y.n.bar() != 6" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/enum/cases/standardMethods.kt b/js/js.translator/testFiles/enum/cases/standardMethods.kt new file mode 100644 index 00000000000..b6ce378911c --- /dev/null +++ b/js/js.translator/testFiles/enum/cases/standardMethods.kt @@ -0,0 +1,47 @@ +package foo + +enum class EmptyEnum + +enum class Simple { + OK +} + +enum class A { + a : A() {} + b : A() + c +} + +enum class B { + c +} +fun box(): String { + if (Simple.OK.name() != "OK") return "Simple.OK.name() != OK" + val ok = Simple.OK + if (ok.ordinal() != 0) return "ok = Simple.Ok; ok.ordinal() != 0" + + val ok2 = Simple.valueOf("OK") + if(!ok2.equals(ok)) return "ok2 not equal ok" + if(!ok2.identityEquals(ok)) return "ok2 not identity equal ok" + + + if (EmptyEnum.values().size != 0) return "EmptyEnum.values().size != 0" + + if (A.values() != array(A.a, A.b, A.c)) return "Wrong A.values(): " + A.values().toString() + + if (A.c.toString() != "c") return "A.c.toString() != c" + if (A.valueOf("b") != A.b) return "A.valueOf('b') != A.b" + if (A.a == A.b) return "A.a == A.b" + + if (A.a.name() != "a") return "A.a.name() != a" + if (A.b.name() != "b") return "A.b.name() != b" + if (A.c.name() != "c") return "A.c.name() != c" + + if (A.a.ordinal() != 0) return "A.a.ordinal() != 0" + if (A.b.ordinal() != 1) return "A.b.ordinal() != 1" + if (A.c.ordinal() != 2) return "A.c.ordinal() != 2" + + if (A.c.equals(B.c)) return "A.c.equals(B.c)" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/kotlin_lib.js b/js/js.translator/testFiles/kotlin_lib.js index a50be03db2c..d7bb54036e3 100644 --- a/js/js.translator/testFiles/kotlin_lib.js +++ b/js/js.translator/testFiles/kotlin_lib.js @@ -45,7 +45,7 @@ String.prototype.contains = function (s) { return obj1 === obj2; }; - + Kotlin.toString = function (o) { if (o == null) { return "null"; @@ -130,6 +130,48 @@ String.prototype.contains = function (s) { Kotlin.Collection = Kotlin.$createClass(); + Kotlin.Enum = Kotlin.$createClass(null, { + initialize: function () { + this.name$ = undefined; + this.ordinal$ = undefined; + }, + name: function () { + return this.name$; + }, + ordinal: function () { + return this.ordinal$; + }, + toString: function () { + return this.name(); + } + }); + (function (){ + function valueOf(name) { + return this[name]; + } + function getValues() { + return this.values$; + } + + Kotlin.createEnumEntries = function(enumEntryList) { + var i = 0; + var values = []; + for (var entryName in enumEntryList) { + if (enumEntryList.hasOwnProperty(entryName)) { + var entryObject = enumEntryList[entryName]; + values[i] = entryObject; + entryObject.ordinal$ = i; + entryObject.name$ = entryName; + i++; + } + } + enumEntryList.values$ = values; + enumEntryList.valueOf = valueOf; + enumEntryList.values = getValues; + return enumEntryList; + }; + })(); + Kotlin.AbstractCollection = Kotlin.$createClass(Kotlin.Collection, { size: function () { return this.$size;