[KT-7683] Implement translation of 'when .. in' clause to JS
This commit is contained in:
@@ -113,4 +113,24 @@ public final class WhenTest extends AbstractExpressionTest {
|
|||||||
public void testIfInWhenDanglingElseIssue() throws Exception {
|
public void testIfInWhenDanglingElseIssue() throws Exception {
|
||||||
checkFooBoxIsOk();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-9
@@ -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.context.TranslationContext;
|
||||||
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
|
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
|
||||||
import org.jetbrains.kotlin.js.translate.general.Translation;
|
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.JsAstUtils;
|
||||||
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
|
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
|
||||||
import org.jetbrains.kotlin.psi.*;
|
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;
|
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.negated;
|
||||||
|
|
||||||
@@ -144,14 +149,6 @@ public final class WhenTranslator extends AbstractTranslator {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private JsExpression translateCondition(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
|
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);
|
JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition, context);
|
||||||
if (isNegated(condition)) {
|
if (isNegated(condition)) {
|
||||||
return negated(patternMatchExpression);
|
return negated(patternMatchExpression);
|
||||||
@@ -167,7 +164,10 @@ public final class WhenTranslator extends AbstractTranslator {
|
|||||||
else if (condition instanceof KtWhenConditionWithExpression) {
|
else if (condition instanceof KtWhenConditionWithExpression) {
|
||||||
return translateExpressionCondition((KtWhenConditionWithExpression) condition, context);
|
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
|
@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<KtExpression, JsExpression> subjectAliases = new HashMap<KtExpression, JsExpression>();
|
||||||
|
subjectAliases.put(whenExpression.getSubjectExpression(), expressionToMatch);
|
||||||
|
TranslationContext callContext = context.innerContextWithAliasesForExpressions(subjectAliases);
|
||||||
|
return new InOperationTranslator(callContext, expressionToMatch, condition.getRangeExpression(), condition.getOperationReference())
|
||||||
|
.translate();
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JsExpression getExpressionToMatch() {
|
private JsExpression getExpressionToMatch() {
|
||||||
return expressionToMatch;
|
return expressionToMatch;
|
||||||
|
|||||||
+101
@@ -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<? extends FunctionDescriptor> 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<? extends FunctionDescriptor> call, @NotNull JsExpression rightTranslated) {
|
||||||
|
return CallTranslator.translate(context(), call, rightTranslated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private JsExpression translateInt() {
|
||||||
|
ResolvedCall<? extends CallableDescriptor> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -289,6 +289,11 @@ public final class JsAstUtils {
|
|||||||
return new JsBinaryOperation(JsBinaryOperator.GT, arg1, arg2);
|
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
|
@NotNull
|
||||||
public static JsExpression assignment(@NotNull JsExpression left, @NotNull JsExpression right) {
|
public static JsExpression assignment(@NotNull JsExpression left, @NotNull JsExpression right) {
|
||||||
return new JsBinaryOperation(JsBinaryOperator.ASG, left, right);
|
return new JsBinaryOperation(JsBinaryOperator.ASG, left, right);
|
||||||
|
|||||||
+16
@@ -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;
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Wrapper> {
|
||||||
|
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<Wrapper>
|
||||||
@@ -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]"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user