[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 {
|
||||
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.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<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
|
||||
private JsExpression getExpressionToMatch() {
|
||||
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);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
+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