001 /*
002 * Copyright 2010-2015 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.js.translate.expression;
018
019 import org.jetbrains.annotations.NotNull;
020 import org.jetbrains.annotations.Nullable;
021 import org.jetbrains.kotlin.backend.common.CodegenUtil;
022 import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
023 import org.jetbrains.kotlin.js.backend.ast.*;
024 import org.jetbrains.kotlin.js.translate.context.Namer;
025 import org.jetbrains.kotlin.js.translate.context.TranslationContext;
026 import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
027 import org.jetbrains.kotlin.js.translate.general.Translation;
028 import org.jetbrains.kotlin.js.translate.operation.InOperationTranslator;
029 import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
030 import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
031 import org.jetbrains.kotlin.lexer.KtTokens;
032 import org.jetbrains.kotlin.psi.*;
033 import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
034 import org.jetbrains.kotlin.types.KotlinType;
035
036 import java.util.HashMap;
037 import java.util.Map;
038
039 import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.not;
040
041 public final class WhenTranslator extends AbstractTranslator {
042 @Nullable
043 public static JsNode translate(@NotNull KtWhenExpression expression, @NotNull TranslationContext context) {
044 return new WhenTranslator(expression, context).translate();
045 }
046
047 @NotNull
048 private final KtWhenExpression whenExpression;
049
050 @Nullable
051 private final JsExpression expressionToMatch;
052
053 private WhenTranslator(@NotNull KtWhenExpression expression, @NotNull TranslationContext context) {
054 super(context);
055
056 whenExpression = expression;
057
058 KtExpression subject = expression.getSubjectExpression();
059 expressionToMatch = subject != null ? context.defineTemporary(Translation.translateAsExpression(subject, context)) : null;
060 }
061
062 private JsNode translate() {
063 JsIf currentIf = null;
064 JsIf resultIf = null;
065 for (KtWhenEntry entry : whenExpression.getEntries()) {
066 JsBlock statementBlock = new JsBlock();
067 JsStatement statement = translateEntryExpression(entry, context(), statementBlock);
068
069 if (resultIf == null && entry.isElse()) {
070 context().addStatementsToCurrentBlockFrom(statementBlock);
071 return statement;
072 }
073 statement = JsAstUtils.mergeStatementInBlockIfNeeded(statement, statementBlock);
074
075 if (resultIf == null) {
076 currentIf = JsAstUtils.newJsIf(translateConditions(entry, context()), statement);
077 resultIf = currentIf;
078 }
079 else {
080 if (entry.isElse()) {
081 currentIf.setElseStatement(statement);
082 return resultIf;
083 }
084 JsBlock conditionsBlock = new JsBlock();
085 JsIf nextIf = JsAstUtils.newJsIf(translateConditions(entry, context().innerBlock(conditionsBlock)), statement);
086 JsStatement statementToAdd = JsAstUtils.mergeStatementInBlockIfNeeded(nextIf, conditionsBlock);
087 currentIf.setElseStatement(statementToAdd);
088 currentIf = nextIf;
089 }
090 }
091
092 if (currentIf != null && currentIf.getElseStatement() == null && isExhaustive()) {
093 JsExpression noWhenMatchedInvocation = new JsInvocation(JsAstUtils.pureFqn("noWhenBranchMatched", Namer.kotlinObject()));
094 currentIf.setElseStatement(JsAstUtils.asSyntheticStatement(noWhenMatchedInvocation));
095 }
096
097 return resultIf != null ? resultIf : JsLiteral.NULL;
098 }
099
100 private boolean isExhaustive() {
101 KotlinType type = bindingContext().getType(whenExpression);
102 boolean isStatement = type != null && KotlinBuiltIns.isUnit(type) && !type.isMarkedNullable();
103 return CodegenUtil.isExhaustive(bindingContext(), whenExpression, isStatement);
104 }
105
106 @NotNull
107 private static JsStatement translateEntryExpression(
108 @NotNull KtWhenEntry entry,
109 @NotNull TranslationContext context,
110 @NotNull JsBlock block) {
111 KtExpression expressionToExecute = entry.getExpression();
112 assert expressionToExecute != null : "WhenEntry should have whenExpression to execute.";
113 return Translation.translateAsStatement(expressionToExecute, context, block);
114 }
115
116 @NotNull
117 private JsExpression translateConditions(@NotNull KtWhenEntry entry, @NotNull TranslationContext context) {
118 KtWhenCondition[] conditions = entry.getConditions();
119
120 assert conditions.length > 0 : "When entry (not else) should have at least one condition";
121
122 if (conditions.length == 1) {
123 return translateCondition(conditions[0], context);
124 }
125
126 JsExpression result = translateCondition(conditions[0], context);
127 for (int i = 1; i < conditions.length; i++) {
128 result = translateOrCondition(result, conditions[i], context);
129 }
130
131 return result;
132 }
133
134 @NotNull
135 private JsExpression translateOrCondition(
136 @NotNull JsExpression leftExpression,
137 @NotNull KtWhenCondition condition,
138 @NotNull TranslationContext context
139 ) {
140 TranslationContext rightContext = context.innerBlock();
141 JsExpression rightExpression = translateCondition(condition, rightContext);
142 context.moveVarsFrom(rightContext);
143 if (rightContext.currentBlockIsEmpty()) {
144 return new JsBinaryOperation(JsBinaryOperator.OR, leftExpression, rightExpression);
145 } else {
146 assert rightExpression instanceof JsNameRef : "expected JsNameRef, but: " + rightExpression;
147 JsNameRef result = (JsNameRef) rightExpression;
148 JsIf ifStatement = JsAstUtils.newJsIf(leftExpression, JsAstUtils.assignment(result, JsLiteral.TRUE).makeStmt(),
149 rightContext.getCurrentBlock());
150 context.addStatementToCurrentBlock(ifStatement);
151 return result;
152 }
153 }
154
155 @NotNull
156 private JsExpression translateCondition(@NotNull KtWhenCondition condition, @NotNull TranslationContext context) {
157 JsExpression patternMatchExpression = translateWhenConditionToBooleanExpression(condition, context);
158 if (isNegated(condition)) {
159 return not(patternMatchExpression);
160 }
161 return patternMatchExpression;
162 }
163
164 @NotNull
165 private JsExpression translateWhenConditionToBooleanExpression(
166 @NotNull KtWhenCondition condition,
167 @NotNull TranslationContext context
168 ) {
169 if (condition instanceof KtWhenConditionIsPattern) {
170 return translateIsCondition((KtWhenConditionIsPattern) condition, context);
171 }
172 else if (condition instanceof KtWhenConditionWithExpression) {
173 return translateExpressionCondition((KtWhenConditionWithExpression) condition, context);
174 }
175 else if (condition instanceof KtWhenConditionInRange) {
176 return translateRangeCondition((KtWhenConditionInRange) condition, context);
177 }
178 throw new AssertionError("Unsupported when condition " + condition.getClass());
179 }
180
181 @NotNull
182 private JsExpression translateIsCondition(@NotNull KtWhenConditionIsPattern conditionIsPattern, @NotNull TranslationContext context) {
183 JsExpression expressionToMatch = getExpressionToMatch();
184 assert expressionToMatch != null : "An is-check is not allowed in when() without subject.";
185
186 KtTypeReference typeReference = conditionIsPattern.getTypeReference();
187 assert typeReference != null : "An is-check must have a type reference.";
188
189 KtExpression expressionToMatchNonTranslated = whenExpression.getSubjectExpression();
190 assert expressionToMatchNonTranslated != null : "expressionToMatch != null => expressionToMatchNonTranslated != null: " +
191 PsiUtilsKt.getTextWithLocation(conditionIsPattern);
192 JsExpression result = Translation.patternTranslator(context).translateIsCheck(expressionToMatch, typeReference);
193 return result != null ? result : JsLiteral.TRUE;
194 }
195
196 @NotNull
197 private JsExpression translateExpressionCondition(@NotNull KtWhenConditionWithExpression condition, @NotNull TranslationContext context) {
198 KtExpression patternExpression = condition.getExpression();
199 assert patternExpression != null : "Expression pattern should have an expression.";
200
201 JsExpression expressionToMatch = getExpressionToMatch();
202 if (expressionToMatch == null) {
203 return Translation.patternTranslator(context).translateExpressionForExpressionPattern(patternExpression);
204 }
205 else {
206 KtExpression subject = whenExpression.getSubjectExpression();
207 assert subject != null : "Subject must be non-null since expressionToMatch is non-null: " +
208 PsiUtilsKt.getTextWithLocation(condition);
209 KotlinType type = BindingUtils.getTypeForExpression(bindingContext(), whenExpression.getSubjectExpression());
210 return Translation.patternTranslator(context).translateExpressionPattern(type, expressionToMatch, patternExpression);
211 }
212 }
213
214 @NotNull
215 private JsExpression translateRangeCondition(@NotNull KtWhenConditionInRange condition, @NotNull TranslationContext context) {
216 KtExpression patternExpression = condition.getRangeExpression();
217 assert patternExpression != null : "Expression pattern should have an expression: " +
218 PsiUtilsKt.getTextWithLocation(condition);
219
220 JsExpression expressionToMatch = getExpressionToMatch();
221 assert expressionToMatch != null : "Range pattern is only available for 'when (C) { in ... }' expressions: " +
222 PsiUtilsKt.getTextWithLocation(condition);
223
224 Map<KtExpression, JsExpression> subjectAliases = new HashMap<KtExpression, JsExpression>();
225 subjectAliases.put(whenExpression.getSubjectExpression(), expressionToMatch);
226 TranslationContext callContext = context.innerContextWithAliasesForExpressions(subjectAliases);
227 boolean negated = condition.getOperationReference().getReferencedNameElementType() == KtTokens.NOT_IN;
228 return new InOperationTranslator(callContext, expressionToMatch, condition.getRangeExpression(), condition.getOperationReference(),
229 negated).translate();
230 }
231
232 @Nullable
233 private JsExpression getExpressionToMatch() {
234 return expressionToMatch;
235 }
236
237 private static boolean isNegated(@NotNull KtWhenCondition condition) {
238 if (condition instanceof KtWhenConditionIsPattern) {
239 return ((KtWhenConditionIsPattern)condition).isNegated();
240 }
241 return false;
242 }
243 }