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.kotlin.builtins.KotlinBuiltIns;
020    import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperation;
021    import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperator;
022    import org.jetbrains.kotlin.js.backend.ast.JsBlock;
023    import org.jetbrains.kotlin.js.backend.ast.JsExpression;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
026    import org.jetbrains.kotlin.js.translate.reference.AccessTranslator;
027    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
028    import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
029    import org.jetbrains.kotlin.lexer.KtSingleValueToken;
030    import org.jetbrains.kotlin.lexer.KtToken;
031    import org.jetbrains.kotlin.psi.KtBinaryExpression;
032    import org.jetbrains.kotlin.psi.KtExpression;
033    import org.jetbrains.kotlin.types.KotlinType;
034    import org.jetbrains.kotlin.types.expressions.OperatorConventions;
035    
036    import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken;
037    import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.isAssignment;
038    import static org.jetbrains.kotlin.js.translate.utils.TranslationUtils.isSimpleNameExpressionNotDelegatedLocalVar;
039    
040    public final class IntrinsicAssignmentTranslator extends AssignmentTranslator {
041        private final JsExpression right;
042        private final AccessTranslator accessTranslator;
043        private final boolean rightExpressionTrivial;
044        private final JsBlock rightBlock = new JsBlock();
045    
046        @NotNull
047        public static JsExpression doTranslate(@NotNull KtBinaryExpression expression, @NotNull TranslationContext context) {
048            return (new IntrinsicAssignmentTranslator(expression, context)).translate();
049        }
050    
051        private IntrinsicAssignmentTranslator(@NotNull KtBinaryExpression expression, @NotNull TranslationContext context) {
052            super(expression, context);
053    
054            right = translateRightExpression(context, expression);
055            rightExpressionTrivial = rightBlock.isEmpty();
056            KtExpression left = expression.getLeft();
057            assert left != null;
058            accessTranslator = createAccessTranslator(left, !rightExpressionTrivial);
059        }
060    
061        private JsExpression translateRightExpression(TranslationContext context, KtBinaryExpression expression) {
062            JsExpression result = TranslationUtils.translateRightExpression(context, expression, rightBlock);
063            KotlinType leftType = context.bindingContext().getType(expression.getLeft());
064            KotlinType rightType = context.bindingContext().getType(expression.getRight());
065            if (rightType != null && KotlinBuiltIns.isCharOrNullableChar(rightType)) {
066                if (leftType != null && KotlinBuiltIns.isStringOrNullableString(leftType)) {
067                    result = JsAstUtils.charToString(result);
068                }
069                else if (leftType == null || !KotlinBuiltIns.isCharOrNullableChar(leftType)) {
070                    result = JsAstUtils.charToBoxedChar(result);
071                }
072            }
073            return result;
074        }
075    
076        @NotNull
077        private JsExpression translate() {
078            if (isAssignment(getOperationToken(expression))) {
079                return translateAsPlainAssignment();
080            }
081            return translateAsAssignmentOperation();
082        }
083    
084        @NotNull
085        private JsExpression translateAsAssignmentOperation() {
086            if (isSimpleNameExpressionNotDelegatedLocalVar(expression.getLeft(), context()) && rightExpressionTrivial) {
087                return translateAsPlainAssignmentOperation();
088            }
089            return translateAsAssignToCounterpart();
090        }
091    
092        @NotNull
093        private JsExpression translateAsAssignToCounterpart() {
094            JsBinaryOperator operator = getCounterpartOperator();
095            JsExpression oldValue = accessTranslator.translateAsGet();
096            if (!rightExpressionTrivial) {
097                oldValue = context().defineTemporary(oldValue);
098            }
099            JsBinaryOperation counterpartOperation = new JsBinaryOperation(operator, oldValue, right);
100            context().addStatementsToCurrentBlockFrom(rightBlock);
101            return accessTranslator.translateAsSet(counterpartOperation);
102        }
103    
104        @NotNull
105        private JsBinaryOperator getCounterpartOperator() {
106            KtToken assignmentOperationToken = getOperationToken(expression);
107            assert assignmentOperationToken instanceof KtSingleValueToken;
108            assert OperatorConventions.ASSIGNMENT_OPERATIONS.containsKey(assignmentOperationToken);
109            KtToken counterpartToken = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS.get(assignmentOperationToken);
110            assert OperatorTable.hasCorrespondingBinaryOperator(counterpartToken) :
111                    "Unsupported token encountered: " + counterpartToken.toString();
112            return OperatorTable.getBinaryOperator(counterpartToken);
113        }
114    
115        @NotNull
116        private JsExpression translateAsPlainAssignmentOperation() {
117            context().addStatementsToCurrentBlockFrom(rightBlock);
118            JsBinaryOperator operator = getAssignmentOperator();
119            return new JsBinaryOperation(operator, accessTranslator.translateAsGet(), right);
120        }
121    
122        @NotNull
123        private JsBinaryOperator getAssignmentOperator() {
124            KtToken token = getOperationToken(expression);
125            assert token instanceof KtSingleValueToken;
126            assert OperatorConventions.ASSIGNMENT_OPERATIONS.containsKey(token);
127            assert OperatorTable.hasCorrespondingBinaryOperator(token) :
128                    "Unsupported token encountered: " + token.toString();
129            return OperatorTable.getBinaryOperator(token);
130        }
131    
132        @NotNull
133        private JsExpression translateAsPlainAssignment() {
134            context().addStatementsToCurrentBlockFrom(rightBlock);
135            return accessTranslator.translateAsSet(right);
136        }
137    }