From 3578845e87dca1545e98da271918ce0d19eb31aa Mon Sep 17 00:00:00 2001 From: Michael Nedzelsky Date: Tue, 26 Aug 2014 14:08:32 +0400 Subject: [PATCH] JS backend: add support for break, return, continue, throw in expressions --- .../backend/js/ast/JsEmptyExpression.java | 36 ++++++ .../compiler/backend/js/ast/JsProgram.java | 7 ++ .../test/semantics/EvaluationOrderTest.java | 11 ++ .../k2js/test/semantics/ForeachTest.java | 2 +- .../callTranslator/FunctionCallCases.kt | 3 + .../translate/context/TranslationContext.java | 10 ++ .../expression/ExpressionVisitor.java | 59 ++++----- .../translate/expression/LoopTranslator.kt | 20 ++- .../translate/expression/TryTranslator.java | 2 +- .../translate/expression/WhenTranslator.java | 26 ++-- .../k2js/translate/general/Translation.java | 22 +++- .../operation/BinaryOperationTranslator.java | 98 ++++++++++----- .../IntrinsicAssignmentTranslator.java | 5 + .../OverloadedAssignmentTranslator.java | 5 + .../reference/CallArgumentTranslator.java | 52 ++++++-- .../utils/FunctionBodyTranslator.java | 2 +- .../k2js/translate/utils/JsAstUtils.java | 47 ++++--- .../translate/utils/TranslationUtils.java | 1 + .../cases/andAndWithBreakContinueReturn.kt | 50 +++++++- .../cases/callWithBreakContinueReturn.kt | 88 +++++++++++++ .../cases/elvisWithBreakContinueReturn.kt | 30 +++-- .../cases/intrinsicWithBreakContinueReturn.kt | 119 ++++++++++++++++++ .../loopWithBreakContinueReturnInCondition.kt | 98 +++++++++++++++ .../cases/orOrWithBreakContinueReturn.kt | 46 ++++++- .../cases/orOrWithSideEffect.kt | 3 + ...forWithWhile.kt => labeledForWithWhile.kt} | 0 26 files changed, 711 insertions(+), 131 deletions(-) create mode 100644 js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsEmptyExpression.java create mode 100644 js/js.translator/testData/expression/evaluationOrder/cases/callWithBreakContinueReturn.kt create mode 100644 js/js.translator/testData/expression/evaluationOrder/cases/intrinsicWithBreakContinueReturn.kt create mode 100644 js/js.translator/testData/expression/evaluationOrder/cases/loopWithBreakContinueReturnInCondition.kt rename js/js.translator/testData/expression/for/cases/{forWithWhile.kt => labeledForWithWhile.kt} (100%) diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsEmptyExpression.java b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsEmptyExpression.java new file mode 100644 index 00000000000..c8aa2bc8cff --- /dev/null +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsEmptyExpression.java @@ -0,0 +1,36 @@ +/* + * 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 com.google.dart.compiler.backend.js.ast; + +import org.jetbrains.annotations.NotNull; + +public class JsEmptyExpression extends JsExpressionImpl { + + JsEmptyExpression() { + } + + @Override + @NotNull + public JsStatement makeStmt() { + return new JsEmpty(); + } + + @Override + public void accept(JsVisitor visitor) { + throw new IllegalArgumentException("empty expression should not be here during generating Javascript code"); + } +} diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java index b39e764b23d..8c923117b1a 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java @@ -20,6 +20,7 @@ import static com.google.dart.compiler.backend.js.ast.JsNumberLiteral.JsIntLiter public final class JsProgram extends SourceInfoAwareJsNode { @NotNull private final JsEmpty emptyStatement; + @NotNull final JsExpression emptyExpression; private JsProgramFragment[] fragments; @@ -36,6 +37,7 @@ public final class JsProgram extends SourceInfoAwareJsNode { setFragmentCount(1); emptyStatement = new JsEmpty(); + emptyExpression = new JsEmptyExpression(); } @NotNull @@ -43,6 +45,11 @@ public final class JsProgram extends SourceInfoAwareJsNode { return emptyStatement; } + @NotNull + public JsExpression getEmptyExpression() { + return emptyExpression; + } + public JsBlock getFragmentBlock(int fragment) { if (fragment < 0 || fragment >= fragments.length) { throw new IllegalArgumentException("Invalid fragment: " + fragment); 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 index 0bf7aa4bec7..b7c867a0d63 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/EvaluationOrderTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/EvaluationOrderTest.java @@ -121,4 +121,15 @@ public class EvaluationOrderTest extends AbstractExpressionTest { checkFooBoxIsOk(); } + public void testIntrinsicWithBreakContinueReturn() throws Exception { + checkFooBoxIsOk(); + } + + public void testCallWithBreakContinueReturn() throws Exception { + checkFooBoxIsOk(); + } + + public void testLoopWithBreakContinueReturnInCondition() 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 44a259aa938..ef35e822491 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 @@ -54,7 +54,7 @@ public final class ForeachTest extends AbstractExpressionTest { checkFooBoxIsOk(); } - public void testForWithWhile() throws Exception { + public void testLabeledForWithWhile() throws Exception { checkFooBoxIsOk(); } } \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt index e8ff43ba563..cf5b124fc3b 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt @@ -192,6 +192,9 @@ object SuperCallCase : FunctionCallCase { } fun FunctionCallInfo.translateFunctionCall(): JsExpression { + if (this.argumentsInfo.hasEmptyExpressionArgument()) + return context.getEmptyExpression() + val intrinsic = DelegateFunctionIntrinsic.intrinsic(this) return when { 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 5dee030e583..5cb77518d86 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 @@ -286,6 +286,16 @@ public class TranslationContext { return dynamicContext.jsBlock(); } + @NotNull + public JsEmpty getEmptyStatement() { + return program().getEmptyStatement(); + } + + @NotNull + public JsExpression getEmptyExpression() { + return program().getEmptyExpression(); + } + @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 ca1ba3c0094..532391e7c0a 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 @@ -96,13 +96,13 @@ public final class ExpressionVisitor extends TranslatorVisitor { public JsNode visitBlockExpression(@NotNull JetBlockExpression jetBlock, @NotNull TranslationContext context) { List statements = jetBlock.getStatements(); JsBlock jsBlock = new JsBlock(); - TranslationContext blockContext = context.innerBlock(jsBlock); for (JetElement statement : statements) { assert statement instanceof JetExpression : "Elements in JetBlockExpression " + "should be of type JetExpression"; - JsNode jsNode = statement.accept(this, blockContext); - if (jsNode != null) { - jsBlock.getStatements().add(convertToStatement(jsNode)); + JsNode jsNode = Translation.translateExpression((JetExpression)statement, context, jsBlock); + JsStatement jsStatement = convertToStatement(jsNode); + if (!JsAstUtils.isEmptyStatement(jsStatement)) { + jsBlock.getStatements().add(jsStatement); } } return jsBlock; @@ -121,7 +121,14 @@ public final class ExpressionVisitor extends TranslatorVisitor { public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression, @NotNull TranslationContext context) { JetExpression returned = jetReturnExpression.getReturnedExpression(); - return new JsReturn(returned != null ? translateAsExpression(returned, context) : null).source(jetReturnExpression); + if (returned == null) { + return new JsReturn(null).source(jetReturnExpression); + } + JsExpression jsReturnExpression = translateAsExpression(returned, context); + if (JsAstUtils.isEmptyExpression(jsReturnExpression)) { + return context.getEmptyExpression(); + } + return new JsReturn(jsReturnExpression).source(jetReturnExpression); } @Override @@ -130,12 +137,9 @@ public final class ExpressionVisitor extends TranslatorVisitor { @NotNull TranslationContext context) { JetExpression expressionInside = expression.getExpression(); if (expressionInside != null) { - JsNode translated = expressionInside.accept(this, context); - if (translated != null) { - return translated; - } + return Translation.translateExpression(expressionInside, context); } - return context.program().getEmptyStatement(); + return context.getEmptyStatement(); } @Override @@ -151,6 +155,10 @@ public final class ExpressionVisitor extends TranslatorVisitor { public JsNode visitProperty(@NotNull JetProperty expression, @NotNull TranslationContext context) { VariableDescriptor descriptor = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, expression); JsExpression initializer = translateInitializerForProperty(expression, context); + if (initializer != null && JsAstUtils.isEmptyExpression(initializer)) { + return context.getEmptyExpression(); + } + JsName name = context.getNameForDescriptor(descriptor); if (isVarCapturedInClosure(context.bindingContext(), descriptor)) { JsNameRef alias = getCapturedVarAccessor(name.makeRef()); @@ -184,7 +192,11 @@ public final class ExpressionVisitor extends TranslatorVisitor { @Override @NotNull public JsNode visitIfExpression(@NotNull JetIfExpression expression, @NotNull TranslationContext context) { - JsExpression testExpression = translateConditionExpression(expression.getCondition(), context); + assert expression.getCondition() != null : "condition should not ne null: " + expression.getText(); + JsExpression testExpression = Translation.translateAsExpression(expression.getCondition(), context); + if (JsAstUtils.isEmptyExpression(testExpression)) { + return testExpression; + } boolean isKotlinExpression = BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext()); @@ -204,8 +216,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { } } JsIf ifStatement = new JsIf(testExpression, thenStatement, elseStatement); - ifStatement.source(expression); - return isKotlinExpression ? convertToExpression(ifStatement, context) : ifStatement; + return ifStatement.source(expression); } @Override @@ -215,23 +226,6 @@ public final class ExpressionVisitor extends TranslatorVisitor { return ReferenceTranslator.translateSimpleNameWithQualifier(expression, null, context).source(expression); } - @NotNull - private JsExpression translateConditionExpression(@Nullable JetExpression expression, - @NotNull TranslationContext context) { - JsExpression jsCondition = translateNullableExpression(expression, context); - assert (jsCondition != null) : "Condition should not be empty"; - return convertToExpression(jsCondition, context); - } - - @Nullable - private JsExpression translateNullableExpression(@Nullable JetExpression expression, - @NotNull TranslationContext context) { - if (expression == null) { - return null; - } - return convertToExpression(expression.accept(this, context), context); - } - @Override @NotNull public JsNode visitWhileExpression(@NotNull JetWhileExpression expression, @NotNull TranslationContext context) { @@ -286,8 +280,9 @@ public final class ExpressionVisitor extends TranslatorVisitor { ) { JetExpression baseExpression = expression.getBaseExpression(); assert baseExpression != null; - return new JsLabel(context.scope().declareName(getReferencedName(expression.getTargetLabel())), - convertToStatement(baseExpression.accept(this, context))).source(expression); + JsName name = context.scope().declareName(getReferencedName(expression.getTargetLabel())); + JsStatement baseStatement = Translation.translateAsStatement(baseExpression, context); + return new JsLabel(name, baseStatement).source(expression); } @Override diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/LoopTranslator.kt b/js/js.translator/src/org/jetbrains/k2js/translate/expression/LoopTranslator.kt index 01bfe10cf8b..6e1bc621e49 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/LoopTranslator.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/LoopTranslator.kt @@ -40,12 +40,13 @@ public fun createWhile(doWhile: Boolean, expression: JetWhileExpressionBase, con throw IllegalArgumentException("condition expression should not be null: ${expression.getText()}") val conditionBlock = JsBlock() var jsCondition = Translation.translateAsExpression(conditionExpression, context, conditionBlock) + val isEmptyLoopCondition = isEmptyExpression(jsCondition) val body = expression.getBody() var bodyStatement = if (body != null) Translation.translateAsStatement(body, context) else - context.program().getEmptyStatement() + context.getEmptyStatement() if (!conditionBlock.isEmpty()) { val breakIfConditionIsFalseStatement = JsIf(not(jsCondition), JsBreak()) @@ -56,18 +57,29 @@ public fun createWhile(doWhile: Boolean, expression: JetWhileExpressionBase, con // translate to: tmpSecondRun = false; do { if(tmpSecondRun) { if(!tmpExprVar) break; } else tmpSecondRun=true; } while(true) val secondRun = context.declareTemporary(JsLiteral.FALSE) context.addStatementToCurrentBlock(secondRun.assignmentExpression().makeStmt()) - conditionBlock.getStatements().add(breakIfConditionIsFalseStatement) + if (!isEmptyLoopCondition) { + conditionBlock.getStatements().add(breakIfConditionIsFalseStatement) + } val ifStatement = JsIf(secondRun.reference(), conditionBlock, assignment(secondRun.reference(), JsLiteral.TRUE).makeStmt()) bodyBlock.getStatements().add(0, ifStatement) } else { // translate to: while (true) { if(!tmpExprVar) break; } - conditionBlock.getStatements().add(breakIfConditionIsFalseStatement) - bodyBlock.getStatements().addAll(0, conditionBlock.getStatements()) + if (isEmptyLoopCondition) { + bodyBlock.getStatements().clear() + context.addStatementsToCurrentBlockFrom(conditionBlock) + } + else { + conditionBlock.getStatements().add(breakIfConditionIsFalseStatement) + bodyBlock.getStatements().addAll(0, conditionBlock.getStatements()) + } } bodyStatement = bodyBlock } + else if (isEmptyLoopCondition) { + jsCondition = JsLiteral.FALSE + } val result = if (doWhile) JsDoWhile() else JsWhile() result.setCondition(jsCondition) diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/TryTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/TryTranslator.java index 1553c061284..642d5671095 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/TryTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/TryTranslator.java @@ -89,7 +89,7 @@ public final class TryTranslator extends AbstractTranslator { private JsBlock translateCatchBody(@NotNull JetCatchClause catchClause) { JetExpression catchBody = catchClause.getCatchBody(); if (catchBody == null) { - return convertToBlock(program().getEmptyStatement()); + return convertToBlock(context().getEmptyStatement()); } return convertToBlock(Translation.translateAsStatement(catchBody, context())); } 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 54879280255..3720fdf4518 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 @@ -20,7 +20,6 @@ import com.google.dart.compiler.backend.js.ast.*; 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; @@ -28,19 +27,12 @@ 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.convertToExpression; import static org.jetbrains.k2js.translate.utils.JsAstUtils.negated; public final class WhenTranslator extends AbstractTranslator { @Nullable public static JsNode translate(@NotNull JetWhenExpression expression, @NotNull TranslationContext context) { - WhenTranslator translator = new WhenTranslator(expression, context); - - if (BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext())) { - return translator.translateAsStatement(); - } - - return translator.translateAsExpression(); + return new WhenTranslator(expression, context).translate(); } @NotNull @@ -69,12 +61,11 @@ public final class WhenTranslator extends AbstractTranslator { } } - @Nullable - private JsNode translateAsExpression() { - return convertToExpression(translateAsStatement(), context()); - } + private JsStatement translate() { + if (expressionToMatch != null && JsAstUtils.isEmptyExpression(expressionToMatch)) { + return context().getEmptyStatement(); + } - private JsStatement translateAsStatement() { JsIf currentIf = null; JsIf resultIf = null; for (JetWhenEntry entry : whenExpression.getEntries()) { @@ -88,7 +79,7 @@ public final class WhenTranslator extends AbstractTranslator { statement = JsAstUtils.mergeStatementInBlockIfNeeded(statement, statementBlock); if (resultIf == null) { - currentIf = new JsIf(translateConditions(entry, context()), statement); + currentIf = JsAstUtils.newJsIf(translateConditions(entry, context()), statement); resultIf = currentIf; } else { @@ -97,7 +88,7 @@ public final class WhenTranslator extends AbstractTranslator { return resultIf; } JsBlock conditionsBlock = new JsBlock(); - JsIf nextIf = new JsIf(translateConditions(entry, context().innerBlock(conditionsBlock)), statement); + JsIf nextIf = JsAstUtils.newJsIf(translateConditions(entry, context().innerBlock(conditionsBlock)), statement); JsStatement statementToAdd = JsAstUtils.mergeStatementInBlockIfNeeded(nextIf, conditionsBlock); currentIf.setElseStatement(statementToAdd); currentIf = nextIf; @@ -144,7 +135,8 @@ public final class WhenTranslator extends AbstractTranslator { } 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()); + JsIf ifStatement = JsAstUtils.newJsIf(leftExpression, JsAstUtils.assignment(result, JsLiteral.TRUE).makeStmt(), + rightContext.getCurrentBlock()); context.addStatementToCurrentBlock(ifStatement); return result; } 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 f55e1014002..652889f940f 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 @@ -26,6 +26,7 @@ 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.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage; import org.jetbrains.jet.plugin.MainFunctionDetector; import org.jetbrains.k2js.config.Config; import org.jetbrains.k2js.facade.MainCallParameters; @@ -36,6 +37,7 @@ import org.jetbrains.k2js.facade.exceptions.UnsupportedFeatureException; import org.jetbrains.k2js.translate.callTranslator.CallTranslator; import org.jetbrains.k2js.translate.context.Namer; import org.jetbrains.k2js.translate.context.StaticContext; +import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.declaration.PackageDeclarationTranslator; import org.jetbrains.k2js.translate.expression.ExpressionVisitor; @@ -46,6 +48,7 @@ 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.mutator.AssignToExpressionMutator; import java.util.Collection; import java.util.Collections; @@ -53,6 +56,7 @@ 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.mutator.LastExpressionMutator.mutateLastExpression; /** * This class provides a interface which all translators use to interact with each other. @@ -101,7 +105,7 @@ public final class Translation { @NotNull public static JsExpression translateAsExpression(@NotNull JetExpression expression, @NotNull TranslationContext context) { - return convertToExpression(translateExpression(expression, context), context); + return translateAsExpression(expression, context, context.dynamicContext().jsBlock()); } @NotNull @@ -110,7 +114,21 @@ public final class Translation { @NotNull TranslationContext context, @NotNull JsBlock block ) { - return convertToExpression(translateExpression(expression, context, block), context, block); + JsNode jsNode = translateExpression(expression, context, block); + if (jsNode instanceof JsExpression) { + return (JsExpression) jsNode; + } + + assert jsNode instanceof JsStatement : "Unexpected node of type: " + jsNode.getClass().toString(); + if (BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext())) { + TemporaryVariable result = context.declareTemporary(null); + AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference()); + block.getStatements().add(mutateLastExpression(jsNode, saveResultToTemporaryMutator)); + return result.reference(); + } + + block.getStatements().add(convertToStatement(jsNode)); + return context.getEmptyExpression(); } @NotNull 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 cdaee1c8370..d4d1bebd528 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 @@ -23,6 +23,7 @@ import org.jetbrains.jet.lang.descriptors.CallableDescriptor; import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; import org.jetbrains.jet.lang.psi.JetBinaryExpression; import org.jetbrains.jet.lang.psi.JetExpression; +import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.jet.lang.types.expressions.OperatorConventions; import org.jetbrains.jet.lexer.JetToken; @@ -111,25 +112,41 @@ public final class BinaryOperationTranslator extends AbstractTranslator { @NotNull private JsExpression translateElvis() { JsExpression leftExpression = Translation.translateAsExpression(leftJetExpression, context()); - - JsBlock rightBlock = new JsBlock(); - JsNode rightNode = Translation.translateExpression(rightJetExpression, context(), rightBlock); - - if (rightNode instanceof JsExpression && rightBlock.isEmpty()) { - return TranslationUtils.notNullConditional(leftExpression, (JsExpression) rightNode, context()); + if (JsAstUtils.isEmptyExpression(leftExpression)) { + return leftExpression; } - TemporaryVariable result = context().declareTemporary(leftExpression); - context().addStatementToCurrentBlock(result.assignmentExpression().makeStmt()); + JsBlock rightBlock = new JsBlock(); + JsExpression rightExpression = Translation.translateAsExpression(rightJetExpression, context(), rightBlock); - JsExpression testExpression = TranslationUtils.isNullCheck(result.reference()); - JsExpression rightExpression = JsAstUtils.convertToExpression(rightNode, context(), rightBlock); - rightBlock.getStatements().add(JsAstUtils.assignment(result.reference(), rightExpression).makeStmt()); + if (rightBlock.isEmpty()) { + return TranslationUtils.notNullConditional(leftExpression, rightExpression, context()); + } - JsIf ifStatement = new JsIf(testExpression, rightBlock); + JsExpression result; + JsIf ifStatement; + if (BindingContextUtilPackage.isUsedAsExpression(expression, context().bindingContext())) { + if (TranslationUtils.isCacheNeeded(leftExpression)) { + TemporaryVariable resultVar = context().declareTemporary(leftExpression); + result = resultVar.reference(); + context().addStatementToCurrentBlock(resultVar.assignmentExpression().makeStmt()); + } + else { + result = leftExpression; + } + JsExpression testExpression = TranslationUtils.isNullCheck(result); + if (!JsAstUtils.isEmptyExpression(rightExpression)) { + rightBlock.getStatements().add(JsAstUtils.assignment(result, rightExpression).makeStmt()); + } + ifStatement = JsAstUtils.newJsIf(testExpression, rightBlock); + } + else { + result = context().getEmptyExpression(); + JsExpression testExpression = TranslationUtils.isNullCheck(leftExpression); + ifStatement = JsAstUtils.newJsIf(testExpression, rightBlock); + } context().addStatementToCurrentBlock(ifStatement); - - return result.reference(); + return result; } @Nullable @@ -140,6 +157,9 @@ public final class BinaryOperationTranslator extends AbstractTranslator { @NotNull private JsExpression applyIntrinsic(@NotNull BinaryOperationIntrinsic intrinsic) { JsExpression leftExpression = Translation.translateAsExpression(leftJetExpression, context()); + if (JsAstUtils.isEmptyExpression(leftExpression)) { + return leftExpression; + } JsBlock rightBlock = new JsBlock(); JsExpression rightExpression = Translation.translateAsExpression(rightJetExpression, context(), rightBlock); @@ -148,6 +168,14 @@ public final class BinaryOperationTranslator extends AbstractTranslator { return intrinsic.apply(expression, leftExpression, rightExpression, context()); } + if (JsAstUtils.isEmptyExpression(rightExpression)) { + if (TranslationUtils.isCacheNeeded(leftExpression)) { + context().addStatementToCurrentBlock(leftExpression.makeStmt()); + } + context().addStatementsToCurrentBlockFrom(rightBlock); + return context().getEmptyExpression(); + } + if (TranslationUtils.isCacheNeeded(leftExpression)) { TemporaryVariable temporaryVariable = context().declareTemporary(null); context().addStatementToCurrentBlock(JsAstUtils.assignment(temporaryVariable.reference(), leftExpression).makeStmt()); @@ -168,6 +196,10 @@ public final class BinaryOperationTranslator extends AbstractTranslator { assert OperatorConventions.NOT_OVERLOADABLE.contains(token); JsBinaryOperator operator = OperatorTable.getBinaryOperator(token); JsExpression leftExpression = Translation.translateAsExpression(leftJetExpression, context()); + if (JsAstUtils.isEmptyExpression(leftExpression)) { + return leftExpression; + } + JsBlock rightBlock = new JsBlock(); JsExpression rightExpression = Translation.translateAsExpression(rightJetExpression, context(), rightBlock); @@ -175,26 +207,34 @@ public final class BinaryOperationTranslator extends AbstractTranslator { return new JsBinaryOperation(operator, leftExpression, rightExpression); } - JsNameRef result; - if (rightExpression instanceof JsNameRef) { - result = (JsNameRef) rightExpression; // Reuse tmp variable - } else { - TemporaryVariable resultVar = context().declareTemporary(rightExpression); - result = resultVar.reference(); - rightBlock.getStatements().add(resultVar.assignmentExpression().makeStmt()); - } + assert token.equals(JetTokens.ANDAND) || token.equals(JetTokens.OROR) : "Unsupported binary operation: " + expression.getText(); + boolean isOror = token.equals(JetTokens.OROR); + JsExpression literalResult = isOror ? JsLiteral.TRUE : JsLiteral.FALSE; + leftExpression = isOror ? not(leftExpression) : leftExpression; 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); + JsExpression result; + if (BindingContextUtilPackage.isUsedAsExpression(expression, context().bindingContext())) { + if (!JsAstUtils.isEmptyExpression(rightExpression)) { + if (rightExpression instanceof JsNameRef) { + result = rightExpression; // Reuse tmp variable + } else { + TemporaryVariable resultVar = context().declareTemporary(rightExpression); + result = resultVar.reference(); + rightBlock.getStatements().add(resultVar.assignmentExpression().makeStmt()); + } + JsStatement assignmentStatement = JsAstUtils.assignment(result, literalResult).makeStmt(); + ifStatement = JsAstUtils.newJsIf(leftExpression, rightBlock, assignmentStatement); + } + else { + ifStatement = JsAstUtils.newJsIf(leftExpression, rightBlock); + result = literalResult; + } } else { - throw new UnsupportedOperationException("Unsupported binary operation: " + expression.getText()); + ifStatement = JsAstUtils.newJsIf(leftExpression, rightBlock); + result = context().getEmptyExpression(); } - context().addStatementToCurrentBlock(ifStatement); return result; } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/operation/IntrinsicAssignmentTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/operation/IntrinsicAssignmentTranslator.java index 9b2e8ae40d3..a4bb6afd170 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/operation/IntrinsicAssignmentTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/operation/IntrinsicAssignmentTranslator.java @@ -25,6 +25,7 @@ import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; import org.jetbrains.jet.lang.types.expressions.OperatorConventions; import org.jetbrains.jet.lexer.JetToken; import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.utils.JsAstUtils; import static org.jetbrains.k2js.translate.utils.PsiUtils.getOperationToken; import static org.jetbrains.k2js.translate.utils.PsiUtils.isAssignment; @@ -45,6 +46,10 @@ public final class IntrinsicAssignmentTranslator extends AssignmentTranslator { @NotNull private JsExpression translate() { + if (JsAstUtils.isEmptyExpression(right)) { + return right; + } + if (isAssignment(getOperationToken(expression))) { return translateAsPlainAssignment(); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/operation/OverloadedAssignmentTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/operation/OverloadedAssignmentTranslator.java index f2cac875a90..4e2d99196bd 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/operation/OverloadedAssignmentTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/operation/OverloadedAssignmentTranslator.java @@ -24,6 +24,7 @@ import org.jetbrains.jet.lang.resolve.calls.callUtil.CallUtilPackage; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.k2js.translate.callTranslator.CallTranslator; import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.utils.JsAstUtils; public final class OverloadedAssignmentTranslator extends AssignmentTranslator { @@ -44,6 +45,10 @@ public final class OverloadedAssignmentTranslator extends AssignmentTranslator { @NotNull private JsExpression translate() { + if (JsAstUtils.isEmptyExpression(right)) { + return right; + } + if (isVariableReassignment) { return reassignment(); } 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 afec04b8e88..7666890a580 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 @@ -30,6 +30,8 @@ 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.AnnotationsUtils; +import org.jetbrains.k2js.translate.utils.JsAstUtils; +import org.jetbrains.k2js.translate.utils.TranslationUtils; import java.util.ArrayList; import java.util.Collections; @@ -64,11 +66,17 @@ public class CallArgumentTranslator extends AbstractTranslator { private final List translateArguments; private final boolean hasSpreadOperator; private final TemporaryConstVariable cachedReceiver; + private final boolean hasEmptyExpressionArgument; public ArgumentsInfo(List arguments, boolean operator, TemporaryConstVariable receiver) { + this(arguments, operator, receiver, false); + } + + public ArgumentsInfo(List arguments, boolean operator, TemporaryConstVariable receiver, boolean hasEmptyExpressionArgument) { translateArguments = arguments; hasSpreadOperator = operator; cachedReceiver = receiver; + this.hasEmptyExpressionArgument = hasEmptyExpressionArgument; } @NotNull @@ -84,9 +92,16 @@ public class CallArgumentTranslator extends AbstractTranslator { public TemporaryConstVariable getCachedReceiver() { return cachedReceiver; } + + public boolean hasEmptyExpressionArgument() { + return hasEmptyExpressionArgument; + } } - public static void translateSingleArgument( + public static enum ArgumentsKind { HAS_EMPTY_EXPRESSION_ARGUMENT, HAS_NOT_EMPTY_EXPRESSION_ARGUMENT } + + @NotNull + public static ArgumentsKind translateSingleArgument( @NotNull ResolvedValueArgument actualArgument, @NotNull List result, @NotNull TranslationContext context, @@ -94,31 +109,41 @@ public class CallArgumentTranslator extends AbstractTranslator { ) { List valueArguments = actualArgument.getArguments(); if (actualArgument instanceof VarargValueArgument) { - translateVarargArgument(valueArguments, result, context, shouldWrapVarargInArray); + return translateVarargArgument(valueArguments, result, context, shouldWrapVarargInArray); } else if (actualArgument instanceof DefaultValueArgument) { result.add(context.namer().getUndefinedExpression()); + return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT; } else { assert actualArgument instanceof ExpressionValueArgument; assert valueArguments.size() == 1; JetExpression argumentExpression = valueArguments.get(0).getArgumentExpression(); assert argumentExpression != null; - result.add(Translation.translateAsExpression(argumentExpression, context)); + JsExpression jsExpression = Translation.translateAsExpression(argumentExpression, context); + result.add(jsExpression); + if (JsAstUtils.isEmptyExpression(jsExpression)) { + return ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT; + } + else { + return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT; + } } } - private static void translateVarargArgument( + @NotNull + private static ArgumentsKind translateVarargArgument( @NotNull List arguments, @NotNull List result, @NotNull TranslationContext context, boolean shouldWrapVarargInArray ) { + ArgumentsKind resultKind = ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT; if (arguments.isEmpty()) { if (shouldWrapVarargInArray) { result.add(new JsArrayLiteral(Collections.emptyList())); } - return; + return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT; } List list; @@ -140,10 +165,15 @@ public class CallArgumentTranslator extends AbstractTranslator { context.moveVarsFrom(argContext); argContexts.add(argContext); argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty(); + if (JsAstUtils.isEmptyExpression(argExpression)) { + resultKind = ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT; + break; + } } if (argumentsShouldBeExtractedToTmpVars) { extractArgumentsToTmpVars(list, argContexts, context); } + return resultKind; } private static void extractArgumentsToTmpVars( @@ -154,7 +184,7 @@ public class CallArgumentTranslator extends AbstractTranslator { for(int i=0; i valueParameters = resolvedCall.getResultingDescriptor().getValueParameters(); if (valueParameters.isEmpty()) { return new ArgumentsInfo(Collections.emptyList(), false, null); @@ -223,10 +254,15 @@ public class CallArgumentTranslator extends AbstractTranslator { } } TranslationContext argContext = context().innerBlock(); - translateSingleArgument(actualArgument, result, argContext, !isNativeFunctionCall && !hasSpreadOperator); + ArgumentsKind kind = translateSingleArgument(actualArgument, result, argContext, !isNativeFunctionCall && !hasSpreadOperator); context().moveVarsFrom(argContext); argContexts.add(argContext); argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty(); + + if (kind == ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT) { + hasEmptyExpressionArgument = true; + break; + } } if (argumentsShouldBeExtractedToTmpVars) { @@ -249,7 +285,7 @@ public class CallArgumentTranslator extends AbstractTranslator { } removeLastUndefinedArguments(result); - return new ArgumentsInfo(result, hasSpreadOperator, cachedReceiver); + return new ArgumentsInfo(result, hasSpreadOperator, cachedReceiver, hasEmptyExpressionArgument); } } 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 f569c4ef4e0..a361b288a59 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 @@ -60,7 +60,7 @@ public final class FunctionBodyTranslator extends AbstractTranslator { JsStatement assignStatement = assignment(jsNameRef, defaultValue).makeStmt(); JsStatement thenStatement = JsAstUtils.mergeStatementInBlockIfNeeded(assignStatement, defaultArgBlock); JsBinaryOperation checkArgIsUndefined = equality(jsNameRef, functionBodyContext.namer().getUndefinedExpression()); - JsIf jsIf = new JsIf(checkArgIsUndefined, thenStatement); + JsIf jsIf = JsAstUtils.newJsIf(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 7fee2981d14..60a2a196b7b 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,16 +21,12 @@ 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"); @@ -71,22 +67,28 @@ public final class JsAstUtils { } @NotNull - public static JsExpression convertToExpression(@NotNull JsNode jsNode, @NotNull TranslationContext context) { - return convertToExpression(jsNode, context, context.dynamicContext().jsBlock()); + private static JsStatement deBlockIfPossible(@NotNull JsStatement statement) { + if (statement instanceof JsBlock && ((JsBlock)statement).getStatements().size() == 1) { + return ((JsBlock)statement).getStatements().get(0); + } + else { + return statement; + } } @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(); - } + public static JsIf newJsIf( + @NotNull JsExpression ifExpression, + @NotNull JsStatement thenStatement, + @Nullable JsStatement elseStatement + ) { + elseStatement = elseStatement != null ? deBlockIfPossible(elseStatement) : null; + return new JsIf(ifExpression, deBlockIfPossible(thenStatement), elseStatement); + } + + @NotNull + public static JsIf newJsIf(@NotNull JsExpression ifExpression, @NotNull JsStatement thenStatement) { + return newJsIf(ifExpression, thenStatement, null); } @Nullable @@ -99,11 +101,22 @@ public final class JsAstUtils { if (block.isEmpty()) { return statement; } else { + if (isEmptyStatement(statement)) { + return deBlockIfPossible(block); + } block.getStatements().add(statement); return block; } } + public static boolean isEmptyStatement(@NotNull JsStatement statement) { + return statement instanceof JsEmpty; + } + + public static boolean isEmptyExpression(@NotNull JsExpression expression) { + return expression instanceof JsEmptyExpression; + } + @NotNull public static JsPrefixOperation negated(@NotNull JsExpression expression) { return new JsPrefixOperation(JsUnaryOperator.NOT, expression); 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 6986d508c38..03680c4e817 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 @@ -398,6 +398,7 @@ public final class TranslationUtils { public static boolean isCacheNeeded(@NotNull JsExpression expression) { return !(expression instanceof JsLiteral.JsValueLiteral) && + !JsAstUtils.isEmptyExpression(expression) && (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null); } diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/andAndWithBreakContinueReturn.kt b/js/js.translator/testData/expression/evaluationOrder/cases/andAndWithBreakContinueReturn.kt index 61fbbcc13be..18a345ac264 100644 --- a/js/js.translator/testData/expression/evaluationOrder/cases/andAndWithBreakContinueReturn.kt +++ b/js/js.translator/testData/expression/evaluationOrder/cases/andAndWithBreakContinueReturn.kt @@ -1,46 +1,62 @@ package foo +var global: String = "" + +fun id(s: String, b: Boolean): Boolean { + global += s + return b +} + fun bar(b: Boolean): String { - if (b && return "A") + if (id("A", b) && return "A") return "B" else return "C" } fun testBreak(b: Boolean, expected: Int) { + global = "" var i = 0 while (i++ < 5) { - if (i == 2) b && break + if (i == 2) id("A", b) && break } assertEquals(expected, i, "break 1") + assertEquals("A", global, "break 1") + global = "" i = 0 while (i++ < 5) { if (i == 2) { - var x = b && break + var x = id("A", b) && break } } assertEquals(expected, i, "break 2") + assertEquals("A", global, "break 2") } fun testContinue(b: Boolean, expected: Int) { + global = "" var i = 0 var n = 0 while (i++ < 5) { - if (i == 2) b && continue + if (i == 2) id("A", b) && continue n++ } assertEquals(expected, n, "continue 1") + assertEquals("A", global, "continue 1") + global = "" i = 0 n = 0 while (i++ < 5) { if (i == 2) { - var x = b && continue + var x = id("A", b) && continue + assertEquals(x, b) } n++ } assertEquals(expected, n, "continue 2") + assertEquals("A", global, "continue 2") } fun box(): String { @@ -49,11 +65,33 @@ fun box(): String { testBreak(true, 2) testBreak(false, 6) + global = "" + var i = 0 + while (i++ < 5) { + if (i == 2) break && id("A", false) + } + assertEquals(2, i, "break && false") + assertEquals("", global, "break && false") + testContinue(true, 4) testContinue(false, 5) - assertEquals("A", bar(true)) + global = "" + i = 0 + var n = 0 + while (i++ < 5) { + if (i == 2) continue && id("A", false) + n++ + } + assertEquals(4, n, "continue && false") + assertEquals("", global, "continue && false") + + assertEquals("A", bar(true), "bar") + assertEquals("A", global, "bar") + + global = "" assertEquals("C", bar(false)) + assertEquals("A", global, "bar") return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/callWithBreakContinueReturn.kt b/js/js.translator/testData/expression/evaluationOrder/cases/callWithBreakContinueReturn.kt new file mode 100644 index 00000000000..70a1d7bc03e --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/callWithBreakContinueReturn.kt @@ -0,0 +1,88 @@ +package foo + +var global: String = "" + +fun f(arg1: String, arg2: String, arg3: String): String { + global += ":f:" + return arg1 + arg2 + arg3 +} + +fun id(s: String): String { + global += s + return s +} + +fun bar(s: String): String { + return f(id(s), if (true) return ":bar:" else return "b", id(s)) +} + +fun box(): String { + var b: Boolean + + var i = 0 + while (i++ < 5) { + if (i == 2) f(id("A"), break, id("B")) + } + assertEquals(2, i, "break 1") + assertEquals("A", global, "break 1") + + global = "" + i = 0 + while (i++ < 5) { + if (i == 2) { + var x = f(id("A"), break, id("B")) + } + } + assertEquals(2, i, "break 2") + assertEquals("A", global, "break 2") + + global = "" + i = 0 + while (i++ < 5) { + if (i == 2) { + var x = "B" + f(id("A"), break, id("B")) + } + } + assertEquals(2, i, "break 3") + assertEquals("A", global, "break 3") + + global = "" + i = 0 + var n = 0 + while (i++ < 5) { + if (i == 2) f(id("A"), continue, id("B")) + n++ + } + assertEquals(4, n, "continue 1") + assertEquals("A", global, "continue 1") + + global = "" + i = 0 + n = 0 + while (i++ < 5) { + if (i == 2) { + var x = f(id("A"), continue, id("B")) + } + n++ + } + assertEquals(4, n, "continue 2") + assertEquals("A", global, "continue 2") + + global = "" + i = 0 + n = 0 + while (i++ < 5) { + if (i == 2) { + var x = "B" + f(id("A"), continue, id("B")) + } + n++ + } + assertEquals(4, n, "continue 3") + assertEquals("A", global, "continue 3") + + global = "" + assertEquals(":bar:", bar("A")) + assertEquals("A", global, "bar") + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/elvisWithBreakContinueReturn.kt b/js/js.translator/testData/expression/evaluationOrder/cases/elvisWithBreakContinueReturn.kt index d4dc8a5cfa1..1ceb0be0e69 100644 --- a/js/js.translator/testData/expression/evaluationOrder/cases/elvisWithBreakContinueReturn.kt +++ b/js/js.translator/testData/expression/evaluationOrder/cases/elvisWithBreakContinueReturn.kt @@ -11,14 +11,14 @@ fun bar(a: A?): String { fun testBreak(a: A?, expected: Int) { var i = 0 - while (i++ < 5) { - if (i == 2) a ?: break + while(i++<5) { + if (i==2) a ?: break } assertEquals(expected, i, "break 1") i = 0 - while (i++ < 5) { - if (i == 2) { + while(i++<5) { + if (i==2) { var x = a ?: break } } @@ -28,16 +28,16 @@ fun testBreak(a: A?, expected: Int) { fun testContinue(a: A?, expected: Int) { var i = 0 var n = 0 - while (i++ < 5) { - if (i == 2) a ?: continue + while(i++<5) { + if (i==2) a ?: continue n++ } assertEquals(expected, n) i = 0 n = 0 - while (i++ < 5) { - if (i == 2) { + while(i++<5) { + if (i==2) { var x = a ?: continue } n++ @@ -50,9 +50,23 @@ fun box(): String { testBreak(null, 2) testBreak(A(), 6) + var i = 0 + while(i++<5) { + if (i==2) break ?: null + } + assertEquals(2, i, "break ?: null") + testContinue(null, 4) testContinue(A(), 5) + i = 0 + var n = 0 + while(i++<5) { + if (i==2) continue ?: null + n++ + } + assertEquals(4, n, "continue ?: null") + assertEquals("A", bar(null)) assertEquals("B", bar(A())) diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicWithBreakContinueReturn.kt b/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicWithBreakContinueReturn.kt new file mode 100644 index 00000000000..7c3218de983 --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/intrinsicWithBreakContinueReturn.kt @@ -0,0 +1,119 @@ +package foo + +var global: String = "" + +class A { + var prop: Boolean = false +} + +fun getA(): A = try { global += "getA"; A() } finally {} + +fun barEq(): String { + 10 == return "eq" +} + +fun barLt(): String { + 10 < (return "lt") as Int +} + +fun box(): String { + var b: Boolean + + var i = 0 + while(i++<5) { + if (i==2) 10 == break + } + assertEquals(2, i, "break 1") + + i = 0 + while(i++<5) { + if (i==2) 10 < (break as Int) + } + assertEquals(2, i, "break 2") + + i = 0 + while(i++<5) { + if (i==2) { + var x = 10 == break; + } + } + assertEquals(2, i, "break 3") + + i = 0 + while(i++<5) { + if (i==2) { + var x = 10 < (break as Int) + } + } + assertEquals(2, i, "break 4") + + i = 0 + var bVar: Boolean + while(i++<5) { + if (i==2) { + bVar = 10 < (break as Int) + } + } + assertEquals(2, i, "break 5") + + i = 0 + var bVarArray = array(true, false) + while(i++<5) { + if (i==2) { + bVarArray[try { global += "A"; 0} finally {}] = 10 < (break as Int) + } + } + assertEquals(2, i, "break 6") + assertEquals("A", global, "break 6") + + i = 0 + global = "" + while(i++<5) { + if (i==2) { + getA().prop = 10 < (break as Int) + } + } + assertEquals(2, i, "break 7") + assertEquals("getA", global, "break 7") + + i = 0 + var n = 0 + while(i++<5) { + if (i==2) 10 == continue + n++ + } + assertEquals(4, n, "continue 1") + + i = 0 + n = 0 + while(i++<5) { + if (i==2) 10 < (continue as Int) + n++ + } + assertEquals(4, n, "continue 2") + + i = 0 + n = 0 + while(i++<5) { + if (i==2) { + var x = 10 == continue + } + n++ + } + assertEquals(4, n, "continue 3") + + i = 0 + n = 0 + while(i++<5) { + if (i==2) { + var x = 10 < (continue as Int) + } + n++ + } + assertEquals(4, n, "continue 4") + + assertEquals("eq", barEq()) + assertEquals("lt", barLt()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/loopWithBreakContinueReturnInCondition.kt b/js/js.translator/testData/expression/evaluationOrder/cases/loopWithBreakContinueReturnInCondition.kt new file mode 100644 index 00000000000..05b2faa568a --- /dev/null +++ b/js/js.translator/testData/expression/evaluationOrder/cases/loopWithBreakContinueReturnInCondition.kt @@ -0,0 +1,98 @@ +package foo + +var global: String = "" + + +fun whileReturn(): String { + var i = 0 + while(if (i<2) true else { global += ":return:"; return ":whileReturn:"}) { + i++ + global += "A" + } + return ":whileReturn:after:" +} + +fun whileImmediateReturn(): String { + var i = 0 + while(return ":whileImmediateReturn:") { + global += "A" + } + return ":whileImmediateReturn:after" +} + +fun doWhileReturn(): String { + var i = 0 + do { + i++ + global += "A" + } + while(if (i<2) true else { global += ":return:"; return ":doWhileReturn:"}) + return ":doWhileReturn:after:" +} + +fun doWhileReturnFromCondition(): String { + var i = 0 + do { + global += "A" + } while(return ":doWhileReturnFromCondition:") + return ":doWhileReturnFromCondition:after" +} + +fun doWhileImmediateReturn(): String { + var i = 0 + do { + return ":doWhileImmediateReturn:" + global += "A" + } while(false) + return ":doWhileImmediateReturn:after" +} + +fun forReturn(b: Boolean): String { + var i = 0 + for(i in (if (b) 1..2 else { global += ":return:"; return ":forReturn:"})) { + global += "A" + } + return ":forReturn:after:" +} + +fun box(): String { + var i = 0 + + // KT-5704 'while' detected as unreachable though loop is running + // TODO Remove or uncomment when issue will be fixed + //while(if (++i==10) break else continue) {} + //assertEquals(10, i) + + i = 0 + do { i++ } while(if (++i==10) break else continue) + assertEquals(10, i) + + assertEquals(":whileReturn:", whileReturn()) + assertEquals("AA:return:", global) + + global = "" + assertEquals(":whileImmediateReturn:", whileImmediateReturn()) + assertEquals("", global) + + global = "" + assertEquals(":doWhileReturn:", doWhileReturn()) + assertEquals("AA:return:", global) + + global = "" + assertEquals(":doWhileReturnFromCondition:", doWhileReturnFromCondition()) + assertEquals("A", global) + + global = "" + assertEquals(":doWhileImmediateReturn:", doWhileImmediateReturn()) + assertEquals("", global) + + global = "" + assertEquals(":forReturn:after:", forReturn(true)) + assertEquals("AA", global) + + global = "" + assertEquals(":forReturn:", forReturn(false)) + assertEquals(":return:", global) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithBreakContinueReturn.kt b/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithBreakContinueReturn.kt index 877b7e42dad..04ca9a67b7a 100644 --- a/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithBreakContinueReturn.kt +++ b/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithBreakContinueReturn.kt @@ -1,46 +1,61 @@ package foo +var global: String = "" + +fun id(s: String, b: Boolean): Boolean { + global += s + return b +} + fun bar(b: Boolean): String { - if (b || return "A") + if (id("A",b) || return "A") return "B" else return "C" } fun testBreak(b: Boolean, expected: Int) { + global = "" var i = 0 while (i++ < 5) { - if (i == 2) b || break + if (i == 2) id("A", b) || break } assertEquals(expected, i, "break 1") + assertEquals("A", global, "break 1") + global = "" i = 0 while (i++ < 5) { if (i == 2) { - var x = b || break + var x = id("A", b) || break } } assertEquals(expected, i, "break 2") + assertEquals("A", global, "break 2") } fun testContinue(b: Boolean, expected: Int) { + global = "" var i = 0 var n = 0 while (i++ < 5) { - if (i == 2) b || continue + if (i == 2) id("A", b) || continue n++ } assertEquals(expected, n, "continue 1") + assertEquals("A", global, "continue 1") + global = "" i = 0 n = 0 while (i++ < 5) { if (i == 2) { - var x = b || continue + var x = id("A", b) || continue } n++ } assertEquals(expected, n, "continue 2") + assertEquals("A", global, "continue 2") } fun box(): String { @@ -48,11 +63,32 @@ fun box(): String { testBreak(true, 6) testBreak(false, 2) + global = "" + var i = 0 + while (i++ < 5) { + if (i == 2) break || id("A", true) + } + assertEquals(2, i, "break || true") + assertEquals("", global, "break || false") + testContinue(true, 5) testContinue(false, 4) + global = "" + i = 0 + var n = 0 + while (i++ < 5) { + if (i == 2) continue || id("A", true) + n++ + } + assertEquals(4, n, "continue || true") + assertEquals("", global, "continue || true") + assertEquals("B", bar(true)) + assertEquals("A", global, "bar") + global = "" assertEquals("A", bar(false)) + assertEquals("A", global, "bar") 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 index f2194e0c3d1..6528e3deadd 100644 --- a/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithSideEffect.kt +++ b/js/js.translator/testData/expression/evaluationOrder/cases/orOrWithSideEffect.kt @@ -11,6 +11,9 @@ fun box(): String { val a = if (true || if(bar("A", false)) {2} else {3} == 0) { } assertEquals("", global) + true || if(bar("A", false)) {2} else {3} == 0 + assertEquals("", global) + // Simple || Simple global = "" assertEquals(true, bar("A", true) || bar("B", true)) diff --git a/js/js.translator/testData/expression/for/cases/forWithWhile.kt b/js/js.translator/testData/expression/for/cases/labeledForWithWhile.kt similarity index 100% rename from js/js.translator/testData/expression/for/cases/forWithWhile.kt rename to js/js.translator/testData/expression/for/cases/labeledForWithWhile.kt