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    }