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