Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.js.translate.expression.WhenTranslator Maven / Gradle / Ivy
/*
* Copyright 2010-2017 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.expression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.CodegenUtil;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.translate.context.Namer;
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.BindingUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
import org.jetbrains.kotlin.types.KotlinType;
import java.util.HashMap;
import java.util.Map;
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.not;
public final class WhenTranslator extends AbstractTranslator {
@Nullable
public static JsNode translate(@NotNull KtWhenExpression expression, @NotNull TranslationContext context) {
return new WhenTranslator(expression, context).translate();
}
@NotNull
private final KtWhenExpression whenExpression;
@Nullable
private final JsExpression expressionToMatch;
private WhenTranslator(@NotNull KtWhenExpression expression, @NotNull TranslationContext context) {
super(context);
whenExpression = expression;
KtExpression subject = expression.getSubjectExpression();
expressionToMatch = subject != null ? context.defineTemporary(Translation.translateAsExpression(subject, context)) : null;
}
private JsNode translate() {
JsIf currentIf = null;
JsIf resultIf = null;
for (KtWhenEntry entry : whenExpression.getEntries()) {
JsBlock statementBlock = new JsBlock();
JsStatement statement = translateEntryExpression(entry, context(), statementBlock);
if (resultIf == null && entry.isElse()) {
context().addStatementsToCurrentBlockFrom(statementBlock);
return statement;
}
statement = JsAstUtils.mergeStatementInBlockIfNeeded(statement, statementBlock);
if (resultIf == null) {
currentIf = JsAstUtils.newJsIf(translateConditions(entry, context()), statement);
currentIf.setSource(entry);
resultIf = currentIf;
}
else {
if (entry.isElse()) {
currentIf.setElseStatement(statement);
return resultIf;
}
JsBlock conditionsBlock = new JsBlock();
JsIf nextIf = JsAstUtils.newJsIf(translateConditions(entry, context().innerBlock(conditionsBlock)), statement);
nextIf.setSource(entry);
JsStatement statementToAdd = JsAstUtils.mergeStatementInBlockIfNeeded(nextIf, conditionsBlock);
currentIf.setElseStatement(statementToAdd);
currentIf = nextIf;
}
}
if (currentIf != null && currentIf.getElseStatement() == null && isExhaustive()) {
JsExpression noWhenMatchedInvocation = new JsInvocation(JsAstUtils.pureFqn("noWhenBranchMatched", Namer.kotlinObject()));
currentIf.setElseStatement(JsAstUtils.asSyntheticStatement(noWhenMatchedInvocation));
}
return resultIf != null ? resultIf : new JsNullLiteral();
}
private boolean isExhaustive() {
KotlinType type = bindingContext().getType(whenExpression);
boolean isStatement = type != null && KotlinBuiltIns.isUnit(type) && !type.isMarkedNullable();
return CodegenUtil.isExhaustive(bindingContext(), whenExpression, isStatement);
}
@NotNull
private static JsStatement translateEntryExpression(
@NotNull KtWhenEntry entry,
@NotNull TranslationContext context,
@NotNull JsBlock block) {
KtExpression expressionToExecute = entry.getExpression();
assert expressionToExecute != null : "WhenEntry should have whenExpression to execute.";
return Translation.translateAsStatement(expressionToExecute, context, block);
}
@NotNull
private JsExpression translateConditions(@NotNull KtWhenEntry entry, @NotNull TranslationContext context) {
KtWhenCondition[] conditions = entry.getConditions();
assert conditions.length > 0 : "When entry (not else) should have at least one condition";
if (conditions.length == 1) {
return translateCondition(conditions[0], context);
}
JsExpression result = translateCondition(conditions[0], context);
for (int i = 1; i < conditions.length; i++) {
result = translateOrCondition(result, conditions[i], context);
}
return result;
}
@NotNull
private JsExpression translateOrCondition(
@NotNull JsExpression leftExpression,
@NotNull KtWhenCondition condition,
@NotNull TranslationContext context
) {
TranslationContext rightContext = context.innerBlock();
JsExpression rightExpression = translateCondition(condition, rightContext);
context.moveVarsFrom(rightContext);
if (rightContext.currentBlockIsEmpty()) {
return new JsBinaryOperation(JsBinaryOperator.OR, leftExpression, rightExpression);
} else {
assert rightExpression instanceof JsNameRef : "expected JsNameRef, but: " + rightExpression;
JsNameRef result = (JsNameRef) rightExpression;
JsIf ifStatement = JsAstUtils.newJsIf(leftExpression, JsAstUtils.assignment(result, new JsBooleanLiteral(true)).makeStmt(),
rightContext.getCurrentBlock());
ifStatement.setSource(condition);
context.addStatementToCurrentBlock(ifStatement);
return result;
}
}
@NotNull
private JsExpression translateCondition(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition, context);
if (isNegated(condition)) {
return not(patternMatchExpression);
}
return patternMatchExpression;
}
@NotNull
private JsExpression translateWhenConditionToBooleanExpression(
@NotNull KtWhenCondition condition,
@NotNull TranslationContext context
) {
if (condition instanceof KtWhenConditionIsPattern) {
return translateIsCondition((KtWhenConditionIsPattern) condition, context);
}
else if (condition instanceof KtWhenConditionWithExpression) {
return translateExpressionCondition((KtWhenConditionWithExpression) condition, context);
}
else if (condition instanceof KtWhenConditionInRange) {
return translateRangeCondition((KtWhenConditionInRange) condition, context);
}
throw new AssertionError("Unsupported when condition " + condition.getClass());
}
@NotNull
private JsExpression translateIsCondition(@NotNull KtWhenConditionIsPattern conditionIsPattern, @NotNull TranslationContext context) {
JsExpression expressionToMatch = getExpressionToMatch();
assert expressionToMatch != null : "An is-check is not allowed in when() without subject.";
KtTypeReference typeReference = conditionIsPattern.getTypeReference();
assert typeReference != null : "An is-check must have a type reference.";
KtExpression expressionToMatchNonTranslated = whenExpression.getSubjectExpression();
assert expressionToMatchNonTranslated != null : "expressionToMatch != null => expressionToMatchNonTranslated != null: " +
PsiUtilsKt.getTextWithLocation(conditionIsPattern);
JsExpression result = Translation.patternTranslator(context).translateIsCheck(expressionToMatch, typeReference);
return (result != null ? result : new JsBooleanLiteral(true)).source(conditionIsPattern);
}
@NotNull
private JsExpression translateExpressionCondition(@NotNull KtWhenConditionWithExpression condition, @NotNull TranslationContext context) {
KtExpression patternExpression = condition.getExpression();
assert patternExpression != null : "Expression pattern should have an expression.";
JsExpression expressionToMatch = getExpressionToMatch();
if (expressionToMatch == null) {
return Translation.patternTranslator(context).translateExpressionForExpressionPattern(patternExpression);
}
else {
KtExpression subject = whenExpression.getSubjectExpression();
assert subject != null : "Subject must be non-null since expressionToMatch is non-null: " +
PsiUtilsKt.getTextWithLocation(condition);
KotlinType type = BindingUtils.getTypeForExpression(bindingContext(), whenExpression.getSubjectExpression());
return Translation.patternTranslator(context).translateExpressionPattern(type, expressionToMatch, patternExpression);
}
}
@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);
boolean negated = condition.getOperationReference().getReferencedNameElementType() == KtTokens.NOT_IN;
return new InOperationTranslator(callContext, expressionToMatch, condition.getRangeExpression(), condition.getOperationReference(),
negated).translate().source(condition);
}
@Nullable
private JsExpression getExpressionToMatch() {
return expressionToMatch;
}
private static boolean isNegated(@NotNull KtWhenCondition condition) {
if (condition instanceof KtWhenConditionIsPattern) {
return ((KtWhenConditionIsPattern)condition).isNegated();
}
return false;
}
}