From 45e827aeb9ea0f369330e2a32896505e7e82e638 Mon Sep 17 00:00:00 2001 From: Erokhin Stanislav Date: Fri, 16 Aug 2013 22:13:16 +0400 Subject: [PATCH] JS backend: add support multi declaration --- .../test/semantics/MultiDeclarationTest.java | 56 +++++++++++ .../expression/ExpressionVisitor.java | 8 ++ .../MultiDeclarationTranslator.java | 92 +++++++++++++++++++ .../foreach/ArrayForTranslator.java | 4 +- .../expression/foreach/ForTranslator.java | 60 ++++++++---- .../foreach/RangeForTranslator.java | 2 +- .../foreach/RangeLiteralForTranslator.java | 2 +- .../k2js/translate/utils/PsiUtils.java | 6 +- .../cases/multiValForArray.kt | 21 +++++ .../multideclaration/cases/multiValForMap.kt | 40 ++++++++ .../cases/multiValForRange.kt | 41 +++++++++ .../multideclaration/cases/multiValInFor.kt | 25 +++++ .../cases/multiValInIntFor.kt | 21 +++++ .../cases/multiValInIntRangeFor.kt | 22 +++++ .../multideclaration/cases/multiValOrVar.kt | 41 +++++++++ 15 files changed, 414 insertions(+), 27 deletions(-) create mode 100644 js/js.tests/test/org/jetbrains/k2js/test/semantics/MultiDeclarationTest.java create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/expression/MultiDeclarationTranslator.java create mode 100644 js/js.translator/testFiles/multideclaration/cases/multiValForArray.kt create mode 100644 js/js.translator/testFiles/multideclaration/cases/multiValForMap.kt create mode 100644 js/js.translator/testFiles/multideclaration/cases/multiValForRange.kt create mode 100644 js/js.translator/testFiles/multideclaration/cases/multiValInFor.kt create mode 100644 js/js.translator/testFiles/multideclaration/cases/multiValInIntFor.kt create mode 100644 js/js.translator/testFiles/multideclaration/cases/multiValInIntRangeFor.kt create mode 100644 js/js.translator/testFiles/multideclaration/cases/multiValOrVar.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/MultiDeclarationTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/MultiDeclarationTest.java new file mode 100644 index 00000000000..77edd506e15 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/MultiDeclarationTest.java @@ -0,0 +1,56 @@ +/* + * 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 MultiDeclarationTest extends SingleFileTranslationTest { + public MultiDeclarationTest() { + super("multideclaration/"); + } + + // TODO: Add support Map.Entry + public void TestMultiValForMap() throws Exception { + checkFooBoxIsOk(); + } + + public void testMultiValOrVar() throws Exception { + checkFooBoxIsOk(); + } + + public void testMultiValInFor() throws Exception { + checkFooBoxIsOk(); + } + + public void testMultiValInIntFor() throws Exception { + checkFooBoxIsOk(); + } + + public void testMultiValInIntRangeFor() throws Exception { + checkFooBoxIsOk(); + } + + public void testMultiValForArray() throws Exception { + checkFooBoxIsOk(); + } + + // TODO: Add support ranges for user types + public void TestMultiValForRange() throws Exception { + checkFooBoxIsOk(); + } + +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java index 14b092ae0d2..021a1f384df 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java @@ -115,6 +115,14 @@ public final class ExpressionVisitor extends TranslatorVisitor { return source(jsBlock, jetBlock); } + @Override + public JsNode visitMultiDeclaration(@NotNull JetMultiDeclaration multiDeclaration, @NotNull TranslationContext context) { + JetExpression jetInitializer = multiDeclaration.getInitializer(); + assert jetInitializer != null : "Initializer for multi declaration must be not null"; + JsExpression initializer = Translation.translateAsExpression(jetInitializer, context); + return MultiDeclarationTranslator.translate(multiDeclaration, context.scope().declareTemporary(), initializer, context); + } + @Override @NotNull public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression, diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/MultiDeclarationTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/MultiDeclarationTranslator.java new file mode 100644 index 00000000000..5cb4f964169 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/MultiDeclarationTranslator.java @@ -0,0 +1,92 @@ +/* + * 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.translate.expression; + +import com.google.dart.compiler.backend.js.ast.JsExpression; +import com.google.dart.compiler.backend.js.ast.JsName; +import com.google.dart.compiler.backend.js.ast.JsNameRef; +import com.google.dart.compiler.backend.js.ast.JsVars; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; +import org.jetbrains.jet.lang.descriptors.VariableDescriptor; +import org.jetbrains.jet.lang.psi.JetMultiDeclaration; +import org.jetbrains.jet.lang.psi.JetMultiDeclarationEntry; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BindingContextUtils; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.general.AbstractTranslator; +import org.jetbrains.k2js.translate.reference.CallBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class MultiDeclarationTranslator extends AbstractTranslator { + + // if multiObjectName was init, initializer must be null + @NotNull + public static JsVars translate( + @NotNull JetMultiDeclaration multiDeclaration, + @NotNull JsName multiObjectName, + @Nullable JsExpression initializer, + @NotNull TranslationContext context + ) { + return new MultiDeclarationTranslator(multiDeclaration, multiObjectName, initializer, context).translate(); + } + + @NotNull + private final JetMultiDeclaration multiDeclaration; + @NotNull + private final JsName multiObjectName; + @Nullable + private final JsExpression initializer; + + private MultiDeclarationTranslator( + @NotNull JetMultiDeclaration multiDeclaration, + @NotNull JsName multiObjectName, + @Nullable JsExpression initializer, + @NotNull TranslationContext context + ) { + super(context); + this.multiDeclaration = multiDeclaration; + this.multiObjectName = multiObjectName; + this.initializer = initializer; + } + + private JsVars translate() { + List jsVars = new ArrayList(); + if (initializer != null) { + jsVars.add(new JsVars.JsVar(multiObjectName, initializer)); + } + + JsNameRef multiObjNameRef = multiObjectName.makeRef(); + for (JetMultiDeclarationEntry entry : multiDeclaration.getEntries()) { + ResolvedCall entryInitCall = context().bindingContext().get(BindingContext.COMPONENT_RESOLVED_CALL, entry); + assert entryInitCall != null : "Entry init call must be not null"; + JsExpression entryInitializer = CallBuilder.build(context()) + .receiver(multiObjNameRef) + .resolvedCall(entryInitCall) + .translate(); + + VariableDescriptor descriptor = BindingContextUtils.getNotNull( context().bindingContext(), BindingContext.VARIABLE, entry); + JsName name = context().getNameForDescriptor(descriptor); + jsVars.add(new JsVars.JsVar(name, entryInitializer)); + } + return new JsVars(jsVars, true); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/ArrayForTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/ArrayForTranslator.java index d0c21a5072b..857fb2557e8 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/ArrayForTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/ArrayForTranslator.java @@ -86,9 +86,7 @@ public final class ArrayForTranslator extends ForTranslator { @NotNull private JsStatement getBody() { JsArrayAccess arrayAccess = new JsArrayAccess(loopRange.reference(), index.reference()); - JsStatement currentVar = newVar(parameterName, arrayAccess); - JsStatement realBody = translateOriginalBodyExpression(); - return new JsBlock(currentVar, realBody); + return translateBody(arrayAccess); } @NotNull diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/ForTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/ForTranslator.java index 73b46c02e1f..9a96b98db16 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/ForTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/ForTranslator.java @@ -21,8 +21,12 @@ import com.google.dart.compiler.backend.js.ast.JsExpression; import com.google.dart.compiler.backend.js.ast.JsName; import com.google.dart.compiler.backend.js.ast.JsStatement; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.psi.JetForExpression; +import org.jetbrains.jet.lang.psi.JetMultiDeclaration; +import org.jetbrains.jet.lang.psi.JetParameter; import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.expression.MultiDeclarationTranslator; import org.jetbrains.k2js.translate.general.AbstractTranslator; import org.jetbrains.k2js.translate.general.Translation; @@ -51,34 +55,54 @@ public abstract class ForTranslator extends AbstractTranslator { protected final JetForExpression expression; @NotNull protected final JsName parameterName; + @Nullable + protected final JetMultiDeclaration multiParameter; protected ForTranslator(@NotNull JetForExpression forExpression, @NotNull TranslationContext context) { super(context); this.expression = forExpression; + this.multiParameter = forExpression.getMultiParameter(); this.parameterName = declareParameter(); } @NotNull private JsName declareParameter() { - return context().getNameForElement(getLoopParameter(expression)); - } - - @NotNull - protected JsStatement translateOriginalBodyExpression() { - return Translation.translateAsStatement(getLoopBody(expression), context()); - } - - @NotNull - protected JsStatement translateBody(JsExpression itemValue) { - JsStatement currentVar = newVar(parameterName, itemValue); - JsStatement realBody = translateOriginalBodyExpression(); - if (realBody instanceof JsBlock) { - JsBlock block = (JsBlock) realBody; - block.getStatements().add(0, currentVar); - return block; + JetParameter loopParameter = getLoopParameter(expression); + if (loopParameter != null) { + return context().getNameForElement(loopParameter); } - else { - return new JsBlock(currentVar, realBody); + assert parameterIsMultiDeclaration() : "If loopParameter is null, multi parameter must be not null"; + return context().scope().declareTemporary(); + } + + private boolean parameterIsMultiDeclaration() { + return multiParameter != null; + } + + @NotNull + private JsStatement makeCurrentVarInit(@Nullable JsExpression itemValue) { + if (multiParameter == null) { + return newVar(parameterName, itemValue); + } else { + return MultiDeclarationTranslator.translate(multiParameter, parameterName, itemValue, context()); + } + } + + @NotNull + protected JsStatement translateBody(@Nullable JsExpression itemValue) { + JsStatement realBody = Translation.translateAsStatement(getLoopBody(expression), context()); + if (itemValue == null && !parameterIsMultiDeclaration()) { + return realBody; + } else { + JsStatement currentVarInit = makeCurrentVarInit(itemValue); + if (realBody instanceof JsBlock) { + JsBlock block = (JsBlock) realBody; + block.getStatements().add(0, currentVarInit); + return block; + } + else { + return new JsBlock(currentVarInit, realBody); + } } } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/RangeForTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/RangeForTranslator.java index 34e01b75e84..da41a113db5 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/RangeForTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/RangeForTranslator.java @@ -79,7 +79,7 @@ public final class RangeForTranslator extends ForTranslator { @NotNull private JsFor generateForExpression() { JsFor result = new JsFor(initExpression(), getCondition(), getIncrExpression()); - result.setBody(translateOriginalBodyExpression()); + result.setBody(translateBody(null)); return result; } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/RangeLiteralForTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/RangeLiteralForTranslator.java index 6d88414c705..a9388031658 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/RangeLiteralForTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/foreach/RangeLiteralForTranslator.java @@ -82,7 +82,7 @@ public final class RangeLiteralForTranslator extends ForTranslator { blockStatements.add(generateForExpression(initExpression(), getCondition(), getIncrExpression(), - translateOriginalBodyExpression())); + translateBody(null))); return new JsBlock(blockStatements); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/PsiUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/PsiUtils.java index d3307c41da3..dbd0f441ee9 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/PsiUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/PsiUtils.java @@ -108,11 +108,9 @@ public final class PsiUtils { return body; } - @NotNull + @Nullable public static JetParameter getLoopParameter(@NotNull JetForExpression expression) { - JetParameter loopParameter = expression.getLoopParameter(); - assert loopParameter != null; - return loopParameter; + return expression.getLoopParameter(); } @NotNull diff --git a/js/js.translator/testFiles/multideclaration/cases/multiValForArray.kt b/js/js.translator/testFiles/multideclaration/cases/multiValForArray.kt new file mode 100644 index 00000000000..5b85d3288fe --- /dev/null +++ b/js/js.translator/testFiles/multideclaration/cases/multiValForArray.kt @@ -0,0 +1,21 @@ +package foo +class A { + fun component1() : Int = 1 +} +fun A.component2() : String = "n" + +fun box(): String { + val list = Array(1, {A()}) + + var i = 0; + var s = "" + for ((a,b) in list) { + i = a; + s = b; + } + + if (i != 1) return "i != 1, it: " + i + if (s != "n") return "s != 'n', it: " + s + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/multideclaration/cases/multiValForMap.kt b/js/js.translator/testFiles/multideclaration/cases/multiValForMap.kt new file mode 100644 index 00000000000..fc5d62a0301 --- /dev/null +++ b/js/js.translator/testFiles/multideclaration/cases/multiValForMap.kt @@ -0,0 +1,40 @@ +package foo + +import java.util.HashMap + + +public inline fun Map.iterator(): Iterator> { + val entrySet = this.entrySet() + return entrySet.iterator() +} + +/** Returns the key of the entry */ +fun Map.Entry.component1() : K { + return getKey() +} + +/** Returns the value of the entry */ +fun Map.Entry.component2() : V { + return getValue() +} + +fun box(): String { + val map = HashMap() + map.put(1, "s1") + map.put(2, "s2") + + var s1 = "" + var s2 = "" + for ((k,v) in map) { + when (k) { + 2 -> s2 = v + 1 -> s1 = v + else -> {} + } + } + + if (s1 != "s1") return "s1 != 's1', it: ${s1}" + if (s2 != "s2") return "s2 != 's2', it: ${s2}" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/multideclaration/cases/multiValForRange.kt b/js/js.translator/testFiles/multideclaration/cases/multiValForRange.kt new file mode 100644 index 00000000000..52e89b36be9 --- /dev/null +++ b/js/js.translator/testFiles/multideclaration/cases/multiValForRange.kt @@ -0,0 +1,41 @@ +package foo +class C(val i: Int): Comparable, A() { + public override fun compareTo(other: C): Int { + return if (other is C) other.i - i else 0 + } + +} + +fun ComparableRange.iterator() : Iterator { + var curI: Int = start.i - 1 + + return object :Iterator { + public override fun next(): C { + curI++ + return C(curI) + } + public override fun hasNext(): Boolean { + return curI < end.i + } + + } +} + +open class A { + fun component1() : Int = 1 +} +fun A.component2() : String = "n" + +fun box(): String { + var i = 0; + var s = "" + for ((a,b) in C(0)..C(2)) { + i = a; + s = b; + } + + if (i != 1) return "i != 1, it: " + i + if (s != "n") return "s != 'n', it: " + s + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/multideclaration/cases/multiValInFor.kt b/js/js.translator/testFiles/multideclaration/cases/multiValInFor.kt new file mode 100644 index 00000000000..7e1d2cf10ad --- /dev/null +++ b/js/js.translator/testFiles/multideclaration/cases/multiValInFor.kt @@ -0,0 +1,25 @@ +package foo + +import java.util.ArrayList + +class A { + fun component1() : Int = 1 +} +fun A.component2() : String = "n" + +fun box(): String { + val list = ArrayList() + list.add(A()) + + var i = 0; + var s = "" + for ((a,b) in list) { + i = a; + s = b; + } + + if (i != 1) return "i != 1, it: " + i + if (s != "n") return "s != 'n', it: " + s + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/multideclaration/cases/multiValInIntFor.kt b/js/js.translator/testFiles/multideclaration/cases/multiValInIntFor.kt new file mode 100644 index 00000000000..5d04d336703 --- /dev/null +++ b/js/js.translator/testFiles/multideclaration/cases/multiValInIntFor.kt @@ -0,0 +1,21 @@ +package foo + +fun Int.component1() : Int { + return this + 10; +} + +fun Int.component2() : String = "b" + +fun box(): String { + var i = 0; + var s = ""; + for ((a, b) in 1..4) { + i = a + s = b + } + + if (i != 14) return "i != 14, it: $i" + if (s != "b") return "s != 'b', it: $s" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/multideclaration/cases/multiValInIntRangeFor.kt b/js/js.translator/testFiles/multideclaration/cases/multiValInIntRangeFor.kt new file mode 100644 index 00000000000..5ffb6e7718a --- /dev/null +++ b/js/js.translator/testFiles/multideclaration/cases/multiValInIntRangeFor.kt @@ -0,0 +1,22 @@ +package foo + +fun Int.component1() : Int { + return this + 10; +} + +fun Int.component2() : String = "b" + +fun box(): String { + var i = 0; + var s = ""; + val range = 1..4 + for ((a, b) in range) { + i = a + s = b + } + + if (i != 14) return "i != 14, it: $i" + if (s != "b") return "s != 'b', it: $s" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/multideclaration/cases/multiValOrVar.kt b/js/js.translator/testFiles/multideclaration/cases/multiValOrVar.kt new file mode 100644 index 00000000000..ff0e77ef75d --- /dev/null +++ b/js/js.translator/testFiles/multideclaration/cases/multiValOrVar.kt @@ -0,0 +1,41 @@ +package foo + +class A { + fun component1(): Int = 1 +} +fun A.component2(): String = "n" + +class B { + fun component1(): Int = 1 + fun component2(): Int = 2 + fun component3(): Int = 3 +} + +class C { + fun component1(): Int = 42 +} + +fun box(): String { + val (a, b) = A() + if (a != 1) return "a != 1, it: $a" + if (b != "n") return "b != 'n', it: $b" + + var (x, y) = A() + if (x != 1) return "x != 1, it: $x" + if (y != "n") return "y != 'n', it: $y" + + x = 3 + if (x != 3) return "x != 3, it: $x" + y = "m" + if (y != "m") return "y != 'm', it: $y" + + var (b1, b2, b3) = B() + if (b1 != 1) return "b1 != 1, it: $b1" + if (b2 != 2) return "b2 != 2, it: $b2" + if (b3 != 3) return "b3 != 3, it: $b3" + + val (c) = C() + if (c != 42) return "c != 42, it: $c" + + return "OK" +} \ No newline at end of file