From 7b403211dd60a4a503ba4658a146d014f5a549d7 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 9 Nov 2017 12:27:17 +0300 Subject: [PATCH] JS: kotlinize WhenTranslator --- .../kotlin/backend/common/CodegenUtil.kt | 6 +- .../translate/expression/WhenTranslator.java | 258 ------------------ .../js/translate/expression/WhenTranslator.kt | 196 +++++++++++++ 3 files changed, 199 insertions(+), 261 deletions(-) delete mode 100644 js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java create mode 100644 js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.kt diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt index f2a99c572b7..1aa87ecf4f4 100644 --- a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt @@ -146,14 +146,14 @@ object CodegenUtil { @JvmStatic - fun BindingContext.isExhaustive(whenExpression: KtWhenExpression, isStatement: Boolean): Boolean { - val slice = if (isStatement && !whenExpression.isUsedAsExpression(this)) { + fun isExhaustive(bindingContext: BindingContext, whenExpression: KtWhenExpression, isStatement: Boolean): Boolean { + val slice = if (isStatement && !whenExpression.isUsedAsExpression(bindingContext)) { BindingContext.IMPLICIT_EXHAUSTIVE_WHEN } else { BindingContext.EXHAUSTIVE_WHEN } - return this[slice, whenExpression] == true + return bindingContext[slice, whenExpression] == true } @JvmStatic diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java deleted file mode 100644 index d9c24380f3a..00000000000 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * 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.js.translate.utils.mutator.CoercionMutator; -import org.jetbrains.kotlin.js.translate.utils.mutator.LastExpressionMutator; -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; - - @Nullable - private final KotlinType type; - - 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; - - type = context().bindingContext().getType(expression); - } - - 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 JsStatement translateEntryExpression( - @NotNull KtWhenEntry entry, - @NotNull TranslationContext context, - @NotNull JsBlock block) { - KtExpression expressionToExecute = entry.getExpression(); - assert expressionToExecute != null : "WhenEntry should have whenExpression to execute."; - JsStatement result = Translation.translateAsStatement(expressionToExecute, context, block); - if (type != null) { - return LastExpressionMutator.mutateLastExpression(result, new CoercionMutator(type, context)); - } - - return result; - } - - @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; - } -} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.kt new file mode 100644 index 00000000000..6acfb0911d9 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.kt @@ -0,0 +1,196 @@ +/* + * 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.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.JsAstUtils +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.not +import org.jetbrains.kotlin.js.translate.utils.mutator.CoercionMutator +import org.jetbrains.kotlin.js.translate.utils.mutator.LastExpressionMutator +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getTextWithLocation +import org.jetbrains.kotlin.types.KotlinType + +class WhenTranslator +private constructor(private val whenExpression: KtWhenExpression, context: TranslationContext) : AbstractTranslator(context) { + private val expressionToMatch: JsExpression? + private val type: KotlinType? + + private val isExhaustive: Boolean + get() { + val type = bindingContext().getType(whenExpression) + val isStatement = type != null && KotlinBuiltIns.isUnit(type) && !type.isMarkedNullable + return CodegenUtil.isExhaustive(bindingContext(), whenExpression, isStatement) + } + + init { + val subject = whenExpression.subjectExpression + expressionToMatch = if (subject != null) context.defineTemporary(Translation.translateAsExpression(subject, context)) else null + + type = bindingContext().getType(whenExpression) + } + + private fun translate(): JsNode { + var currentIf: JsIf? = null + var resultIf: JsIf? = null + for (entry in whenExpression.entries) { + val statementBlock = JsBlock() + var 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.source = entry + resultIf = currentIf + } + else { + currentIf!! + if (entry.isElse) { + currentIf.elseStatement = statement + return resultIf + } + val conditionsBlock = JsBlock() + val nextIf = JsAstUtils.newJsIf(translateConditions(entry, context().innerBlock(conditionsBlock)), statement) + nextIf.source = entry + val statementToAdd = JsAstUtils.mergeStatementInBlockIfNeeded(nextIf, conditionsBlock) + currentIf.elseStatement = statementToAdd + currentIf = nextIf + } + } + + if (currentIf != null && currentIf.elseStatement == null && isExhaustive) { + val noWhenMatchedInvocation = JsInvocation(JsAstUtils.pureFqn("noWhenBranchMatched", Namer.kotlinObject())) + currentIf.elseStatement = JsAstUtils.asSyntheticStatement(noWhenMatchedInvocation) + } + + return if (resultIf != null) resultIf else JsNullLiteral() + } + + private fun translateEntryExpression( + entry: KtWhenEntry, + context: TranslationContext, + block: JsBlock + ): JsStatement { + val expressionToExecute = entry.expression ?: error("WhenEntry should have whenExpression to execute.") + val result = Translation.translateAsStatement(expressionToExecute, context, block) + return if (type != null) { + LastExpressionMutator.mutateLastExpression(result, CoercionMutator(type, context)) + } + else { + result + } + } + + private fun translateConditions(entry: KtWhenEntry, context: TranslationContext): JsExpression { + val conditions = entry.conditions + assert(conditions.isNotEmpty()) { "When entry (not else) should have at least one condition" } + + val first = translateCondition(conditions[0], context) + return conditions.asSequence().drop(1).fold(first) { acc, condition -> translateOrCondition(acc, condition, context) } + } + + private fun translateOrCondition( + leftExpression: JsExpression, + condition: KtWhenCondition, + context: TranslationContext + ): JsExpression { + val rightContext = context.innerBlock() + val rightExpression = translateCondition(condition, rightContext) + context.moveVarsFrom(rightContext) + return if (rightContext.currentBlockIsEmpty()) { + JsBinaryOperation(JsBinaryOperator.OR, leftExpression, rightExpression) + } + else { + assert(rightExpression is JsNameRef) { "expected JsNameRef, but: " + rightExpression } + val result = rightExpression as JsNameRef + val ifStatement = JsAstUtils.newJsIf(leftExpression, JsAstUtils.assignment(result, JsBooleanLiteral(true)).makeStmt(), + rightContext.currentBlock) + ifStatement.source = condition + context.addStatementToCurrentBlock(ifStatement) + result + } + } + + private fun translateCondition(condition: KtWhenCondition, context: TranslationContext): JsExpression { + val patternMatchExpression = translateWhenConditionToBooleanExpression(condition, context) + return if (isNegated(condition)) not(patternMatchExpression) else patternMatchExpression + } + + private fun translateWhenConditionToBooleanExpression( + condition: KtWhenCondition, + context: TranslationContext + ): JsExpression = when (condition) { + is KtWhenConditionIsPattern -> translateIsCondition(condition, context) + is KtWhenConditionWithExpression -> translateExpressionCondition(condition, context) + is KtWhenConditionInRange -> translateRangeCondition(condition, context) + else -> error("Unsupported when condition " + condition.javaClass) + } + + private fun translateIsCondition(conditionIsPattern: KtWhenConditionIsPattern, context: TranslationContext): JsExpression { + val expressionToMatch = expressionToMatch ?: error("An is-check is not allowed in when() without subject.") + val typeReference = conditionIsPattern.typeReference ?: error("An is-check must have a type reference.") + + val result = Translation.patternTranslator(context).translateIsCheck(expressionToMatch, typeReference) + return (result ?: JsBooleanLiteral(true)).source(conditionIsPattern) + } + + private fun translateExpressionCondition(condition: KtWhenConditionWithExpression, context: TranslationContext): JsExpression { + val patternExpression = condition.expression ?: error("Expression pattern should have an expression.") + + val expressionToMatch = expressionToMatch + val patternTranslator = Translation.patternTranslator(context) + return if (expressionToMatch == null) { + patternTranslator.translateExpressionForExpressionPattern(patternExpression) + } + else { + val type = bindingContext().getType(whenExpression.subjectExpression!!)!! + patternTranslator.translateExpressionPattern(type, expressionToMatch, patternExpression) + } + } + + private fun translateRangeCondition(condition: KtWhenConditionInRange, context: TranslationContext): JsExpression { + val expressionToMatch = expressionToMatch ?: error("Range pattern is only available for " + + "'when (C) { in ... }' expressions: ${condition.getTextWithLocation()}") + + val subjectAliases = hashMapOf() + subjectAliases[whenExpression.subjectExpression!!] = expressionToMatch + val callContext = context.innerContextWithAliasesForExpressions(subjectAliases) + val negated = condition.operationReference.getReferencedNameElementType() === KtTokens.NOT_IN + return InOperationTranslator(callContext, expressionToMatch, condition.rangeExpression!!, condition.operationReference, + negated).translate().source(condition) + } + + companion object { + @JvmStatic + fun translate(expression: KtWhenExpression, context: TranslationContext): JsNode = WhenTranslator(expression, context).translate() + + private fun isNegated(condition: KtWhenCondition): Boolean = (condition as? KtWhenConditionIsPattern)?.isNegated ?: false + } +}