[KT-7683] Implement translation of 'when .. in' clause to JS

This commit is contained in:
Alexey Andreev
2016-02-03 16:23:52 +03:00
parent 6ee4071462
commit e9d5d8f0fe
9 changed files with 250 additions and 9 deletions
@@ -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();
}
}
@@ -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;
@@ -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);
@@ -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