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.operation;
018
019 import com.google.dart.compiler.backend.js.ast.*;
020 import org.jetbrains.annotations.NotNull;
021 import org.jetbrains.annotations.Nullable;
022 import org.jetbrains.kotlin.descriptors.CallableDescriptor;
023 import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
024 import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
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.intrinsic.functions.factories.TopLevelFIF;
029 import org.jetbrains.kotlin.js.translate.intrinsic.operation.BinaryOperationIntrinsic;
030 import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
031 import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
032 import org.jetbrains.kotlin.lexer.KtToken;
033 import org.jetbrains.kotlin.lexer.KtTokens;
034 import org.jetbrains.kotlin.psi.KtBinaryExpression;
035 import org.jetbrains.kotlin.psi.KtExpression;
036 import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt;
037 import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
038 import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
039 import org.jetbrains.kotlin.types.KotlinType;
040 import org.jetbrains.kotlin.types.TypeUtils;
041 import org.jetbrains.kotlin.types.expressions.OperatorConventions;
042
043 import java.util.Collections;
044
045 import static org.jetbrains.kotlin.js.translate.operation.AssignmentTranslator.isAssignmentOperator;
046 import static org.jetbrains.kotlin.js.translate.operation.CompareToTranslator.isCompareToCall;
047 import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
048 import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.not;
049 import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.*;
050
051 public final class BinaryOperationTranslator extends AbstractTranslator {
052
053 @NotNull
054 public static JsExpression translate(@NotNull KtBinaryExpression expression,
055 @NotNull TranslationContext context) {
056 JsExpression jsExpression = new BinaryOperationTranslator(expression, context).translate();
057 return jsExpression.source(expression);
058 }
059
060 @NotNull
061 /*package*/ static JsExpression translateAsOverloadedCall(@NotNull KtBinaryExpression expression,
062 @NotNull TranslationContext context) {
063 JsExpression jsExpression = (new BinaryOperationTranslator(expression, context)).translateAsOverloadedBinaryOperation();
064 return jsExpression.source(expression);
065 }
066
067 @NotNull
068 private final KtBinaryExpression expression;
069
070 @NotNull
071 private final KtExpression leftKtExpression;
072
073 @NotNull
074 private final KtExpression rightKtExpression;
075
076 @NotNull
077 private final KtToken operationToken;
078
079 @Nullable
080 private final CallableDescriptor operationDescriptor;
081
082 private BinaryOperationTranslator(@NotNull KtBinaryExpression expression,
083 @NotNull TranslationContext context) {
084 super(context);
085 this.expression = expression;
086
087 assert expression.getLeft() != null : "Binary expression should have a left expression: " + expression.getText();
088 leftKtExpression = expression.getLeft();
089
090 assert expression.getRight() != null : "Binary expression should have a right expression: " + expression.getText();
091 rightKtExpression = expression.getRight();
092
093 this.operationToken = getOperationToken(expression);
094 this.operationDescriptor = getCallableDescriptorForOperationExpression(bindingContext(), expression);
095 }
096
097 @NotNull
098 private JsExpression translate() {
099 BinaryOperationIntrinsic intrinsic = getIntrinsicForExpression();
100 if (intrinsic.exists()) {
101 return applyIntrinsic(intrinsic);
102 }
103 if (operationToken == KtTokens.ELVIS) {
104 return translateElvis();
105 }
106 if (isAssignmentOperator(operationToken)) {
107 return AssignmentTranslator.translate(expression, context());
108 }
109 if (isNotOverloadable()) {
110 return translateAsUnOverloadableBinaryOperation();
111 }
112 if (isCompareToCall(operationToken, operationDescriptor)) {
113 return CompareToTranslator.translate(expression, context());
114 }
115 if (isEquals()) {
116 return translateEquals();
117 }
118 assert operationDescriptor != null :
119 "Overloadable operations must have not null descriptor";
120 return translateAsOverloadedBinaryOperation();
121 }
122
123 @NotNull
124 private JsExpression translateElvis() {
125 JsExpression leftExpression = Translation.translateAsExpression(leftKtExpression, context());
126
127 JsBlock rightBlock = new JsBlock();
128 JsExpression rightExpression = Translation.translateAsExpression(rightKtExpression, context(), rightBlock);
129
130 if (rightBlock.isEmpty()) {
131 return TranslationUtils.notNullConditional(leftExpression, rightExpression, context());
132 }
133
134 JsExpression result;
135 JsIf ifStatement;
136 if (BindingContextUtilsKt.isUsedAsExpression(expression, context().bindingContext())) {
137 result = context().cacheExpressionIfNeeded(leftExpression);
138 JsExpression testExpression = TranslationUtils.isNullCheck(result);
139 rightBlock.getStatements().add(JsAstUtils.assignment(result, rightExpression).makeStmt());
140 ifStatement = JsAstUtils.newJsIf(testExpression, rightBlock);
141 }
142 else {
143 result = JsLiteral.NULL;
144 JsExpression testExpression = TranslationUtils.isNullCheck(leftExpression);
145 ifStatement = JsAstUtils.newJsIf(testExpression, rightBlock);
146 }
147 context().addStatementToCurrentBlock(ifStatement);
148 return result;
149 }
150
151 @NotNull
152 private BinaryOperationIntrinsic getIntrinsicForExpression() {
153 return context().intrinsics().getBinaryOperationIntrinsic(expression, context());
154 }
155
156 @NotNull
157 private JsExpression applyIntrinsic(@NotNull BinaryOperationIntrinsic intrinsic) {
158 JsExpression leftExpression = Translation.translateAsExpression(leftKtExpression, context());
159
160 JsBlock rightBlock = new JsBlock();
161 JsExpression rightExpression = Translation.translateAsExpression(rightKtExpression, context(), rightBlock);
162
163 if (rightBlock.isEmpty()) {
164 return intrinsic.apply(expression, leftExpression, rightExpression, context());
165 }
166
167 leftExpression = context().cacheExpressionIfNeeded(leftExpression);
168 context().addStatementsToCurrentBlockFrom(rightBlock);
169
170 return intrinsic.apply(expression, leftExpression, rightExpression, context());
171 }
172
173 private boolean isNotOverloadable() {
174 return OperatorConventions.NOT_OVERLOADABLE.contains(operationToken);
175 }
176
177 @NotNull
178 private JsExpression translateAsUnOverloadableBinaryOperation() {
179 assert OperatorConventions.NOT_OVERLOADABLE.contains(operationToken);
180 JsBinaryOperator operator = OperatorTable.getBinaryOperator(operationToken);
181 JsExpression leftExpression = Translation.translateAsExpression(leftKtExpression, context());
182
183 JsBlock rightBlock = new JsBlock();
184 JsExpression rightExpression = Translation.translateAsExpression(rightKtExpression, context(), rightBlock);
185
186 if (rightBlock.isEmpty()) {
187 return new JsBinaryOperation(operator, leftExpression, rightExpression);
188 }
189 else if (OperatorConventions.IDENTITY_EQUALS_OPERATIONS.contains(operationToken)) {
190 context().addStatementsToCurrentBlockFrom(rightBlock);
191 return new JsBinaryOperation(operator, leftExpression, rightExpression);
192 }
193
194 assert operationToken.equals(KtTokens.ANDAND) || operationToken.equals(KtTokens.OROR) : "Unsupported binary operation: " + expression.getText();
195 boolean isOror = operationToken.equals(KtTokens.OROR);
196 JsExpression literalResult = isOror ? JsLiteral.TRUE : JsLiteral.FALSE;
197 leftExpression = isOror ? not(leftExpression) : leftExpression;
198
199 JsIf ifStatement;
200 JsExpression result;
201 if (BindingContextUtilsKt.isUsedAsExpression(expression, context().bindingContext())) {
202 if (rightExpression instanceof JsNameRef) {
203 result = rightExpression; // Reuse tmp variable
204 } else {
205 result = context().defineTemporary(rightExpression);
206 }
207 JsStatement assignmentStatement = JsAstUtils.assignment(result, literalResult).makeStmt();
208 ifStatement = JsAstUtils.newJsIf(leftExpression, rightBlock, assignmentStatement);
209 }
210 else {
211 ifStatement = JsAstUtils.newJsIf(leftExpression, rightBlock);
212 result = JsLiteral.NULL;
213 }
214 context().addStatementToCurrentBlock(ifStatement);
215 return result;
216 }
217
218 private boolean isEquals() {
219 return operationToken == KtTokens.EQEQ || operationToken == KtTokens.EXCLEQ;
220 }
221
222 private JsExpression translateEquals() {
223 JsExpression left = Translation.translateAsExpression(leftKtExpression, context());
224 JsExpression right = Translation.translateAsExpression(rightKtExpression, context());
225
226 if (left == JsLiteral.NULL || right == JsLiteral.NULL) {
227 JsBinaryOperator operator = operationToken == KtTokens.EXCLEQ ? JsBinaryOperator.NEQ : JsBinaryOperator.EQ;
228 return new JsBinaryOperation(operator, left, right);
229 }
230
231 KotlinType leftType = context().bindingContext().getType(leftKtExpression);
232 KotlinType rightType = context().bindingContext().getType(rightKtExpression);
233
234 if (leftType != null && TypeUtils.isNullableType(leftType) || rightType != null && TypeUtils.isNullableType(rightType)) {
235 return mayBeWrapWithNegation(TopLevelFIF.KOTLIN_EQUALS.apply(left, Collections.singletonList(right), context()));
236 }
237
238 return translateAsOverloadedBinaryOperation();
239 }
240
241 @NotNull
242 private JsExpression translateAsOverloadedBinaryOperation() {
243 ResolvedCall<? extends FunctionDescriptor> resolvedCall = CallUtilKt.getFunctionResolvedCallWithAssert(expression, bindingContext());
244 JsExpression result = CallTranslator.translate(context(), resolvedCall, getReceiver());
245 return mayBeWrapWithNegation(result);
246 }
247
248 @NotNull
249 private JsExpression getReceiver() {
250 if (isInOrNotInOperation(expression)) {
251 return Translation.translateAsExpression(rightKtExpression, context());
252 } else {
253 return Translation.translateAsExpression(leftKtExpression, context());
254 }
255 }
256
257 @NotNull
258 private JsExpression mayBeWrapWithNegation(@NotNull JsExpression result) {
259 if (isNegatedOperation(expression)) {
260 return not(result);
261 }
262 else {
263 return result;
264 }
265 }
266 }