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 org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.kotlin.descriptors.CallableDescriptor;
022    import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
023    import org.jetbrains.kotlin.js.backend.ast.*;
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    }