From e9d5d8f0feff92391ddc6ce7ab2cec5f9d8fd1b5 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 3 Feb 2016 16:23:52 +0300 Subject: [PATCH] [KT-7683] Implement translation of 'when .. in' clause to JS --- .../kotlin/js/test/semantics/WhenTest.java | 20 ++++ .../translate/expression/WhenTranslator.java | 35 ++++-- .../operation/InOperationTranslator.java | 101 ++++++++++++++++++ .../kotlin/js/translate/utils/JsAstUtils.java | 5 + .../cases/whenStatementWithRangeClause.kt | 16 +++ .../when/cases/whenWithCharRangeClause.kt | 14 +++ .../when/cases/whenWithCustomRangeClause.kt | 24 +++++ .../when/cases/whenWithLongRangeClause.kt | 21 ++++ .../when/cases/whenWithRangeClause.kt | 23 ++++ 9 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/InOperationTranslator.java create mode 100644 js/js.translator/testData/expression/when/cases/whenStatementWithRangeClause.kt create mode 100644 js/js.translator/testData/expression/when/cases/whenWithCharRangeClause.kt create mode 100644 js/js.translator/testData/expression/when/cases/whenWithCustomRangeClause.kt create mode 100644 js/js.translator/testData/expression/when/cases/whenWithLongRangeClause.kt create mode 100644 js/js.translator/testData/expression/when/cases/whenWithRangeClause.kt diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/WhenTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/WhenTest.java index a6d76fc34cc..82af76b750b 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/WhenTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/WhenTest.java @@ -113,4 +113,24 @@ public final class WhenTest extends AbstractExpressionTest { public void testIfInWhenDanglingElseIssue() throws Exception { checkFooBoxIsOk(); } + + public void testWhenWithRangeClause() throws Exception { + checkFooBoxIsOk(); + } + + public void testWhenWithLongRangeClause() throws Exception { + checkFooBoxIsOk(); + } + + public void testWhenWithCharRangeClause() throws Exception { + checkFooBoxIsOk(); + } + + public void testWhenWithCustomRangeClause() throws Exception { + checkFooBoxIsOk(); + } + + public void testWhenStatementWithRangeClause() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java index 52cc92ef0a0..11a4b05dbbf 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java @@ -23,9 +23,14 @@ import org.jetbrains.kotlin.js.translate.context.TemporaryVariable; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.general.AbstractTranslator; import org.jetbrains.kotlin.js.translate.general.Translation; +import org.jetbrains.kotlin.js.translate.operation.InOperationTranslator; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.TranslationUtils; import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; + +import java.util.HashMap; +import java.util.Map; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.negated; @@ -144,14 +149,6 @@ public final class WhenTranslator extends AbstractTranslator { @NotNull private JsExpression translateCondition(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) { - if ((condition instanceof KtWhenConditionIsPattern) || (condition instanceof KtWhenConditionWithExpression)) { - return translatePatternCondition(condition, context); - } - throw new AssertionError("Unsupported when condition " + condition.getClass()); - } - - @NotNull - private JsExpression translatePatternCondition(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) { JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition, context); if (isNegated(condition)) { return negated(patternMatchExpression); @@ -167,7 +164,10 @@ public final class WhenTranslator extends AbstractTranslator { else if (condition instanceof KtWhenConditionWithExpression) { return translateExpressionCondition((KtWhenConditionWithExpression) condition, context); } - throw new AssertionError("Wrong type of JetWhenCondition"); + else if (condition instanceof KtWhenConditionInRange) { + return translateRangeCondition((KtWhenConditionInRange) condition, context); + } + throw new AssertionError("Unsupported when condition " + condition.getClass()); } @NotNull @@ -195,6 +195,23 @@ public final class WhenTranslator extends AbstractTranslator { } } + @NotNull + private JsExpression translateRangeCondition(@NotNull KtWhenConditionInRange condition, @NotNull TranslationContext context) { + KtExpression patternExpression = condition.getRangeExpression(); + assert patternExpression != null : "Expression pattern should have an expression: " + + PsiUtilsKt.getTextWithLocation(condition); + + JsExpression expressionToMatch = getExpressionToMatch(); + assert expressionToMatch != null : "Range pattern is only available for 'when (C) { in ... }' expressions: " + + PsiUtilsKt.getTextWithLocation(condition); + + Map subjectAliases = new HashMap(); + subjectAliases.put(whenExpression.getSubjectExpression(), expressionToMatch); + TranslationContext callContext = context.innerContextWithAliasesForExpressions(subjectAliases); + return new InOperationTranslator(callContext, expressionToMatch, condition.getRangeExpression(), condition.getOperationReference()) + .translate(); + } + @Nullable private JsExpression getExpressionToMatch() { return expressionToMatch; diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/InOperationTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/InOperationTranslator.java new file mode 100644 index 00000000000..37ff9775cd7 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/InOperationTranslator.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2016 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.kotlin.js.translate.operation; + +import com.google.dart.compiler.backend.js.ast.JsExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.descriptors.CallableDescriptor; +import org.jetbrains.kotlin.descriptors.FunctionDescriptor; +import org.jetbrains.kotlin.js.patterns.DescriptorPredicate; +import org.jetbrains.kotlin.js.patterns.PatternBuilder; +import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator; +import org.jetbrains.kotlin.js.translate.context.TranslationContext; +import org.jetbrains.kotlin.js.translate.general.AbstractTranslator; +import org.jetbrains.kotlin.js.translate.general.Translation; +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; +import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; +import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; +import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver; + +/** + * Translates 'A in B' expression applying specialization if possible + */ +public class InOperationTranslator extends AbstractTranslator { + private static final DescriptorPredicate INT_SPECIALIZATION_TEST = PatternBuilder.pattern("ranges.IntRange.contains"); + private static final DescriptorPredicate INT_RANGE_TEST = PatternBuilder.pattern("Int.rangeTo"); + private final JsExpression left; + private final KtExpression right; + private final KtSimpleNameExpression operation; + + public InOperationTranslator(@NotNull TranslationContext context, @NotNull JsExpression left, @NotNull KtExpression right, + @NotNull KtSimpleNameExpression operation) { + super(context); + this.left = left; + this.right = right; + this.operation = operation; + } + + @NotNull + public JsExpression translate() { + ResolvedCall call = CallUtilKt.getFunctionResolvedCallWithAssert(operation, bindingContext()); + if (INT_SPECIALIZATION_TEST.apply(call.getResultingDescriptor())) { + JsExpression candidate = translateInt(); + if (candidate != null) { + return candidate; + } + } + JsExpression rightTranslated = Translation.translateAsExpression(right, context()); + return translateGeneral(call, rightTranslated); + } + + @NotNull + private JsExpression translateGeneral(@NotNull ResolvedCall call, @NotNull JsExpression rightTranslated) { + return CallTranslator.translate(context(), call, rightTranslated); + } + + @Nullable + private JsExpression translateInt() { + ResolvedCall rightCall = CallUtilKt.getResolvedCallWithAssert(right, bindingContext()); + if (!(rightCall.getResultingDescriptor() instanceof FunctionDescriptor)) { + return null; + } + FunctionDescriptor callDescriptor = (FunctionDescriptor) rightCall.getResultingDescriptor(); + if (!INT_RANGE_TEST.apply(callDescriptor)) { + return null; + } + if (!(rightCall.getDispatchReceiver() instanceof ExpressionReceiver)) { + return null; + } + + KtExpression lower = ((ExpressionReceiver) rightCall.getDispatchReceiver()).getExpression(); + KtExpression upper = rightCall.getCall().getValueArguments().get(0).getArgumentExpression(); + assert upper != null : "Parse error occurred: " + PsiUtilsKt.getTextWithLocation(right); + return translateInt(lower, upper); + } + + @NotNull + private JsExpression translateInt(@NotNull KtExpression lowerExpression, @NotNull KtExpression upperExpression) { + JsExpression lower = Translation.translateAsExpression(lowerExpression, context()); + JsExpression upper = Translation.translateAsExpression(upperExpression, context()); + JsExpression first = JsAstUtils.greaterThanEq(left, lower); + JsExpression second = JsAstUtils.lessThanEq(left, upper); + return JsAstUtils.and(first, second); + } +} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java index 38021e082c5..509ea2a8aca 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java @@ -289,6 +289,11 @@ public final class JsAstUtils { return new JsBinaryOperation(JsBinaryOperator.GT, arg1, arg2); } + @NotNull + public static JsBinaryOperation greaterThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) { + return new JsBinaryOperation(JsBinaryOperator.GTE, arg1, arg2); + } + @NotNull public static JsExpression assignment(@NotNull JsExpression left, @NotNull JsExpression right) { return new JsBinaryOperation(JsBinaryOperator.ASG, left, right); diff --git a/js/js.translator/testData/expression/when/cases/whenStatementWithRangeClause.kt b/js/js.translator/testData/expression/when/cases/whenStatementWithRangeClause.kt new file mode 100644 index 00000000000..96b2798eae9 --- /dev/null +++ b/js/js.translator/testData/expression/when/cases/whenStatementWithRangeClause.kt @@ -0,0 +1,16 @@ +// see KT-7683 +// WhenTranslator must recognize KtWhenConditionInRange for when statement +package foo + +fun box(): String { + var result = testFun(-1) + testFun(5) + testFun(50) + testFun(150) + return if (result == "[miss][hit1][miss][hit2]") "OK" else "fail" +} +fun testFun(index: Int): String { + var r = "[miss]" + when (index) { + in 0..9 -> r = "[hit1]" + in 100..200 -> r = "[hit2]" + } + return r; +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/when/cases/whenWithCharRangeClause.kt b/js/js.translator/testData/expression/when/cases/whenWithCharRangeClause.kt new file mode 100644 index 00000000000..da284994e79 --- /dev/null +++ b/js/js.translator/testData/expression/when/cases/whenWithCharRangeClause.kt @@ -0,0 +1,14 @@ +// see KT-7683 +// WhenTranslator must recognize KtWhenConditionInRange +package foo + +fun box(): String { + var result = testFun('1') + testFun('Q') + testFun('z') + return if (result == "misshitmiss") "OK" else "fail" +} +fun testFun(index: Char): String { + return when (index) { + in 'A'..'Z' -> "hit" + else -> "miss" + } +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/when/cases/whenWithCustomRangeClause.kt b/js/js.translator/testData/expression/when/cases/whenWithCustomRangeClause.kt new file mode 100644 index 00000000000..ff8ce1a2b07 --- /dev/null +++ b/js/js.translator/testData/expression/when/cases/whenWithCustomRangeClause.kt @@ -0,0 +1,24 @@ +// see KT-7683 +// WhenTranslator must recognize KtWhenConditionInRange for custom classes that implement ClosedRange +package foo + +fun box(): String { + var result = testFun(-1) + testFun(0) + testFun(5) + testFun(9) + testFun(10) + testFun(150) + return if (result == "misshithithitmisshit!") "OK" else "fail" +} +fun testFun(index: Int): String { + var lower = Wrapper(0) + var upper = Wrapper(9) + var secondRange = Wrapper(100)..Wrapper(200) + return when (Wrapper(index)) { + in lower..upper -> "hit" + in secondRange -> "hit!" + else -> "miss" + } +} + +class Wrapper(val value: Int) : Comparable { + operator fun rangeTo(upper: Wrapper) = WrapperRange(this, upper) + override operator fun compareTo(other: Wrapper) = value.compareTo(other.value) +} +class WrapperRange(override val start: Wrapper, override val endInclusive: Wrapper) : ClosedRange diff --git a/js/js.translator/testData/expression/when/cases/whenWithLongRangeClause.kt b/js/js.translator/testData/expression/when/cases/whenWithLongRangeClause.kt new file mode 100644 index 00000000000..a6208a94a24 --- /dev/null +++ b/js/js.translator/testData/expression/when/cases/whenWithLongRangeClause.kt @@ -0,0 +1,21 @@ +// see KT-7683 +// WhenTranslator must recognize KtWhenConditionInRange in general case of a class that has rangeTo method +package foo + +fun box(): String { + var result = testFun(-1) + testFun(0) + testFun(5) + testFun(9) + testFun(10) + testFun(150) + testFun2(50) + testFun2(5) + return if (result == "misshithithitmisshit![miss][hit]") "OK" else "fail" +} +fun testFun(index: Long): String { + return when (index) { + in 0..9 -> "hit" + in 100L..200L -> "hit!" + else -> "miss" + } +} +fun testFun2(index: Int): String { + return when (index) { + in 0L..9L -> "[hit]" + else -> "[miss]" + } +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/when/cases/whenWithRangeClause.kt b/js/js.translator/testData/expression/when/cases/whenWithRangeClause.kt new file mode 100644 index 00000000000..9aacd416fd3 --- /dev/null +++ b/js/js.translator/testData/expression/when/cases/whenWithRangeClause.kt @@ -0,0 +1,23 @@ +// see KT-7683 +// WhenTranslator must recognize KtWhenConditionInRange and produce faster code when matched expression is Int +package foo + +fun box(): String { + var result = testFun(-1) + testFun(0) + testFun(5) + testFun(9) + testFun(10) + testFun(150) + testFun(800) + return if (result == "misshithithitmisshit!@@@" && invocationCount == 7) "OK" else "fail" +} +fun testFun(index: Int): String { + val thirdRange = 500..1000 + return when (get(index)) { + in 0..9 -> "hit" + in 100.rangeTo(200) -> "hit!" + in thirdRange -> "@@@" + else -> "miss" + } +} + +fun get(value: Int): Int { + invocationCount++ + return value +} +var invocationCount = 0