From c0111d6e33a832b335fbe3a85e1203a59e363a46 Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Fri, 10 Apr 2015 18:13:29 +0300 Subject: [PATCH] JS: decomposition of binary operator #KT-6437 fixed #KT-7359 fixed --- .../kotlin/js/inline/ExpressionDecomposer.kt | 52 +++++++++++++++++++ .../kotlin/js/translate/utils/astUtils.kt | 15 ++++++ .../inlineEvaluationOrder/cases/assignment.kt | 14 +++++ .../cases/binaryOperator.kt | 8 +++ .../binaryOperatorMultipleInlineCalls.kt | 8 +++ .../inlineEvaluationOrder/cases/logicalAnd.kt | 14 +++++ .../cases/logicalAndOrMultipleInlineCalls.kt | 8 +++ .../inlineEvaluationOrder/cases/logicalOr.kt | 14 +++++ .../cases/propertiesInitializationOrder.kt | 20 +++++++ .../propertiesInitializationOrderSimple.kt | 17 ++++++ 10 files changed, 170 insertions(+) create mode 100644 js/js.translator/testData/inlineEvaluationOrder/cases/assignment.kt create mode 100644 js/js.translator/testData/inlineEvaluationOrder/cases/binaryOperator.kt create mode 100644 js/js.translator/testData/inlineEvaluationOrder/cases/binaryOperatorMultipleInlineCalls.kt create mode 100644 js/js.translator/testData/inlineEvaluationOrder/cases/logicalAnd.kt create mode 100644 js/js.translator/testData/inlineEvaluationOrder/cases/logicalAndOrMultipleInlineCalls.kt create mode 100644 js/js.translator/testData/inlineEvaluationOrder/cases/logicalOr.kt create mode 100644 js/js.translator/testData/inlineEvaluationOrder/cases/propertiesInitializationOrder.kt create mode 100644 js/js.translator/testData/inlineEvaluationOrder/cases/propertiesInitializationOrderSimple.kt diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt index 3e47c3d3eee..9a3f3058f46 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt @@ -76,6 +76,58 @@ class ExpressionDecomposer private ( } } + + // TODO: comma operator? + override fun visit(x: JsBinaryOperation, ctx: JsContext<*>): Boolean { + x.arg1 = accept(x.arg1) + + when (x.operator) { + JsBinaryOperator.AND, + JsBinaryOperator.OR -> x.processOrAnd(ctx) + else -> x.process() + } + + return false + } + + private fun JsBinaryOperation.processOrAnd(ctx: JsContext<*>) { + if (arg2 !in containsExtractable) return + + val tmp = Temporary(arg1) + addStatement(tmp.variable) + var test = if (operator == JsBinaryOperator.OR) not(tmp.nameRef) else tmp.nameRef + val arg2Eval = withNewAdditionalStatements { + arg2 = accept(arg2) + addStatement(tmp.assign(arg2)) + additionalStatements.toStatement() + } + + addStatement(JsIf(test, arg2Eval)) + ctx.replaceMe(tmp.nameRef) + } + + private fun JsBinaryOperation.process() { + if (arg1 !in containsNodeWithSideEffect || arg2 !in containsExtractable) { + // If arg1 does not have side effect, but arg2 contains extractable, + // we should extract from arg2 anyway. + // If arg2 does not contain extractable, it's still ok to visit. + arg2 = accept(arg2) + return + } + + if (operator.isAssignment()) { + // Must be (someThingWithSideEffect).x = arg2, because arg1 can have side effect + assert(arg1 is JsNameRef) { "Valid JavaScript left-hand side must be JsNameRef, got: $this" } + val arg1AsRef = arg1 as JsNameRef + arg1AsRef.qualifier = arg1AsRef.qualifier!!.extractToTemporary() + } + else { + arg1 = arg1.extractToTemporary() + } + + arg2 = accept(arg2) + } + override fun visit(x: JsArrayLiteral, ctx: JsContext<*>): Boolean { val elements = x.getExpressions() processByIndices(elements, elements.indicesOfExtractable) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/astUtils.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/astUtils.kt index 222d0768dbb..9687796d6b2 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/astUtils.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/astUtils.kt @@ -56,6 +56,10 @@ public fun JsNode.any(predicate: (JsNode) -> Boolean): Boolean { return visitor.matched } +public var JsNameRef.qualifier: JsExpression? + get() = getQualifier() + set(value) = setQualifier(value) + public var JsArrayAccess.index: JsExpression get() = getIndexExpression() set(value) = setIndexExpression(value) @@ -75,3 +79,14 @@ public var JsConditional.then: JsExpression public var JsConditional.otherwise: JsExpression get() = getElseExpression() set(value) = setElseExpression(value) + +public var JsBinaryOperation.arg1: JsExpression + get() = getArg1() + set(value) = setArg1(value) + +public var JsBinaryOperation.arg2: JsExpression + get() = getArg2() + set(value) = setArg2(value) + +public val JsBinaryOperation.operator: JsBinaryOperator + get() = getOperator() \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/assignment.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/assignment.kt new file mode 100644 index 00000000000..d7a30bfe665 --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/assignment.kt @@ -0,0 +1,14 @@ +package foo + +class A(var x: Int) { + override fun toString(): String = "A($x)" +} + +fun box(): String { + val a = A(10) + fizz(a).x = buzz(20) + assertEquals(20, a.x) + assertEquals("fizz(A(10));buzz(20);", pullLog()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/binaryOperator.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/binaryOperator.kt new file mode 100644 index 00000000000..675399fed3d --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/binaryOperator.kt @@ -0,0 +1,8 @@ +package foo + +fun box(): String { + assertEquals(3, fizz(1) + buzz(2)) + assertEquals("fizz(1);buzz(2);", pullLog()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/binaryOperatorMultipleInlineCalls.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/binaryOperatorMultipleInlineCalls.kt new file mode 100644 index 00000000000..7d868c73925 --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/binaryOperatorMultipleInlineCalls.kt @@ -0,0 +1,8 @@ +package foo + +fun box(): String { + assertEquals(10, (fizz(1) + buzz(2)) + (fizz(3) + buzz(4))) + assertEquals("fizz(1);buzz(2);fizz(3);buzz(4);", pullLog()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/logicalAnd.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/logicalAnd.kt new file mode 100644 index 00000000000..63b393a946f --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/logicalAnd.kt @@ -0,0 +1,14 @@ +package foo + +fun box(): String { + assertEquals(false, fizz(false) && buzz(false)) + assertEquals("fizz(false);", pullLog()) + + assertEquals(false, fizz(true) && buzz(false)) + assertEquals("fizz(true);buzz(false);", pullLog()) + + assertEquals(true, fizz(true) && buzz(true)) + assertEquals("fizz(true);buzz(true);", pullLog()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/logicalAndOrMultipleInlineCalls.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/logicalAndOrMultipleInlineCalls.kt new file mode 100644 index 00000000000..c40f1ad876b --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/logicalAndOrMultipleInlineCalls.kt @@ -0,0 +1,8 @@ +package foo + +fun box(): String { + assertEquals(true, fizz(true) || buzz(true) && (fizz(false) || buzz(true))) + assertEquals("fizz(true);", pullLog()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/logicalOr.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/logicalOr.kt new file mode 100644 index 00000000000..6b3f42f10a4 --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/logicalOr.kt @@ -0,0 +1,14 @@ +package foo + +fun box(): String { + assertEquals(true, fizz(true) || buzz(false)) + assertEquals("fizz(true);", pullLog()) + + assertEquals(true, fizz(false) || buzz(true)) + assertEquals("fizz(false);buzz(true);", pullLog()) + + assertEquals(false, fizz(false) || buzz(false)) + assertEquals("fizz(false);buzz(false);", pullLog()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/propertiesInitializationOrder.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/propertiesInitializationOrder.kt new file mode 100644 index 00000000000..97d0a39933a --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/propertiesInitializationOrder.kt @@ -0,0 +1,20 @@ +package foo + +class A(val x: Int = fizz(1) + 1) { + val y = buzz(x) + 1 + val z: Int + + init { + z = fizz(x) + buzz(y) + } +} + +fun box(): String { + val a = A() + assertEquals(2, a.x) + assertEquals(3, a.y) + assertEquals(5, a.z) + assertEquals("fizz(1);buzz(2);fizz(2);buzz(3);", pullLog()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineEvaluationOrder/cases/propertiesInitializationOrderSimple.kt b/js/js.translator/testData/inlineEvaluationOrder/cases/propertiesInitializationOrderSimple.kt new file mode 100644 index 00000000000..30e40fd6adb --- /dev/null +++ b/js/js.translator/testData/inlineEvaluationOrder/cases/propertiesInitializationOrderSimple.kt @@ -0,0 +1,17 @@ +package foo + +class A { + val x: Int + + init { + x = fizz(1) + buzz(2) + } +} + +fun box(): String { + val a = A() + assertEquals(3, a.x) + assertEquals("fizz(1);buzz(2);", pullLog()) + + return "OK" +} \ No newline at end of file