diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsVars.java b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsVars.java index 3e9de8bf4ec..b13ed1acede 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsVars.java +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsVars.java @@ -8,10 +8,7 @@ import com.google.dart.compiler.common.Symbol; import com.intellij.util.SmartList; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import java.util.*; /** * A JavaScript var statement. @@ -97,6 +94,14 @@ public class JsVars extends SourceInfoAwareJsNode implements JsStatement, Iterab vars.add(var); } + public void addAll(Collection vars) { + this.vars.addAll(vars); + } + + public void addAll(JsVars otherVars) { + this.vars.addAll(otherVars.vars); + } + public void addIfHasInitializer(JsVar var) { if (var.getInitExpression() != null) { add(var); diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/DangerousTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/DangerousTest.java deleted file mode 100644 index cf5caceb1b8..00000000000 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/DangerousTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 final class DangerousTest extends SingleFileTranslationTest { - - public DangerousTest() { - super("dangerous/"); - } - - public void testIfAsFunArgument() throws Exception { - fooBoxTest(); - } - - public void testIfAsPlusArgument() throws Exception { - fooBoxTest(); - } - - public void testWhenAsMinusArgument() throws Exception { - fooBoxTest(); - } - - public void testEvaluationOrder() throws Exception { - fooBoxTest(); - } - - public void testDangerousInsideDangerous() throws Exception { - fooBoxTest(); - } - - public void test2dangerousInExpression() throws Exception { - fooBoxTest(); - } - - public void testDangerousInline() throws Exception { - fooBoxTest(); - } -} diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/DefaultArgumentsTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/DefaultArgumentsTest.java index c3f497cbc0b..7b85103e6be 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/DefaultArgumentsTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/DefaultArgumentsTest.java @@ -68,6 +68,7 @@ public class DefaultArgumentsTest extends SingleFileTranslationTest { checkFooBoxIsOk(); } - - + public void testComplexExpressionAsDefaultArgument() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/EvaluationOrderTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/EvaluationOrderTest.java new file mode 100644 index 00000000000..f7dd8e79e35 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/EvaluationOrderTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2014 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; + +public class EvaluationOrderTest extends AbstractExpressionTest { + public EvaluationOrderTest() { + super("evaluationOrder/"); + } + + public void test2dangerousInExpression() throws Exception { + fooBoxTest(); + } + + public void testAndAndWithSideEffect() throws Exception { + checkFooBoxIsOk(); + } + + public void testOrOrWithSideEffect() throws Exception { + checkFooBoxIsOk(); + } + + public void testAssignToDotQualifiedWithSideEffect() throws Exception { + checkFooBoxIsOk(); + } + + public void testAssignToArrayElementWithSideEffect() throws Exception { + checkFooBoxIsOk(); + } + + public void testCallArgs() throws Exception { + checkFooBoxIsOk(); + } + + public void testCallVarargs() throws Exception { + checkFooBoxIsOk(); + } + + public void testCompareToIntrinsicWithSideEffect() throws Exception { + checkFooBoxIsOk(); + } + + public void testDangerousInsideDangerous() throws Exception { + fooBoxTest(); + } + + public void testDangerousInline() throws Exception { + fooBoxTest(); + } + + public void testElvisComplex() throws Exception { + checkFooBoxIsOk(); + } + + public void testEvaluationOrder1() throws Exception { + fooBoxTest(); + } + + public void testEqualsIntrinsicWithSideEffect() throws Exception { + checkFooBoxIsOk(); + } + + public void testEvaluationOrder2() throws Exception { + checkFooBoxIsOk(); + } + + public void testIfAsFunArgument() throws Exception { + fooBoxTest(); + } + + public void testIfAsPlusArgument() throws Exception { + fooBoxTest(); + } + + public void testIfWithComplex() throws Exception { + checkFooBoxIsOk(); + } + + public void testIntrinsicComplex() throws Exception { + checkFooBoxIsOk(); + } + + public void testWhenAsMinusArgument() throws Exception { + fooBoxTest(); + } + + public void testWhenWithComplexConditions() throws Exception { + checkFooBoxIsOk(); + } +} diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/ForeachTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ForeachTest.java index fb11f9e2d9a..55a2514f2b5 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/ForeachTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/ForeachTest.java @@ -33,4 +33,8 @@ public final class ForeachTest extends AbstractExpressionTest { public void testLabeledFor() throws Exception { fooBoxTest(); } + + public void testForWithComplexOneStatement() throws Exception { + checkFooBoxIsOk(); + } } \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/IfExpressionTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/IfExpressionTest.java new file mode 100644 index 00000000000..4e1eab8cfa8 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/IfExpressionTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2014 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.mozilla.javascript.JavaScriptException; + +public class IfExpressionTest extends AbstractExpressionTest { + + public IfExpressionTest() { + super("if/"); + } + + public void testIfElseAsExpressionWithThrow() throws Exception { + try { + fooBoxTest(); + fail(); + } + catch (JavaScriptException e) { + } + } + + public void testIfInsideLambda() throws Exception { + checkFooBoxIsOk(); + } + + public void testNestedIf() throws Exception { + checkFooBoxIsOk(); + } +} diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/MiscTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/MiscTest.java index e0e7743e915..7fa92289aa6 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/MiscTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/MiscTest.java @@ -45,15 +45,6 @@ public final class MiscTest extends AbstractExpressionTest { runFunctionOutputTest("classWithoutPackage.kt", Namer.getRootPackageName(), TEST_FUNCTION, true); } - public void testIfElseAsExpressionWithThrow() throws Exception { - try { - fooBoxTest(); - fail(); - } - catch (JavaScriptException e) { - } - } - public void testKt1052_2() throws Exception { checkFooBoxIsTrue("KT-1052-2.kt"); } diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/TryCatchTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/TryCatchTest.java new file mode 100644 index 00000000000..aaca6fc7b19 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/TryCatchTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2014 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; + +public class TryCatchTest extends AbstractExpressionTest { + + public TryCatchTest() { + super("try/"); + } + + public void testTryCatchExpr() throws Exception { + checkFooBoxIsOk(); + } +} diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/WhileTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/WhileTest.java new file mode 100644 index 00000000000..bf05522ec9e --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/WhileTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2014 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; + +public class WhileTest extends AbstractExpressionTest { + + public WhileTest() { + super("while/"); + } + + public void testWhileWithComplexOneStatement() throws Exception { + checkFooBoxIsOk(); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfo.kt b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfo.kt index 725e4932bbc..9ddc1cf0346 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfo.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfo.kt @@ -38,6 +38,7 @@ import org.jetbrains.k2js.translate.reference.CallArgumentTranslator import org.jetbrains.k2js.translate.utils.TranslationUtils import com.google.dart.compiler.backend.js.ast.JsLiteral import com.google.dart.compiler.backend.js.ast.JsConditional +import com.google.dart.compiler.backend.js.ast.JsBlock trait CallInfo { @@ -79,8 +80,25 @@ fun TranslationContext.getCallInfo(resolvedCall: ResolvedCall, explicitReceivers: ExplicitReceivers): FunctionCallInfo { - val callInfo = createCallInfo(resolvedCall, explicitReceivers) - val argumentsInfo = CallArgumentTranslator.translate(resolvedCall, explicitReceivers.receiverOrThisObject, this) + val argsBlock = JsBlock() + val argumentsInfo = CallArgumentTranslator.translate(resolvedCall, explicitReceivers.receiverOrThisObject, this, argsBlock) + val explicitReceiversCorrected = + if (!argsBlock.isEmpty() && explicitReceivers.receiverOrThisObject != null) { + val receiverOrThisRefVar = this.declareTemporary(explicitReceivers.receiverOrThisObject) + this.addStatementToCurrentBlock(receiverOrThisRefVar.assignmentExpression().makeStmt()!!) + + var receiverRef = explicitReceivers.receiverObject + if (receiverRef != null) { + receiverRef = this.declareTemporary(null).reference() + this.addStatementToCurrentBlock(JsAstUtils.assignment(receiverRef!!, explicitReceivers.receiverObject!!).makeStmt()!!) + } + ExplicitReceivers(receiverOrThisRefVar.reference(), receiverRef) + } + else { + explicitReceivers + } + this.addStatementsToCurrentBlockFrom(argsBlock) + val callInfo = createCallInfo(resolvedCall, explicitReceiversCorrected) return FunctionCallInfo(callInfo, argumentsInfo) } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/DynamicContext.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/DynamicContext.java index 9cca63dfdc1..800bd869a7f 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/DynamicContext.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/DynamicContext.java @@ -66,6 +66,19 @@ public final class DynamicContext { return TemporaryVariable.create(temporaryName, initExpression); } + void moveVarsFrom(@NotNull DynamicContext dynamicContext) { + if (dynamicContext.vars != null) { + if (vars == null) { + vars = dynamicContext.vars; + currentBlock.getStatements().add(vars); + } else { + vars.addAll(dynamicContext.vars); + } + dynamicContext.currentBlock.getStatements().remove(dynamicContext.vars); + dynamicContext.vars = null; + } + } + //TODO: replace return type to make it more readable @NotNull public Pair createTemporary(@Nullable JsExpression initExpression) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java index 24ee7094042..5dee030e583 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java @@ -81,6 +81,7 @@ public class TranslationContext { return usageTracker; } + @NotNull public DynamicContext dynamicContext() { return dynamicContext; } @@ -112,6 +113,11 @@ public class TranslationContext { return new TranslationContext(this, staticContext, dynamicContext.innerBlock(block), aliasingContext, usageTracker, null); } + @NotNull + public TranslationContext innerBlock() { + return innerBlock(new JsBlock()); + } + @NotNull public TranslationContext newDeclaration(@NotNull DeclarationDescriptor descriptor, @Nullable DefinitionPlace place) { DynamicContext dynamicContext = DynamicContext.newContext(getScopeForDescriptor(descriptor), getBlockForDescriptor(descriptor)); @@ -259,6 +265,27 @@ public class TranslationContext { dynamicContext.jsBlock().getStatements().add(statement); } + public void addStatementsToCurrentBlockFrom(@NotNull TranslationContext context) { + addStatementsToCurrentBlockFrom(context.dynamicContext().jsBlock()); + } + + public void addStatementsToCurrentBlockFrom(@NotNull JsBlock block) { + dynamicContext.jsBlock().getStatements().addAll(block.getStatements()); + } + + public boolean currentBlockIsEmpty() { + return dynamicContext.jsBlock().isEmpty(); + } + + public void moveVarsFrom(@NotNull TranslationContext context) { + dynamicContext.moveVarsFrom(context.dynamicContext()); + } + + @NotNull + public JsBlock getCurrentBlock() { + return dynamicContext.jsBlock(); + } + @Nullable public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) { JsNameRef nameRef = captureIfNeedAndGetCapturedName(descriptor); 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 13015b2a94d..f1b40795ab3 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 @@ -29,7 +29,6 @@ import org.jetbrains.jet.lang.resolve.constants.NullValue; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.TypeUtils; import org.jetbrains.jet.lexer.JetTokens; -import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.declaration.ClassTranslator; import org.jetbrains.k2js.translate.expression.foreach.ForTranslator; @@ -39,7 +38,6 @@ import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator; import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator; import org.jetbrains.k2js.translate.reference.*; import org.jetbrains.k2js.translate.utils.*; -import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator; import java.util.List; @@ -52,7 +50,6 @@ import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message; import static org.jetbrains.k2js.translate.utils.JsAstUtils.*; import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration; import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty; -import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression; public final class ExpressionVisitor extends TranslatorVisitor { @Override @@ -179,29 +176,27 @@ public final class ExpressionVisitor extends TranslatorVisitor { @NotNull public JsNode visitIfExpression(@NotNull JetIfExpression expression, @NotNull TranslationContext context) { JsExpression testExpression = translateConditionExpression(expression.getCondition(), context); + + boolean isKotlinExpression = BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext()); + JetExpression thenExpression = expression.getThen(); + assert thenExpression != null : "then expression should not be null: " + expression.getText(); JetExpression elseExpression = expression.getElse(); - assert thenExpression != null; - JsNode thenNode = thenExpression.accept(this, context); - JsNode elseNode = elseExpression == null ? null : elseExpression.accept(this, context); - boolean isKotlinStatement = BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext()); - boolean canBeJsExpression = thenNode instanceof JsExpression && elseNode instanceof JsExpression; - if (!isKotlinStatement && canBeJsExpression) { - return new JsConditional(testExpression, convertToExpression(thenNode), convertToExpression(elseNode)).source(expression); - } - else { - JsIf ifStatement = new JsIf(testExpression, convertToStatement(thenNode), elseNode == null ? null : convertToStatement(elseNode)); - ifStatement.source(expression); - if (isKotlinStatement) { - return ifStatement; + JsStatement thenStatement = Translation.translateAsStatement(thenExpression, context); + JsStatement elseStatement = (elseExpression != null) ? Translation.translateAsStatement(elseExpression, context) : null; + + if (isKotlinExpression) { + JsExpression jsThenExpression = JsAstUtils.extractExpressionFromStatement(thenStatement); + JsExpression jsElseExpression = JsAstUtils.extractExpressionFromStatement(elseStatement); + boolean canBeJsExpression = jsThenExpression != null && jsElseExpression != null; + if (canBeJsExpression) { + return new JsConditional(testExpression, jsThenExpression, jsElseExpression).source(expression); } - - TemporaryVariable result = context.declareTemporary(null); - AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference()); - context.addStatementToCurrentBlock(mutateLastExpression(ifStatement, saveResultToTemporaryMutator)); - return result.reference(); } + JsIf ifStatement = new JsIf(testExpression, thenStatement, elseStatement); + ifStatement.source(expression); + return isKotlinExpression ? convertToExpression(ifStatement, context) : ifStatement; } @Override @@ -212,12 +207,14 @@ public final class ExpressionVisitor extends TranslatorVisitor { } @NotNull - private JsStatement translateNullableExpressionAsNotNullStatement(@Nullable JetExpression nullableExpression, - @NotNull TranslationContext context) { + private static JsStatement translateNullableExpressionAsNotNullStatement( + @Nullable JetExpression nullableExpression, + @NotNull TranslationContext context + ) { if (nullableExpression == null) { return context.program().getEmptyStatement(); } - return convertToStatement(nullableExpression.accept(this, context)); + return Translation.translateAsStatement(nullableExpression, context); } @NotNull @@ -225,7 +222,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { @NotNull TranslationContext context) { JsExpression jsCondition = translateNullableExpression(expression, context); assert (jsCondition != null) : "Condition should not be empty"; - return convertToExpression(jsCondition); + return convertToExpression(jsCondition, context); } @Nullable @@ -234,7 +231,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { if (expression == null) { return null; } - return convertToExpression(expression.accept(this, context)); + return convertToExpression(expression.accept(this, context), context); } @Override @@ -251,7 +248,9 @@ public final class ExpressionVisitor extends TranslatorVisitor { private JsNode createWhile(@NotNull JsWhile result, @NotNull JetWhileExpressionBase expression, @NotNull TranslationContext context) { result.setCondition(translateConditionExpression(expression.getCondition(), context)); - result.setBody(translateNullableExpressionAsNotNullStatement(expression.getBody(), context)); + TranslationContext bodyContext = context.innerBlock(); + JsStatement bodyStatement = translateNullableExpressionAsNotNullStatement(expression.getBody(), bodyContext); + result.setBody(bodyStatement); return result.source(expression); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/WhenTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/WhenTranslator.java index 798a1f9ec0e..54879280255 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/WhenTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/WhenTranslator.java @@ -17,21 +17,18 @@ package org.jetbrains.k2js.translate.expression; import com.google.dart.compiler.backend.js.ast.*; -import com.intellij.openapi.util.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage; +import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.AbstractTranslator; import org.jetbrains.k2js.translate.general.Translation; +import org.jetbrains.k2js.translate.utils.JsAstUtils; import org.jetbrains.k2js.translate.utils.TranslationUtils; -import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator; -import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator; -import java.util.List; - -import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement; +import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToExpression; import static org.jetbrains.k2js.translate.utils.JsAstUtils.negated; public final class WhenTranslator extends AbstractTranslator { @@ -40,9 +37,7 @@ public final class WhenTranslator extends AbstractTranslator { WhenTranslator translator = new WhenTranslator(expression, context); if (BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext())) { - JsBlock jsBlock = new JsBlock(); - translator.translateAsStatement(jsBlock.getStatements()); - return jsBlock; + return translator.translateAsStatement(); } return translator.translateAsExpression(); @@ -52,10 +47,7 @@ public final class WhenTranslator extends AbstractTranslator { private final JetWhenExpression whenExpression; @Nullable - private final Pair expressionToMatch; - - @Nullable - private Pair result; + private final JsExpression expressionToMatch; private WhenTranslator(@NotNull JetWhenExpression expression, @NotNull TranslationContext context) { super(context); @@ -64,7 +56,13 @@ public final class WhenTranslator extends AbstractTranslator { JetExpression subject = expression.getSubjectExpression(); if (subject != null) { - expressionToMatch = TranslationUtils.createTemporaryIfNeed(Translation.translateAsExpression(subject, context()), context); + JsExpression subjectExpression = Translation.translateAsExpression(subject, context); + if (TranslationUtils.isCacheNeeded(subjectExpression)) { + TemporaryVariable subjectVar = context.declareTemporary(null); + context.addStatementToCurrentBlock(JsAstUtils.assignment(subjectVar.reference(), subjectExpression).makeStmt()); + subjectExpression = subjectVar.reference(); + } + expressionToMatch = subjectExpression; } else { expressionToMatch = null; @@ -73,95 +71,96 @@ public final class WhenTranslator extends AbstractTranslator { @Nullable private JsNode translateAsExpression() { - result = context().dynamicContext().createTemporary(null); - translateAsStatement(context().dynamicContext().jsBlock().getStatements()); - return result.second; + return convertToExpression(translateAsStatement(), context()); } - private void translateAsStatement(List statements) { - addTempVarsStatement(statements); - - JsIf prevIf = null; + private JsStatement translateAsStatement() { + JsIf currentIf = null; + JsIf resultIf = null; for (JetWhenEntry entry : whenExpression.getEntries()) { - JsBlock entryBody = new JsBlock(); - JsStatement statement = withReturnValueCaptured(translateEntryExpression(entry, context().innerBlock(entryBody))); - if (!entryBody.isEmpty()) { - entryBody.getStatements().add(statement); - statement = entryBody; - } + JsBlock statementBlock = new JsBlock(); + JsStatement statement = translateEntryExpression(entry, context(), statementBlock); - JsStatement statementToAdd = entry.isElse() ? statement : new JsIf(translateConditions(entry), statement); - if (prevIf == null) { - statements.add(statementToAdd); + if (resultIf == null && entry.isElse()) { + context().addStatementsToCurrentBlockFrom(statementBlock); + return statement; + } + statement = JsAstUtils.mergeStatementInBlockIfNeeded(statement, statementBlock); + + if (resultIf == null) { + currentIf = new JsIf(translateConditions(entry, context()), statement); + resultIf = currentIf; } else { - prevIf.setElseStatement(statementToAdd); + if (entry.isElse()) { + currentIf.setElseStatement(statement); + return resultIf; + } + JsBlock conditionsBlock = new JsBlock(); + JsIf nextIf = new JsIf(translateConditions(entry, context().innerBlock(conditionsBlock)), statement); + JsStatement statementToAdd = JsAstUtils.mergeStatementInBlockIfNeeded(nextIf, conditionsBlock); + currentIf.setElseStatement(statementToAdd); + currentIf = nextIf; } - - if (entry.isElse()) break; - - assert statementToAdd instanceof JsIf : "Non-else entry expected: " + entry.getText(); - prevIf = (JsIf) statementToAdd; - } - } - - private void addTempVarsStatement(List statements) { - JsVars vars = new JsVars(); - if (expressionToMatch != null && expressionToMatch.first != null) { - vars.add(expressionToMatch.first); - } - if (result != null) { - vars.add(result.first); - } - - if (!vars.isEmpty()) { - statements.add(vars); } + return resultIf; } @NotNull - private JsStatement withReturnValueCaptured(@NotNull JsNode node) { - return result == null - ? convertToStatement(node) - : LastExpressionMutator.mutateLastExpression(node, new AssignToExpressionMutator(result.second)); - } - - @NotNull - private JsNode translateEntryExpression(@NotNull JetWhenEntry entry, @NotNull TranslationContext context) { + private static JsStatement translateEntryExpression( + @NotNull JetWhenEntry entry, + @NotNull TranslationContext context, + @NotNull JsBlock block) { JetExpression expressionToExecute = entry.getExpression(); assert expressionToExecute != null : "WhenEntry should have whenExpression to execute."; - return Translation.translateExpression(expressionToExecute, context); + return Translation.translateAsStatement(expressionToExecute, context, block); } @NotNull - private JsExpression translateConditions(@NotNull JetWhenEntry entry) { + private JsExpression translateConditions(@NotNull JetWhenEntry entry, @NotNull TranslationContext context) { JetWhenCondition[] conditions = entry.getConditions(); assert conditions.length > 0 : "When entry (not else) should have at least one condition"; if (conditions.length == 1) { - return translateCondition(conditions[0]); + return translateCondition(conditions[0], context); } - JsExpression result = translateCondition(conditions[0]); + JsExpression result = translateCondition(conditions[0], context); for (int i = 1; i < conditions.length; i++) { - result = new JsBinaryOperation(JsBinaryOperator.OR, translateCondition(conditions[i]), result); + result = translateOrCondition(result, conditions[i], context); } return result; } @NotNull - private JsExpression translateCondition(@NotNull JetWhenCondition condition) { + private JsExpression translateOrCondition(@NotNull JsExpression leftExpression, @NotNull JetWhenCondition condition, @NotNull TranslationContext context) { + TranslationContext rightContext = context.innerBlock(); + JsExpression rightExpression = translateCondition(condition, rightContext); + context.moveVarsFrom(rightContext); + if (rightContext.currentBlockIsEmpty()) { + return new JsBinaryOperation(JsBinaryOperator.OR, leftExpression, rightExpression); + } else { + assert rightExpression instanceof JsNameRef : "expected JsNameRef, but: " + rightExpression; + JsNameRef result = (JsNameRef) rightExpression; + JsIf ifStatement = new JsIf(leftExpression, JsAstUtils.assignment(result, JsLiteral.TRUE).makeStmt(), rightContext.getCurrentBlock()); + context.addStatementToCurrentBlock(ifStatement); + return result; + } + } + + @NotNull + private JsExpression translateCondition(@NotNull JetWhenCondition condition, @NotNull TranslationContext context) { if ((condition instanceof JetWhenConditionIsPattern) || (condition instanceof JetWhenConditionWithExpression)) { - return translatePatternCondition(condition); + return translatePatternCondition(condition, context); } throw new AssertionError("Unsupported when condition " + condition.getClass()); } @NotNull - private JsExpression translatePatternCondition(@NotNull JetWhenCondition condition) { - JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition); + private JsExpression translatePatternCondition(@NotNull JetWhenCondition condition, @NotNull TranslationContext context) { + JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition, context); if (isNegated(condition)) { return negated(patternMatchExpression); } @@ -169,44 +168,44 @@ public final class WhenTranslator extends AbstractTranslator { } @NotNull - private JsExpression translateWhenConditionToBooleanExpression(@NotNull JetWhenCondition condition) { + private JsExpression translateWhenConditionToBooleanExpression(@NotNull JetWhenCondition condition, @NotNull TranslationContext context) { if (condition instanceof JetWhenConditionIsPattern) { - return translateIsCondition((JetWhenConditionIsPattern) condition); + return translateIsCondition((JetWhenConditionIsPattern) condition, context); } else if (condition instanceof JetWhenConditionWithExpression) { - return translateExpressionCondition((JetWhenConditionWithExpression) condition); + return translateExpressionCondition((JetWhenConditionWithExpression) condition, context); } throw new AssertionError("Wrong type of JetWhenCondition"); } @NotNull - private JsExpression translateIsCondition(@NotNull JetWhenConditionIsPattern conditionIsPattern) { + private JsExpression translateIsCondition(@NotNull JetWhenConditionIsPattern conditionIsPattern, @NotNull TranslationContext context) { JsExpression expressionToMatch = getExpressionToMatch(); assert expressionToMatch != null : "An is-check is not allowed in when() without subject."; JetTypeReference typeReference = conditionIsPattern.getTypeRef(); assert typeReference != null : "An is-check must have a type reference."; - return Translation.patternTranslator(context()).translateIsCheck(expressionToMatch, typeReference); + return Translation.patternTranslator(context).translateIsCheck(expressionToMatch, typeReference); } @NotNull - private JsExpression translateExpressionCondition(@NotNull JetWhenConditionWithExpression condition) { + private JsExpression translateExpressionCondition(@NotNull JetWhenConditionWithExpression condition, @NotNull TranslationContext context) { JetExpression patternExpression = condition.getExpression(); assert patternExpression != null : "Expression pattern should have an expression."; JsExpression expressionToMatch = getExpressionToMatch(); if (expressionToMatch == null) { - return Translation.patternTranslator(context()).translateExpressionForExpressionPattern(patternExpression); + return Translation.patternTranslator(context).translateExpressionForExpressionPattern(patternExpression); } else { - return Translation.patternTranslator(context()).translateExpressionPattern(expressionToMatch, patternExpression); + return Translation.patternTranslator(context).translateExpressionPattern(expressionToMatch, patternExpression); } } @Nullable private JsExpression getExpressionToMatch() { - return expressionToMatch != null ? expressionToMatch.second : null; + return expressionToMatch; } private static boolean isNegated(@NotNull JetWhenCondition condition) { 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 9a96b98db16..f46c1c60c5d 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 @@ -29,6 +29,8 @@ 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; +import org.jetbrains.k2js.translate.utils.JsAstUtils; +import org.jetbrains.k2js.translate.utils.TranslationUtils; import static org.jetbrains.k2js.translate.utils.JsAstUtils.newVar; import static org.jetbrains.k2js.translate.utils.PsiUtils.getLoopBody; @@ -95,14 +97,9 @@ public abstract class ForTranslator extends AbstractTranslator { 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); - } + JsBlock block = JsAstUtils.convertToBlock(realBody); + block.getStatements().add(0, currentVarInit); + return block; } } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/k2js/translate/general/Translation.java index 7fd2e1431f5..f55e1014002 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/general/Translation.java @@ -25,7 +25,6 @@ import org.jetbrains.jet.lang.psi.JetDeclarationWithBody; import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetNamedFunction; -import org.jetbrains.jet.lang.reflect.ReflectionTypes; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.plugin.MainFunctionDetector; import org.jetbrains.k2js.config.Config; @@ -47,8 +46,6 @@ import org.jetbrains.k2js.translate.test.JSTestGenerator; import org.jetbrains.k2js.translate.test.JSTester; import org.jetbrains.k2js.translate.test.QUnitTester; import org.jetbrains.k2js.translate.utils.JsAstUtils; -import org.jetbrains.k2js.translate.utils.dangerous.DangerousData; -import org.jetbrains.k2js.translate.utils.dangerous.DangerousTranslator; import java.util.Collection; import java.util.Collections; @@ -56,7 +53,6 @@ import java.util.List; import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor; import static org.jetbrains.k2js.translate.utils.JsAstUtils.*; -import static org.jetbrains.k2js.translate.utils.dangerous.DangerousData.collect; /** * This class provides a interface which all translators use to interact with each other. @@ -80,15 +76,21 @@ public final class Translation { @NotNull public static JsNode translateExpression(@NotNull JetExpression expression, @NotNull TranslationContext context) { + return translateExpression(expression, context, context.dynamicContext().jsBlock()); + } + + @NotNull + public static JsNode translateExpression(@NotNull JetExpression expression, @NotNull TranslationContext context, @NotNull JsBlock block) { JsExpression aliasForExpression = context.aliasingContext().getAliasForExpression(expression); if (aliasForExpression != null) { return aliasForExpression; } - DangerousData data = collect(expression, context); - if (data.shouldBeTranslated()) { - return DangerousTranslator.translate(data, context); - } - return doTranslateExpression(expression, context); + + TranslationContext innerContext = context.innerBlock(); + JsNode result = doTranslateExpression(expression, innerContext); + context.moveVarsFrom(innerContext); + block.getStatements().addAll(innerContext.dynamicContext().jsBlock().getStatements()); + return result; } //NOTE: use with care @@ -98,15 +100,32 @@ public final class Translation { } @NotNull - public static JsExpression translateAsExpression(@NotNull JetExpression expression, - @NotNull TranslationContext context) { - return convertToExpression(translateExpression(expression, context)); + public static JsExpression translateAsExpression(@NotNull JetExpression expression, @NotNull TranslationContext context) { + return convertToExpression(translateExpression(expression, context), context); } @NotNull - public static JsStatement translateAsStatement(@NotNull JetExpression expression, - @NotNull TranslationContext context) { - return convertToStatement(translateExpression(expression, context)); + public static JsExpression translateAsExpression( + @NotNull JetExpression expression, + @NotNull TranslationContext context, + @NotNull JsBlock block + ) { + return convertToExpression(translateExpression(expression, context, block), context, block); + } + + @NotNull + public static JsStatement translateAsStatement(@NotNull JetExpression expression, @NotNull TranslationContext context) { + JsBlock block = new JsBlock(); + JsNode node = translateExpression(expression, context, block); + return JsAstUtils.mergeStatementInBlockIfNeeded(convertToStatement(node), block); + } + + @NotNull + public static JsStatement translateAsStatement( + @NotNull JetExpression expression, + @NotNull TranslationContext context, + @NotNull JsBlock block) { + return convertToStatement(translateExpression(expression, context, block)); } @NotNull diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/operation/AssignmentTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/operation/AssignmentTranslator.java index ae7a53caa95..6139320cfd1 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/operation/AssignmentTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/operation/AssignmentTranslator.java @@ -16,6 +16,7 @@ package org.jetbrains.k2js.translate.operation; +import com.google.dart.compiler.backend.js.ast.JsBlock; import com.google.dart.compiler.backend.js.ast.JsExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.psi.JetBinaryExpression; @@ -64,7 +65,11 @@ public abstract class AssignmentTranslator extends AbstractTranslator { this.isVariableReassignment = isVariableReassignment(context.bindingContext(), expression); JetExpression left = expression.getLeft(); assert left != null : "No left-hand side: " + expression.getText(); - this.accessTranslator = AccessTranslationUtils.getAccessTranslator(left, context()); - this.right = translateRightExpression(context(), expression); + + JsBlock rightBlock = new JsBlock(); + this.right = translateRightExpression(context, expression, rightBlock); + + this.accessTranslator = AccessTranslationUtils.getAccessTranslator(left, context(), !rightBlock.isEmpty()); + context.addStatementsToCurrentBlockFrom(rightBlock); } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/operation/BinaryOperationTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/operation/BinaryOperationTranslator.java index 5e1a33aa575..f8c3c998c2d 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/operation/BinaryOperationTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/operation/BinaryOperationTranslator.java @@ -33,15 +33,13 @@ import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.AbstractTranslator; import org.jetbrains.k2js.translate.general.Translation; import org.jetbrains.k2js.translate.intrinsic.operation.BinaryOperationIntrinsic; +import org.jetbrains.k2js.translate.utils.JsAstUtils; import org.jetbrains.k2js.translate.utils.TranslationUtils; -import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator; -import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator; import static org.jetbrains.jet.lang.resolve.calls.callUtil.CallUtilPackage.getFunctionResolvedCallWithAssert; import static org.jetbrains.k2js.translate.operation.AssignmentTranslator.isAssignmentOperator; import static org.jetbrains.k2js.translate.operation.CompareToTranslator.isCompareToCall; import static org.jetbrains.k2js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression; -import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement; import static org.jetbrains.k2js.translate.utils.JsAstUtils.not; import static org.jetbrains.k2js.translate.utils.PsiUtils.*; import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateLeftExpression; @@ -84,7 +82,7 @@ public final class BinaryOperationTranslator extends AbstractTranslator { return applyIntrinsic(intrinsic); } if (getOperationToken(expression).equals(JetTokens.ELVIS)) { - return translateElvis(expression); + return translateElvis(); } if (isAssignmentOperator(expression)) { return AssignmentTranslator.translate(expression, context()); @@ -101,24 +99,26 @@ public final class BinaryOperationTranslator extends AbstractTranslator { } @NotNull - private JsExpression translateElvis(JetBinaryExpression expression) { + private JsExpression translateElvis() { JsExpression leftExpression = translateLeftExpression(context(), expression); JetExpression rightJetExpression = expression.getRight(); assert rightJetExpression != null : "Binary expression should have a right expression"; - JsNode rightNode = Translation.translateExpression(rightJetExpression, context()); + JsBlock rightBlock = new JsBlock(); + JsNode rightNode = Translation.translateExpression(rightJetExpression, context(), rightBlock); - if (rightNode instanceof JsExpression) { + if (rightNode instanceof JsExpression && rightBlock.isEmpty()) { return TranslationUtils.notNullConditional(leftExpression, (JsExpression) rightNode, context()); } - TemporaryVariable result = context().declareTemporary(null); - AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference()); - context().addStatementToCurrentBlock(LastExpressionMutator.mutateLastExpression(leftExpression, saveResultToTemporaryMutator)); + TemporaryVariable result = context().declareTemporary(leftExpression); + context().addStatementToCurrentBlock(result.assignmentExpression().makeStmt()); JsExpression testExpression = TranslationUtils.isNullCheck(result.reference()); - JsStatement thenStatement = convertToStatement(rightNode); - JsIf ifStatement = new JsIf(testExpression, thenStatement); + JsExpression rightExpression = JsAstUtils.convertToExpression(rightNode, context(), rightBlock); + rightBlock.getStatements().add(JsAstUtils.assignment(result.reference(), rightExpression).makeStmt()); + + JsIf ifStatement = new JsIf(testExpression, rightBlock); context().addStatementToCurrentBlock(ifStatement); return result.reference(); @@ -131,10 +131,22 @@ public final class BinaryOperationTranslator extends AbstractTranslator { @NotNull private JsExpression applyIntrinsic(@NotNull BinaryOperationIntrinsic intrinsic) { - return intrinsic.apply(expression, - translateLeftExpression(context(), expression), - translateRightExpression(context(), expression), - context()); + JsExpression leftExpression = translateLeftExpression(context(), expression); + JsBlock rightBlock = new JsBlock(); + JsExpression rightExpression = translateRightExpression(context(), expression, rightBlock); + + if (rightBlock.isEmpty()) { + return intrinsic.apply(expression, leftExpression, rightExpression, context()); + } + + if (TranslationUtils.isCacheNeeded(leftExpression)) { + TemporaryVariable temporaryVariable = context().declareTemporary(null); + context().addStatementToCurrentBlock(JsAstUtils.assignment(temporaryVariable.reference(), leftExpression).makeStmt()); + leftExpression = temporaryVariable.reference(); + } + context().addStatementsToCurrentBlockFrom(rightBlock); + + return intrinsic.apply(expression, leftExpression, rightExpression, context()); } private boolean isNotOverloadable() { @@ -144,11 +156,32 @@ public final class BinaryOperationTranslator extends AbstractTranslator { @NotNull private JsExpression translateAsUnOverloadableBinaryOperation() { JetToken token = getOperationToken(expression); - JsBinaryOperator operator = OperatorTable.getBinaryOperator(token); assert OperatorConventions.NOT_OVERLOADABLE.contains(token); - JsExpression left = translateLeftExpression(context(), expression); - JsExpression right = translateRightExpression(context(), expression); - return new JsBinaryOperation(operator, left, right); + JsBinaryOperator operator = OperatorTable.getBinaryOperator(token); + JsExpression leftExpression = translateLeftExpression(context(), expression); + JsBlock rightBlock = new JsBlock(); + JsExpression rightExpression = translateRightExpression(context(), expression, rightBlock); + + if (rightBlock.isEmpty()) { + return new JsBinaryOperation(operator, leftExpression, rightExpression); + } + + assert rightExpression instanceof JsNameRef : "expected JsNameRef, but " + expression.getText(); + JsNameRef result = (JsNameRef) rightExpression; // Reuse tmp variable + + JsIf ifStatement; + if (token.equals(JetTokens.ANDAND)) { + ifStatement = new JsIf(leftExpression, rightBlock, JsAstUtils.assignment(result, JsLiteral.FALSE).makeStmt()); + } + else if (token.equals(JetTokens.OROR)) { + ifStatement = new JsIf(leftExpression, JsAstUtils.assignment(result, JsLiteral.TRUE).makeStmt(), rightBlock); + } + else { + throw new UnsupportedOperationException("Unsupported binary operation: " + expression.getText()); + } + + context().addStatementToCurrentBlock(ifStatement); + return result; } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/AccessTranslationUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/AccessTranslationUtils.java index c68ab70c8d7..b9e2b720df7 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/AccessTranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/AccessTranslationUtils.java @@ -19,7 +19,14 @@ package org.jetbrains.k2js.translate.reference; import com.google.dart.compiler.backend.js.ast.JsExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.general.Translation; +import org.jetbrains.k2js.translate.utils.JsAstUtils; +import org.jetbrains.k2js.translate.utils.TranslationUtils; + +import java.util.LinkedHashMap; +import java.util.Map; public final class AccessTranslationUtils { private AccessTranslationUtils() { @@ -28,16 +35,48 @@ public final class AccessTranslationUtils { @NotNull public static AccessTranslator getAccessTranslator(@NotNull JetExpression referenceExpression, @NotNull TranslationContext context) { + return getAccessTranslator(referenceExpression, context, false); + } + + @NotNull + public static AccessTranslator getAccessTranslator(@NotNull JetExpression referenceExpression, + @NotNull TranslationContext context, boolean forceOrderOfEvaluation) { assert ((referenceExpression instanceof JetReferenceExpression) || (referenceExpression instanceof JetQualifiedExpression)); if (referenceExpression instanceof JetQualifiedExpression) { - return QualifiedExpressionTranslator.getAccessTranslator((JetQualifiedExpression) referenceExpression, context); + return QualifiedExpressionTranslator.getAccessTranslator((JetQualifiedExpression) referenceExpression, context, forceOrderOfEvaluation); } if (referenceExpression instanceof JetSimpleNameExpression) { return ReferenceTranslator.getAccessTranslator((JetSimpleNameExpression) referenceExpression, context); } assert referenceExpression instanceof JetArrayAccessExpression; - return ArrayAccessTranslator.newInstance((JetArrayAccessExpression) referenceExpression, context); + return getArrayAccessTranslator((JetArrayAccessExpression) referenceExpression, context, forceOrderOfEvaluation); + } + + @NotNull + private static AccessTranslator getArrayAccessTranslator( + @NotNull JetArrayAccessExpression expression, + @NotNull TranslationContext context, + boolean forceOrderOfEvaluation + ) { + TranslationContext accessArrayContext; + if (forceOrderOfEvaluation) { + Map indexesMap = new LinkedHashMap(); + for(JetExpression indexExpression : expression.getIndexExpressions()) { + JsExpression jsIndexExpression = Translation.translateAsExpression(indexExpression, context); + if (TranslationUtils.isCacheNeeded(jsIndexExpression)) { + TemporaryVariable temporaryVariable = context.declareTemporary(null); + context.addStatementToCurrentBlock(JsAstUtils.assignment(temporaryVariable.reference(), jsIndexExpression).makeStmt()); + jsIndexExpression = temporaryVariable.reference(); + } + indexesMap.put(indexExpression, jsIndexExpression); + } + accessArrayContext = context.innerContextWithAliasesForExpressions(indexesMap); + } else { + accessArrayContext = context; + } + + return ArrayAccessTranslator.newInstance(expression, accessArrayContext); } @NotNull diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallArgumentTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallArgumentTranslator.java index 263a91bbd45..afec04b8e88 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallArgumentTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallArgumentTranslator.java @@ -25,6 +25,7 @@ import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.ValueArgument; import org.jetbrains.jet.lang.resolve.calls.model.*; import org.jetbrains.k2js.translate.context.TemporaryConstVariable; +import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.AbstractTranslator; import org.jetbrains.k2js.translate.general.Translation; @@ -42,8 +43,21 @@ public class CallArgumentTranslator extends AbstractTranslator { @Nullable JsExpression receiver, @NotNull TranslationContext context ) { - CallArgumentTranslator argumentTranslator = new CallArgumentTranslator(resolvedCall, receiver, context); - return argumentTranslator.translate(); + return translate(resolvedCall, receiver, context, context.dynamicContext().jsBlock()); + } + + @NotNull + public static ArgumentsInfo translate( + @NotNull ResolvedCall resolvedCall, + @Nullable JsExpression receiver, + @NotNull TranslationContext context, + @NotNull JsBlock block + ) { + TranslationContext innerContext = context.innerBlock(block); + CallArgumentTranslator argumentTranslator = new CallArgumentTranslator(resolvedCall, receiver, innerContext); + ArgumentsInfo result = argumentTranslator.translate(); + context.moveVarsFrom(innerContext); + return result; } public static class ArgumentsInfo { @@ -115,10 +129,38 @@ public class CallArgumentTranslator extends AbstractTranslator { else { list = result; } + List argContexts = new SmartList(); + boolean argumentsShouldBeExtractedToTmpVars = false; for (ValueArgument argument : arguments) { JetExpression argumentExpression = argument.getArgumentExpression(); assert argumentExpression != null; - list.add(Translation.translateAsExpression(argumentExpression, context)); + TranslationContext argContext = context.innerBlock(); + JsExpression argExpression = Translation.translateAsExpression(argumentExpression, argContext); + list.add(argExpression); + context.moveVarsFrom(argContext); + argContexts.add(argContext); + argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty(); + } + if (argumentsShouldBeExtractedToTmpVars) { + extractArgumentsToTmpVars(list, argContexts, context); + } + } + + private static void extractArgumentsToTmpVars( + @NotNull List argExpressions, + @NotNull List argContexts, + @NotNull TranslationContext context + ) { + for(int i=0; i argsBeforeVararg = null; + boolean argumentsShouldBeExtractedToTmpVars = false; + List argContexts = new SmartList(); + for (ValueParameterDescriptor parameterDescriptor : valueParameters) { ResolvedValueArgument actualArgument = valueArgumentsByIndex.get(parameterDescriptor.getIndex()); @@ -177,8 +222,15 @@ public class CallArgumentTranslator extends AbstractTranslator { result = new SmartList(); } } + TranslationContext argContext = context().innerBlock(); + translateSingleArgument(actualArgument, result, argContext, !isNativeFunctionCall && !hasSpreadOperator); + context().moveVarsFrom(argContext); + argContexts.add(argContext); + argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty(); + } - translateSingleArgument(actualArgument, result, context(), !isNativeFunctionCall && !hasSpreadOperator); + if (argumentsShouldBeExtractedToTmpVars) { + extractArgumentsToTmpVars(result, argContexts, context()); } if (isNativeFunctionCall && hasSpreadOperator) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/QualifiedExpressionTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/QualifiedExpressionTranslator.java index 59ecbce3d5e..4de9bf3af4d 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/QualifiedExpressionTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/QualifiedExpressionTranslator.java @@ -22,8 +22,10 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.PackageViewDescriptor; import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.utils.ErrorReportingUtils; +import org.jetbrains.k2js.translate.utils.JsAstUtils; import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression; import static org.jetbrains.k2js.translate.utils.BindingUtils.getDescriptorForReferenceExpression; @@ -37,8 +39,13 @@ public final class QualifiedExpressionTranslator { @NotNull public static AccessTranslator getAccessTranslator(@NotNull JetQualifiedExpression expression, - @NotNull TranslationContext context) { + @NotNull TranslationContext context, boolean forceOrderOfEvaluation) { JsExpression receiver = translateReceiver(expression, context); + if (forceOrderOfEvaluation && receiver != null) { + TemporaryVariable temporaryVariable = context.declareTemporary(null); + context.addStatementToCurrentBlock(JsAstUtils.assignment(temporaryVariable.reference(), receiver).makeStmt()); + receiver = temporaryVariable.reference(); + } return VariableAccessTranslator.newInstance(context, getNotNullSimpleNameSelector(expression), receiver); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/FunctionBodyTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/FunctionBodyTranslator.java index 698a173d881..f569c4ef4e0 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/FunctionBodyTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/FunctionBodyTranslator.java @@ -55,10 +55,12 @@ public final class FunctionBodyTranslator extends AbstractTranslator { if (valueParameter.hasDefaultValue()) { JsNameRef jsNameRef = functionBodyContext.getNameForDescriptor(valueParameter).makeRef(); JetExpression defaultArgument = getDefaultArgument(valueParameter); - JsExpression defaultValue = Translation.translateAsExpression(defaultArgument, functionBodyContext); - + JsBlock defaultArgBlock = new JsBlock(); + JsExpression defaultValue = Translation.translateAsExpression(defaultArgument, functionBodyContext, defaultArgBlock); + JsStatement assignStatement = assignment(jsNameRef, defaultValue).makeStmt(); + JsStatement thenStatement = JsAstUtils.mergeStatementInBlockIfNeeded(assignStatement, defaultArgBlock); JsBinaryOperation checkArgIsUndefined = equality(jsNameRef, functionBodyContext.namer().getUndefinedExpression()); - JsIf jsIf = new JsIf(checkArgIsUndefined, assignment(jsNameRef, defaultValue).makeStmt()); + JsIf jsIf = new JsIf(checkArgIsUndefined, thenStatement); result.add(jsIf); } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java index 90097dbe12b..7fee2981d14 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java @@ -21,12 +21,16 @@ import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.k2js.translate.context.Namer; +import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator; import java.util.Arrays; import java.util.Collections; import java.util.List; +import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression; + public final class JsAstUtils { private static final JsNameRef DEFINE_PROPERTY = new JsNameRef("defineProperty"); public static final JsNameRef CREATE_OBJECT = new JsNameRef("create"); @@ -61,13 +65,43 @@ public final class JsAstUtils { if (jsNode instanceof JsBlock) { return (JsBlock) jsNode; } - return new JsBlock(convertToStatement(jsNode)); + JsBlock block = new JsBlock(); + block.getStatements().add(convertToStatement(jsNode)); + return block; } @NotNull - public static JsExpression convertToExpression(@NotNull JsNode jsNode) { - assert jsNode instanceof JsExpression : "Unexpected node of type: " + jsNode.getClass().toString(); - return (JsExpression) jsNode; + public static JsExpression convertToExpression(@NotNull JsNode jsNode, @NotNull TranslationContext context) { + return convertToExpression(jsNode, context, context.dynamicContext().jsBlock()); + } + + @NotNull + public static JsExpression convertToExpression(@NotNull JsNode jsNode, @NotNull TranslationContext context, @NotNull JsBlock block) { + if (jsNode instanceof JsExpression) { + return (JsExpression) jsNode; + } + else { + assert jsNode instanceof JsStatement : "Unexpected node of type: " + jsNode.getClass().toString(); + TemporaryVariable result = context.declareTemporary(null); + AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference()); + block.getStatements().add(mutateLastExpression(jsNode, saveResultToTemporaryMutator)); + return result.reference(); + } + } + + @Nullable + public static JsExpression extractExpressionFromStatement(@Nullable JsStatement statement) { + return statement instanceof JsExpressionStatement ? ((JsExpressionStatement) statement).getExpression() : null; + } + + @NotNull + public static JsStatement mergeStatementInBlockIfNeeded(@NotNull JsStatement statement, @NotNull JsBlock block) { + if (block.isEmpty()) { + return statement; + } else { + block.getStatements().add(statement); + return block; + } } @NotNull 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 cf31c83532b..4ea808732a4 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 @@ -17,7 +17,6 @@ package org.jetbrains.k2js.translate.utils; import com.google.dart.compiler.backend.js.ast.*; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; @@ -40,8 +39,7 @@ import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*; import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName; import static org.jetbrains.k2js.translate.context.Namer.getKotlinBackingFieldName; import static org.jetbrains.k2js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression; -import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment; -import static org.jetbrains.k2js.translate.utils.JsAstUtils.createDataDescriptor; +import static org.jetbrains.k2js.translate.utils.JsAstUtils.*; public final class TranslationUtils { public static final Comparator OVERLOADED_FUNCTION_COMPARATOR = new OverloadedFunctionComparator(); @@ -344,17 +342,34 @@ public final class TranslationUtils { @NotNull public static JsExpression translateLeftExpression(@NotNull TranslationContext context, @NotNull JetBinaryExpression expression) { + return translateLeftExpression(context, expression, context.dynamicContext().jsBlock()); + } + + @NotNull + public static JsExpression translateLeftExpression( + @NotNull TranslationContext context, + @NotNull JetBinaryExpression expression, + @NotNull JsBlock block + ) { JetExpression left = expression.getLeft(); assert left != null : "Binary expression should have a left expression: " + expression.getText(); - return Translation.translateAsExpression(left, context); + return Translation.translateAsExpression(left, context, block); } @NotNull public static JsExpression translateRightExpression(@NotNull TranslationContext context, @NotNull JetBinaryExpression expression) { + return translateRightExpression(context, expression, context.dynamicContext().jsBlock()); + } + + @NotNull + public static JsExpression translateRightExpression( + @NotNull TranslationContext context, + @NotNull JetBinaryExpression expression, + @NotNull JsBlock block) { JetExpression rightExpression = expression.getRight(); assert rightExpression != null : "Binary expression should have a right expression"; - return Translation.translateAsExpression(rightExpression, context); + return Translation.translateAsExpression(rightExpression, context, block); } public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context, @@ -384,20 +399,6 @@ public final class TranslationUtils { (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null); } - @NotNull - public static Pair createTemporaryIfNeed( - @NotNull JsExpression expression, - @NotNull TranslationContext context - ) { - // don't create temp variable for simple expression - if (isCacheNeeded(expression)) { - return context.dynamicContext().createTemporary(expression); - } - else { - return Pair.create(null, expression); - } - } - @NotNull public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) { JsInvocation throwNPE = new JsInvocation(context.namer().throwNPEFunctionRef()); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/DangerousData.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/DangerousData.java deleted file mode 100644 index 07891d6c2cf..00000000000 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/DangerousData.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.utils.dangerous; - -import com.google.common.collect.Lists; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.psi.JetBlockExpression; -import org.jetbrains.jet.lang.psi.JetElement; -import org.jetbrains.jet.lang.psi.JetExpression; -import org.jetbrains.k2js.translate.context.TranslationContext; - -import java.util.List; - -/** - * This module uses a methaphor for naming. - *

- * Dangerous are the nodes that can be expressions in Kotlin but can't be expressions in JavaScript. - * These are: when, if, inlined functions. - * The issue with them is that we have to translate them to a list of statements. And also all the expressions which must be computed before - * the dangerous expressions. - * RootNode is a node which contains such an expression. For example, it may be a statement expression belongs to. - */ -public class DangerousData { - @NotNull - private final List nodesToBeGeneratedBefore = Lists.newArrayList(); - - - @NotNull - public static DangerousData collect(@NotNull JetExpression expression, @NotNull TranslationContext context) { - if (cantContainDangerousElements(expression)) { - return emptyData(); - } - return doCollectData(expression, context); - } - - private static boolean cantContainDangerousElements(@NotNull JetElement element) { - return element instanceof JetBlockExpression; - } - - @NotNull - private static DangerousData doCollectData(@NotNull JetExpression expression, - @NotNull TranslationContext context) { - DangerousData data = new DangerousData(); - FindDangerousVisitor visitor = new FindDangerousVisitor(context); - expression.accept(visitor, data); - if (!data.exists()) { - return emptyData(); - } - data.setRootNode(expression); - FindPreviousVisitor findPreviousVisitor = new FindPreviousVisitor(data); - expression.accept(findPreviousVisitor, data); - return data; - } - - private static final DangerousData EMPTY = new DangerousData() { - @Override - public boolean exists() { - return false; - } - - @Override - public boolean shouldBeTranslated() { - return false; - } - }; - - @NotNull - public static DangerousData emptyData() { - return EMPTY; - } - - @Nullable - private JetExpression dangerousNode = null; - - @Nullable - private JetExpression rootNode = null; - - public void setDangerousNode(@NotNull JetExpression dangerousNode) { - assert this.dangerousNode == null : "Should be assigned only once"; - this.dangerousNode = dangerousNode; - } - - @NotNull - public List getNodesToBeGeneratedBefore() { - return nodesToBeGeneratedBefore; - } - - public boolean exists() { - return dangerousNode != null; - } - - public boolean shouldBeTranslated() { - return exists() && !nodesToBeGeneratedBefore.isEmpty(); - } - - @NotNull - public JetExpression getDangerousNode() { - assert dangerousNode != null; - return dangerousNode; - } - - @NotNull - public JetExpression getRootNode() { - assert rootNode != null; - return rootNode; - } - - @SuppressWarnings("NullableProblems") - public void setRootNode(@NotNull JetExpression rootNode) { - this.rootNode = rootNode; - } -} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/DangerousTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/DangerousTranslator.java deleted file mode 100644 index 01d75062e70..00000000000 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/DangerousTranslator.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.utils.dangerous; - -import com.google.dart.compiler.backend.js.ast.JsExpression; -import com.google.dart.compiler.backend.js.ast.JsNode; -import com.google.dart.compiler.backend.js.ast.JsVars; -import gnu.trove.THashMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.lang.psi.JetExpression; -import org.jetbrains.k2js.translate.context.TranslationContext; -import org.jetbrains.k2js.translate.general.AbstractTranslator; -import org.jetbrains.k2js.translate.general.Translation; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public final class DangerousTranslator extends AbstractTranslator { - @NotNull - private final DangerousData data; - - @NotNull - public static JsNode translate(@NotNull DangerousData data, @NotNull TranslationContext context) { - assert data.exists(); - return new DangerousTranslator(data, context).translate(); - } - - private DangerousTranslator(@NotNull DangerousData data, @NotNull TranslationContext context) { - super(context); - this.data = data; - } - - @NotNull - private JsNode translate() { - Map aliasesForExpressions = - translateAllExpressionsAndCreateAliasesForThem(data.getNodesToBeGeneratedBefore()); - TranslationContext contextWithAliases = context().innerContextWithAliasesForExpressions(aliasesForExpressions); - return Translation.doTranslateExpression(data.getRootNode(), contextWithAliases); - } - - @NotNull - private Map translateAllExpressionsAndCreateAliasesForThem(@NotNull List expressions) { - Map aliasesForExpressions = new THashMap(expressions.size()); - List vars = new ArrayList(expressions.size()); - for (JetExpression expression : expressions) { - JsExpression translatedExpression = Translation.translateAsExpression(expression, context()); - JsVars.JsVar aliasForExpression = context().dynamicContext().createTemporaryVar(translatedExpression); - vars.add(aliasForExpression); - aliasesForExpressions.put(expression, aliasForExpression.getName().makeRef()); - } - context().addStatementToCurrentBlock(new JsVars(vars, true)); - return aliasesForExpressions; - } -} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/FindDangerousVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/FindDangerousVisitor.java deleted file mode 100644 index f3cc62e0698..00000000000 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/FindDangerousVisitor.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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.utils.dangerous; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.lang.psi.*; -import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage; -import org.jetbrains.k2js.translate.context.TranslationContext; -import org.jetbrains.k2js.translate.reference.InlinedCallExpressionTranslator; - -public final class FindDangerousVisitor extends JetTreeVisitor { - - @NotNull - private final TranslationContext context; - - public FindDangerousVisitor(@NotNull TranslationContext context) { - this.context = context; - } - - @Override - public Void visitDeclaration(@NotNull JetDeclaration dcl, DangerousData data) { - return null; - } - - @Override - public Void visitJetElement(@NotNull JetElement element, DangerousData data) { - if (data.exists()) { - return null; - } - return super.visitJetElement(element, data); - } - - @Override - public Void visitWhenExpression(@NotNull JetWhenExpression expression, DangerousData data) { - if (expressionFound(expression, data)) { - return null; - } - return super.visitWhenExpression(expression, data); - } - - @Override - public Void visitIfExpression(@NotNull JetIfExpression expression, DangerousData data) { - if (expressionFound(expression, data)) { - return null; - } - return super.visitIfExpression(expression, data); - } - - @Override - public Void visitBlockExpression(@NotNull JetBlockExpression expression, DangerousData data) { - return BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext()) - ? null - : super.visitBlockExpression(expression, data); - } - - @Override - public Void visitCallExpression(@NotNull JetCallExpression expression, DangerousData data) { - if (InlinedCallExpressionTranslator.shouldBeInlined(expression, context)) { - if (expressionFound(expression, data)) { - return null; - } - } - return super.visitCallExpression(expression, data); - } - - private boolean expressionFound(@NotNull JetExpression expression, @NotNull DangerousData data) { - if (data.exists()) { - return true; - } - if (!BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext())) { - data.setDangerousNode(expression); - return true; - } - return false; - } -} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/FindPreviousVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/FindPreviousVisitor.java deleted file mode 100644 index 58fb1fa3aae..00000000000 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/dangerous/FindPreviousVisitor.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.utils.dangerous; - -import com.google.common.collect.Maps; -import com.intellij.psi.PsiElement; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.lang.psi.*; - -import java.util.Map; - -import static org.jetbrains.k2js.translate.utils.PsiUtils.getBaseExpression; - -//TODO: refactor -public final class FindPreviousVisitor extends JetTreeVisitor { - - @NotNull - private final Map hasDangerous = Maps.newHashMap(); - - public FindPreviousVisitor(@NotNull DangerousData data) { - JetElement node = data.getDangerousNode(); - PsiElement last = data.getRootNode().getParent(); - while (node != last) { - hasDangerous.put(node, null); - PsiElement parent = node.getParent(); - assert parent instanceof JetElement; - node = (JetElement)parent; - } - } - - @Override - public Void visitJetElement(@NotNull JetElement element, DangerousData data) { - if (data.getDangerousNode() == element) { - return null; - } - if (!hasDangerous(element)) { - addElement(element, data); - } - else { - acceptChildrenThatAreBeforeTheDangerousNode(element, data); - } - return null; - } - - //TODO: return value not used, wtf? - private static boolean addElement(@NotNull JetElement element, @NotNull DangerousData data) { - if (element instanceof JetExpression) { - data.getNodesToBeGeneratedBefore().add((JetExpression)element); - return true; - } - return false; - } - - private void acceptChildrenThatAreBeforeTheDangerousNode(@NotNull JetElement element, @NotNull DangerousData data) { - PsiElement current = element.getFirstChild(); - while (current != null) { - if (current instanceof JetElement) { - ((JetElement)current).accept(this, data); - if (hasDangerous(element)) { - break; - } - } - current = current.getNextSibling(); - } - } - - @Override - public Void visitPrefixExpression(@NotNull JetPrefixExpression expression, @NotNull DangerousData data) { - if (data.getDangerousNode() == expression) { - return null; - } - if (!hasDangerous(expression)) { - addElement(expression, data); - return null; - } - else { - if (hasDangerous(getBaseExpression(expression))) { - return null; - } - else { - //TODO: - throw new IllegalStateException(); - } - } - } - - @Override - public Void visitCallExpression(@NotNull JetCallExpression expression, @NotNull DangerousData data) { - if (data.getDangerousNode() == expression) { - return null; - } - if (!hasDangerous(expression)) { - data.getNodesToBeGeneratedBefore().add(expression); - } - else { - acceptArgumentsThatAreBeforeDangerousNode(expression, data); - } - return null; - } - - private void acceptArgumentsThatAreBeforeDangerousNode(@NotNull JetCallExpression expression, @NotNull DangerousData data) { - for (ValueArgument argument : expression.getValueArguments()) { - JetExpression argumentExpression = argument.getArgumentExpression(); - assert argumentExpression != null; - argumentExpression.accept(this, data); - if (hasDangerous(argumentExpression)) { - break; - } - } - } - - private boolean hasDangerous(@NotNull JetElement element) { - return hasDangerous.containsKey(element); - } -} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/mutator/LastExpressionMutator.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/mutator/LastExpressionMutator.java index 8fc1193a44c..1af6d6e4ba0 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/mutator/LastExpressionMutator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/mutator/LastExpressionMutator.java @@ -45,6 +45,9 @@ public final class LastExpressionMutator { if (node instanceof JsIf) { return applyToIf((JsIf) node); } + if (node instanceof JsTry) { + return applyToTry((JsTry) node); + } if (node instanceof JsExpressionStatement) { return applyToStatement((JsExpressionStatement) node); } @@ -66,6 +69,15 @@ public final class LastExpressionMutator { return node; } + @NotNull + private JsNode applyToTry(@NotNull JsTry node) { + applyToBlock(node.getTryBlock()); + for(JsCatch jsCatch: node.getCatches()) { + applyToBlock(jsCatch.getBody()); + } + return node; + } + @NotNull private JsNode applyToBlock(@NotNull JsBlock node) { List statements = node.getStatements(); diff --git a/js/js.translator/testData/defaultArguments/cases/complexExpressionAsDefaultArgument.kt b/js/js.translator/testData/defaultArguments/cases/complexExpressionAsDefaultArgument.kt new file mode 100644 index 00000000000..23408dda077 --- /dev/null +++ b/js/js.translator/testData/defaultArguments/cases/complexExpressionAsDefaultArgument.kt @@ -0,0 +1,28 @@ +// http://youtrack.jetbrains.com/issue/KT-4879 +// JS: extra side effect when use when in default arguments + +package foo + +var global: String = "" + +fun bar(): Int { + global += ":bar:" + return 100 +} + +fun foo(a: Int = when { else -> bar() }): Int = a + 1 + +fun bar0(x: String = try { global } finally {}): String { + return "bar: ${x}" +} + +fun box(): String { + global = "" + assertEquals(101, foo(100)) + assertEquals("", global) + + assertEquals(101, foo()) + assertEquals(":bar:", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/dangerous/cases/2dangerousInExpression.kt b/js/js.translator/testData/expression/evaluationOrder/cases/2dangerousInExpression.kt similarity index 100% rename from js/js.translator/testData/dangerous/cases/2dangerousInExpression.kt rename to js/js.translator/testData/expression/evaluationOrder/cases/2dangerousInExpression.kt diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/andAndWithSideEffect.kt b/js/js.translator/testData/expression/evaluationOrder/cases/andAndWithSideEffect.kt new file mode 100644 index 00000000000..02b3d7eade8 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/andAndWithSideEffect.kt @@ -0,0 +1,81 @@ +package foo + +var global: String = "" + +fun bar(s: String, value: Boolean): Boolean { + global += s + return value +} + +fun box(): String { + + // Simple && Simple + global = "" + assertEquals(true, bar("A", true) && bar("B", true)) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A", true) && bar("B", false)) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A", false) && bar("B", true)) + assertEquals("A", global) + + global = "" + assertEquals(false, bar("A", false) && bar("B", false)) + assertEquals("A", global) + + // Simple && Complex + global = "" + assertEquals(true, bar("A", true) && try { bar("B", true) } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A", true) && try { bar("B", false) } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A", false) && try { bar("B", true) } finally {}) + assertEquals("A", global) + + global = "" + assertEquals(false, bar("A", false) && try { bar("B", false) } finally {}) + assertEquals("A", global) + + // Complex && Simple + global = "" + assertEquals(true, try { bar("A", true) } finally {} && bar("B", true)) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A", true) } finally {} && bar("B", false)) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A", false) } finally {} && bar("B", true)) + assertEquals("A", global) + + global = "" + assertEquals(false, try { bar("A", false) } finally {} && bar("B", false)) + assertEquals("A", global) + + // Complex && Complex + global = "" + assertEquals(true, try { bar("A", true) } finally {} && try { bar("B", true) } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A", true) } finally {} && try { bar("B", false) } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A", false) } finally {} && try { bar("B", true) } finally {}) + assertEquals("A", global) + + global = "" + assertEquals(false, try { bar("A", false) } finally {} && try { bar("B", false) } finally {}) + assertEquals("A", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/assignToArrayElementWithSideEffect.kt b/js/js.translator/testData/expression/evaluationOrder/cases/assignToArrayElementWithSideEffect.kt new file mode 100644 index 00000000000..4d7013bc4f0 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/assignToArrayElementWithSideEffect.kt @@ -0,0 +1,79 @@ +package foo + +var global: String = "" + +class A { + var prop: Int = 0 +} + +fun bar(s: String, index: Int): Int { + global += s + return index +} + +val baz: Int + get() { + global += ":baz:"; return 1 + } + +fun box(): String { + val a = array(0,1,2,3) + + + global = "" + a[bar("A", 1)] = try { global += "B"; 10 } finally {} + assertEquals("AB", global) + assertEquals(10, a[1]) + + global = "" + a[bar("A", 1)] += try { global += "B"; 10 } finally {} + assertEquals("AB", global) + assertEquals(20, a[1]) + + global = "" + a[bar("A", 1)] -= try { global += "B"; 10 } finally {} + assertEquals("AB", global) + assertEquals(10, a[1]) + + global = "" + a[bar("A", 1)] *= try { global += "B"; 2 } finally {} + assertEquals("AB", global) + assertEquals(20, a[1]) + + global = "" + a[bar("A", 1)] /= try { global += "B"; 5 } finally {} + assertEquals("AB", global) + assertEquals(4, a[1]) + + global = "" + a[bar("A", 1)] %= try { global += "B"; 3 } finally {} + assertEquals("AB", global) + assertEquals(1, a[1]) + + global = "" + a[bar("A", 1)]++ + assertEquals("A", global) + assertEquals(2, a[1]) + + global = "" + a[try { bar("A", 1)} finally {}]++ + assertEquals("A", global) + assertEquals(3, a[1]) + + global = "" + ++a[bar("A", 1)] + assertEquals("A", global) + assertEquals(4, a[1]) + + global = "" + ++a[try { bar("A", 1)} finally {}] + assertEquals("A", global) + assertEquals(5, a[1]) + + global = "" + a[baz] = try { global += "right"; 100 } finally {} + assertEquals(":baz:right", global) + assertEquals(100, a[1]) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/assignToDotQualifiedWithSideEffect.kt b/js/js.translator/testData/expression/evaluationOrder/cases/assignToDotQualifiedWithSideEffect.kt new file mode 100644 index 00000000000..78fa05e08d5 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/assignToDotQualifiedWithSideEffect.kt @@ -0,0 +1,59 @@ +package foo + +var global: String = "" + +class A { + var prop: Int = 0 +} + +fun bar(s: String, a: A): A { + global += s + return a +} + + +fun box(): String { + val a = A() + + global = "" + bar("A", a).prop = try { global += "B"; 10 } finally {} + assertEquals("AB", global) + assertEquals(10, a.prop) + + global = "" + bar("A", a).prop += try { global += "B"; 20 } finally {} + assertEquals("AB", global) + assertEquals(30, a.prop) + + global = "" + bar("A", a).prop -= try { global += "B"; 20 } finally {} + assertEquals("AB", global) + assertEquals(10, a.prop) + + global = "" + bar("A", a).prop *= try { global += "B"; 2 } finally {} + assertEquals("AB", global) + assertEquals(20, a.prop) + + global = "" + bar("A", a).prop /= try { global += "B"; 5 } finally {} + assertEquals("AB", global) + assertEquals(4, a.prop) + + global = "" + bar("A", a).prop %= try { global += "B"; 3 } finally {} + assertEquals("AB", global) + assertEquals(1, a.prop) + + global = "" + (try { bar("A", a) } finally {}).prop++ + assertEquals("A", global) + assertEquals(2, a.prop) + + global = "" + ++(try { bar("A", a) } finally {}).prop + assertEquals("A", global) + assertEquals(3, a.prop) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/callArgs.kt b/js/js.translator/testData/expression/evaluationOrder/cases/callArgs.kt new file mode 100644 index 00000000000..71385f14a1b --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/callArgs.kt @@ -0,0 +1,48 @@ +package foo + +var global = "" + +fun addToGlobal(s: String): String { + global += s + return "" +} + +fun bar(s: String, u: Int) { global += ":bar:${s}" } + +class A() { + fun memFun(s: String) { global += ":memFun:${s}" } +} + +class B() { + fun A.bExt(s: String) { global += ":bExt:${s}" } + + fun baz(s: String, a: A) = a.bExt(s) +} + +fun A.extFun(s: String) { global += ":extFun:${s}" } + +fun box(): String { + + bar(addToGlobal("A"), try { global += "B"; 10 } finally {}) + assertEquals("AB:bar:", global) + + global = "" + val a = A() + a.memFun(try { "A" } finally {}) + assertEquals(":memFun:A", global) + + global = "" + (try { global += "A"; a } finally {}).memFun("B") + assertEquals("A:memFun:B", global) + + global = "" + (try { global += "A"; a } finally {}).memFun(try { global += "B"; "C" } finally {}) + assertEquals("AB:memFun:C", global) + + global = "" + val b = B() + b.baz("S", a) + assertEquals(":bExt:S", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/callVarargs.kt b/js/js.translator/testData/expression/evaluationOrder/cases/callVarargs.kt new file mode 100644 index 00000000000..7d04e5b123b --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/callVarargs.kt @@ -0,0 +1,23 @@ +package foo + +var global: String = "" + +fun bar(s: String, value: Int): Int { + global += s + return value +} + +fun baz(vararg args: Int): String { + return "baz: ${args.size}" +} + +fun box(): String { + baz(bar("A", 10), try { global += "B"; 20} finally {}) + assertEquals("AB", global) + + global = "" + baz(bar("A", 10), 30, if (true) { while(false){}; global+= "B"; 20 } else { 50 }) + assertEquals("AB", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/compareToIntrinsicWithSideEffect.kt b/js/js.translator/testData/expression/evaluationOrder/cases/compareToIntrinsicWithSideEffect.kt new file mode 100644 index 00000000000..04c71a33c69 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/compareToIntrinsicWithSideEffect.kt @@ -0,0 +1,61 @@ +package foo + +var global: String = "" + +fun bar(s: String): String { + global += s + return s +} + +fun box(): String { + + global = "" + assertEquals(true, bar("A") < bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(true, bar("A") <= bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A") > bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A") >= bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(true, bar("A") < try { bar("B") } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(true, bar("A") <= try { bar("B") } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A") } finally {} > bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A") } finally {} >= bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(true, try { bar("A") } finally {} < try { bar("B") } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(true, try { bar("A") } finally {} <= try { bar("B") } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A") } finally {} > try { bar("B") } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A") } finally {} >= try { bar("B") } finally {}) + assertEquals("AB", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/dangerous/cases/dangerousInline.kt b/js/js.translator/testData/expression/evaluationOrder/cases/dangerousInline.kt similarity index 100% rename from js/js.translator/testData/dangerous/cases/dangerousInline.kt rename to js/js.translator/testData/expression/evaluationOrder/cases/dangerousInline.kt diff --git a/js/js.translator/testData/dangerous/cases/dangerousInsideDangerous.kt b/js/js.translator/testData/expression/evaluationOrder/cases/dangerousInsideDangerous.kt similarity index 100% rename from js/js.translator/testData/dangerous/cases/dangerousInsideDangerous.kt rename to js/js.translator/testData/expression/evaluationOrder/cases/dangerousInsideDangerous.kt diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/elvisComplex.kt b/js/js.translator/testData/expression/evaluationOrder/cases/elvisComplex.kt new file mode 100644 index 00000000000..51a817ce12e --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/elvisComplex.kt @@ -0,0 +1,22 @@ +package foo + +var sideEffect: Int = 0; + +class A(val x: Int) + +val global_a = A(1) +val global_b = A(2) + +fun nullFun(value: Boolean): Any? = if (value) null else global_a + +fun box(): String { + + nullFun(false) ?: global_b + assertEquals(global_a, nullFun(false) ?: try { ++sideEffect; global_b } finally {}, "false, global_b") + assertEquals(0, sideEffect, "false, global_b side effect") + + assertEquals(global_b, nullFun(true) ?: try { ++sideEffect; global_b } finally {}, "true, global_b") + assertEquals(1, sideEffect, "true, global_b side effect") + + return "OK" +} diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/equalsIntrinsicWithSideEffect.kt b/js/js.translator/testData/expression/evaluationOrder/cases/equalsIntrinsicWithSideEffect.kt new file mode 100644 index 00000000000..adaa9758754 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/equalsIntrinsicWithSideEffect.kt @@ -0,0 +1,45 @@ +package foo + +var global: String = "" + +fun bar(s: String): String { + global += s + return s +} + +fun box(): String { + + global = "" + assertEquals(false, bar("A") == bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(true, bar("A") != bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A") == try { bar("B") } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(true, bar("A") != try { bar("B") } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A") } finally {} == bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(true, try { bar("A") } finally {} != bar("B")) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A") } finally {} == try { bar("B") } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(true, try { bar("A") } finally {} != try { bar("B") } finally {}) + assertEquals("AB", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/dangerous/cases/evaluationOrder.kt b/js/js.translator/testData/expression/evaluationOrder/cases/evaluationOrder1.kt similarity index 100% rename from js/js.translator/testData/dangerous/cases/evaluationOrder.kt rename to js/js.translator/testData/expression/evaluationOrder/cases/evaluationOrder1.kt diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/evaluationOrder2.kt b/js/js.translator/testData/expression/evaluationOrder/cases/evaluationOrder2.kt new file mode 100644 index 00000000000..948bff84c2e --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/evaluationOrder2.kt @@ -0,0 +1,29 @@ +// Order of evaluation differs for JVM and Javascript backend +// http://youtrack.jetbrains.com/issue/KT-5254 + +package foo + +var s = "" + +fun a():String { + s += "A" + return "" +} + +fun b():String { + s += "B" + return "" +} + +fun c():String { + s += "C" + return "" +} + + +fun box(): String { + var res = (if(true) {a()} else "") + b() + (if (true) {c()} else "") + assertEquals("ABC", s) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/dangerous/cases/ifAsFunArgument.kt b/js/js.translator/testData/expression/evaluationOrder/cases/ifAsFunArgument.kt similarity index 100% rename from js/js.translator/testData/dangerous/cases/ifAsFunArgument.kt rename to js/js.translator/testData/expression/evaluationOrder/cases/ifAsFunArgument.kt diff --git a/js/js.translator/testData/dangerous/cases/ifAsPlusArgument.kt b/js/js.translator/testData/expression/evaluationOrder/cases/ifAsPlusArgument.kt similarity index 100% rename from js/js.translator/testData/dangerous/cases/ifAsPlusArgument.kt rename to js/js.translator/testData/expression/evaluationOrder/cases/ifAsPlusArgument.kt diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/ifWithComplex.kt b/js/js.translator/testData/expression/evaluationOrder/cases/ifWithComplex.kt new file mode 100644 index 00000000000..8e04ff7b4c3 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/ifWithComplex.kt @@ -0,0 +1,28 @@ +package foo + +var sideEffect: Int = 0; + +fun id(value: Boolean): Boolean = value + +fun box(): String { + + assertEquals(if (id(true)) try { ++sideEffect; 10 } finally {} else 20, 10) + assertEquals(1, sideEffect) + + assertEquals(if (id(false)) try { ++sideEffect; 10 } finally {} else 20, 20) + assertEquals(1, sideEffect) + + assertEquals(if (id(true)) 100 else try { ++sideEffect; 200 } finally {}, 100) + assertEquals(1, sideEffect) + + assertEquals(if (id(false)) 100 else try { ++sideEffect; 200 } finally {}, 200) + assertEquals(2, sideEffect) + + assertEquals(if (id(true)) try { ++sideEffect; 1000 } finally {} else try { ++sideEffect; 2000 } finally {}, 1000) + assertEquals(3, sideEffect) + + assertEquals(if (id(false)) try { ++sideEffect; 1000 } finally {} else try { ++sideEffect; 2000 } finally {}, 2000) + assertEquals(4, sideEffect) + + return "OK" +} diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicComplex.kt b/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicComplex.kt new file mode 100644 index 00000000000..dcc9a871088 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicComplex.kt @@ -0,0 +1,15 @@ +package foo + +fun box(): String { + var x = 0 + assertEquals(false, ++x > x) + assertEquals(false, ++x > try {x} finally {}) + + assertEquals(false, x++ > x) + assertEquals(false, x++ > try {x} finally {}) + + assertEquals(true, ++x == x) + assertEquals(true, ++x == try {x} finally {}) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithSideEffect.kt b/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithSideEffect.kt new file mode 100644 index 00000000000..340ffd2081a --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithSideEffect.kt @@ -0,0 +1,81 @@ +package foo + +var global: String = "" + +fun bar(s: String, value: Boolean): Boolean { + global += s + return value +} + +fun box(): String { + + // Simple || Simple + global = "" + assertEquals(true, bar("A", true) || bar("B", true)) + assertEquals("A", global) + + global = "" + assertEquals(true, bar("A", true) || bar("B", false)) + assertEquals("A", global) + + global = "" + assertEquals(true, bar("A", false) || bar("B", true)) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A", false) || bar("B", false)) + assertEquals("AB", global) + + // Simple || Complex + global = "" + assertEquals(true, bar("A", true) || try { bar("B", true) } finally {}) + assertEquals("A", global) + + global = "" + assertEquals(true, bar("A", true) || try { bar("B", false) } finally {}) + assertEquals("A", global) + + global = "" + assertEquals(true, bar("A", false) || try { bar("B", true) } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, bar("A", false) || try { bar("B", false) } finally {}) + assertEquals("AB", global) + + // Complex || Simple + global = "" + assertEquals(true, try { bar("A", true) } finally {} || bar("B", true)) + assertEquals("A", global) + + global = "" + assertEquals(true, try { bar("A", true) } finally {} || bar("B", false)) + assertEquals("A", global) + + global = "" + assertEquals(true, try { bar("A", false) } finally {} || bar("B", true)) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A", false) } finally {} || bar("B", false)) + assertEquals("AB", global) + + // Complex || Complex + global = "" + assertEquals(true, try { bar("A", true) } finally {} || try { bar("B", true) } finally {}) + assertEquals("A", global) + + global = "" + assertEquals(true, try { bar("A", true) } finally {} || try { bar("B", false) } finally {}) + assertEquals("A", global) + + global = "" + assertEquals(true, try { bar("A", false) } finally {} || try { bar("B", true) } finally {}) + assertEquals("AB", global) + + global = "" + assertEquals(false, try { bar("A", false) } finally {} || try { bar("B", false) } finally {}) + assertEquals("AB", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/dangerous/cases/whenAsMinusArgument.kt b/js/js.translator/testData/expression/evaluationOrder/cases/whenAsMinusArgument.kt similarity index 100% rename from js/js.translator/testData/dangerous/cases/whenAsMinusArgument.kt rename to js/js.translator/testData/expression/evaluationOrder/cases/whenAsMinusArgument.kt diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/whenWithComplexConditions.kt b/js/js.translator/testData/expression/evaluationOrder/cases/whenWithComplexConditions.kt new file mode 100644 index 00000000000..39f3dc6a0f4 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/whenWithComplexConditions.kt @@ -0,0 +1,68 @@ +package foo + +var global: String = "" + +fun id(s: String, value: Boolean): Boolean { + global += s + return value +} + +fun box(): String { + + when { + id("A", true), id("B", true) -> 10 + } + assertEquals("A", global) + + global = "" + when { + id("A", false), id("B", true), id("C", true) -> 10 + } + assertEquals("AB", global) + + global = "" + var b = true + when { + try { global += "A"; b } finally {} -> 10 + try { global += "B"; !b } finally {} -> 20 + } + assertEquals("A", global) + + global = "" + b = false + when { + try { global += "A"; b } finally {} -> 10 + try { global += "B"; !b } finally {} -> 20 + } + assertEquals("AB", global) + + global = "" + b = true + when { + b, try { global += "A"; !b } finally {} -> 10 + } + assertEquals("", global) + + global = "" + b = false + when { + b, try { global += "A"; !b } finally {} -> 10 + } + assertEquals("A", global) + + global = "" + when { + false -> { + global += "A" + } + try { global += "B"; false } finally {} -> { + global += "D" + } + else -> { + global += "C" + } + } + assertEquals("BC", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/for/cases/forWithComplexOneStatement.kt b/js/js.translator/testData/expression/for/cases/forWithComplexOneStatement.kt new file mode 100644 index 00000000000..39f607796c5 --- /dev/null +++ b/js/js.translator/testData/expression/for/cases/forWithComplexOneStatement.kt @@ -0,0 +1,12 @@ +package foo + +fun box(): String { + var s: String = "" + + s = "" + for(i in 0..2) + try { s += "A"} finally {} + assertEquals("AAA", s) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/misc/cases/ifElseAsExpressionWithThrow.kt b/js/js.translator/testData/expression/if/cases/ifElseAsExpressionWithThrow.kt similarity index 100% rename from js/js.translator/testData/expression/misc/cases/ifElseAsExpressionWithThrow.kt rename to js/js.translator/testData/expression/if/cases/ifElseAsExpressionWithThrow.kt diff --git a/js/js.translator/testData/expression/if/cases/ifInsideLambda.kt b/js/js.translator/testData/expression/if/cases/ifInsideLambda.kt new file mode 100644 index 00000000000..d1354b61882 --- /dev/null +++ b/js/js.translator/testData/expression/if/cases/ifInsideLambda.kt @@ -0,0 +1,43 @@ +// http://youtrack.jetbrains.com/issue/KT-5594 +// JS: compiler crashes + +package foo + +fun bar(f: () -> Unit) { +} + +fun test() { + bar { + // val actionId: Any = 1 + val item: Any? = 1 + if (item != null) { + // In original version, as I remember, `when` was an important to reproduce, but now it is not. + // when(actionId){ + // 1 -> { 1 } + // "2" -> { "2"} + // else -> {} + // } + } + } + bar { + val actionId: Any = 1 + val item: Any? = 1 + if (item != null) { + when (actionId) { + 1 -> { + 1 + } + "2" -> { + "2" + } + else -> { + } + } + } + } + +} + +fun box(): String { + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/if/cases/nestedIf.kt b/js/js.translator/testData/expression/if/cases/nestedIf.kt new file mode 100644 index 00000000000..9828e4a7694 --- /dev/null +++ b/js/js.translator/testData/expression/if/cases/nestedIf.kt @@ -0,0 +1,35 @@ +// JS: generate wrong code for nested if +// http://youtrack.jetbrains.com/issue/KT-5576 + +package foo + +fun test2(a: Boolean, b: Boolean, c: Boolean) { + val a = + if (a) { + if (b) { + "1" + } else if (c) { + "2" + } else { + throw Exception("Rest parameter must be array types") + } + } + else { + "3" + } +} + +fun box(): String { + + test2(true, true, false) + var wasException = false + try { + test2(true, false, false) + } + catch(e: Exception) { + wasException = true + } + assertEquals(true, wasException) + + return "OK" +} diff --git a/js/js.translator/testData/expression/try/cases/tryCatchExpr.kt b/js/js.translator/testData/expression/try/cases/tryCatchExpr.kt new file mode 100644 index 00000000000..2915364dcaf --- /dev/null +++ b/js/js.translator/testData/expression/try/cases/tryCatchExpr.kt @@ -0,0 +1,20 @@ +package foo + +fun box(): String { + var res = try { 10 } catch(e: Exception) { 20 } + assertEquals(10, res) + + res = try { 10 } catch(e: Exception) { 20 } finally { 80 } + assertEquals(10, res) + + res = try { 10; throw RuntimeException() } catch(e: Exception) { 20 } + assertEquals(20, res) + + res = try { 10; throw RuntimeException() } catch(e: Exception) { 20 } finally { 100 } + assertEquals(20, res) + + res = 50 + try { 10 } catch(e: Exception) { 20 } + assertEquals(60, res) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/while/cases/whileWithComplexOneStatement.kt b/js/js.translator/testData/expression/while/cases/whileWithComplexOneStatement.kt new file mode 100644 index 00000000000..dd2795671f5 --- /dev/null +++ b/js/js.translator/testData/expression/while/cases/whileWithComplexOneStatement.kt @@ -0,0 +1,19 @@ +package foo + +fun box(): String { + var s: String = "" + + var i:Int = 0 + while(i<2) + try { s += "A"; i++} finally {} + assertEquals("AA", s) + + s = "" + i = 0 + do + try { s += "A"; i++} finally {} + while(i<2) + assertEquals("AA", s) + + return "OK" +} \ No newline at end of file