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.js.backend.ast.JsBinaryOperation;
020    import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperator;
021    import org.jetbrains.kotlin.js.backend.ast.JsBlock;
022    import org.jetbrains.kotlin.js.backend.ast.JsExpression;
023    import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties;
024    import org.jetbrains.kotlin.js.util.AstUtil;
025    import com.intellij.psi.tree.IElementType;
026    import org.jetbrains.annotations.NotNull;
027    import org.jetbrains.kotlin.descriptors.CallableDescriptor;
028    import org.jetbrains.kotlin.js.translate.context.TemporaryVariable;
029    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
030    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
031    import org.jetbrains.kotlin.js.translate.reference.AccessTranslator;
032    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
033    import org.jetbrains.kotlin.psi.KtExpression;
034    import org.jetbrains.kotlin.psi.KtUnaryExpression;
035    import org.jetbrains.kotlin.resolve.calls.tasks.DynamicCallsKt;
036    import org.jetbrains.kotlin.types.expressions.OperatorConventions;
037    
038    import static org.jetbrains.kotlin.js.translate.reference.AccessTranslationUtils.getAccessTranslator;
039    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
040    import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getBaseExpression;
041    import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.isPrefix;
042    import static org.jetbrains.kotlin.js.translate.utils.TranslationUtils.hasCorrespondingFunctionIntrinsic;
043    
044    // TODO: provide better increment translator logic
045    public abstract class IncrementTranslator extends AbstractTranslator {
046    
047        public static boolean isIncrement(IElementType operationToken) {
048            //noinspection SuspiciousMethodCalls
049            return OperatorConventions.INCREMENT_OPERATIONS.contains(operationToken);
050        }
051    
052        @NotNull
053        public static JsExpression translate(@NotNull KtUnaryExpression expression,
054                                             @NotNull TranslationContext context) {
055            if (isDynamic(context, expression)) {
056                return DynamicIncrementTranslator.doTranslate(expression, context);
057            }
058            if (hasCorrespondingFunctionIntrinsic(context, expression)) {
059                return new IntrinsicIncrementTranslator(expression, context).translateIncrementExpression();
060            }
061            return (new OverloadedIncrementTranslator(expression, context)).translateIncrementExpression();
062        }
063    
064        @NotNull
065        protected final KtUnaryExpression expression;
066        @NotNull
067        protected final AccessTranslator accessTranslator;
068    
069        @NotNull
070        private final JsBlock accessBlock = new JsBlock();
071    
072        protected IncrementTranslator(@NotNull KtUnaryExpression expression,
073                                      @NotNull TranslationContext context) {
074            super(context);
075            this.expression = expression;
076            KtExpression baseExpression = getBaseExpression(expression);
077            this.accessTranslator = getAccessTranslator(baseExpression, context().innerBlock(accessBlock)).getCached();
078        }
079    
080        @NotNull
081        protected JsExpression translateIncrementExpression() {
082            if (isPrefix(expression)) {
083                return asPrefix();
084            }
085            return asPostfix();
086        }
087    
088        //TODO: decide if this expression can be optimised in case of direct access (not property)
089        @NotNull
090        private JsExpression asPrefix() {
091            // code fragment: expr(a++)
092            // generate: expr(a = a.inc(), a)
093            JsExpression getExpression = accessTranslator.translateAsGet();
094            JsExpression reassignment = variableReassignment(getExpression);
095            accessBlock.getStatements().add(JsAstUtils.asSyntheticStatement(reassignment));
096            JsExpression getNewValue = accessTranslator.translateAsGet();
097    
098            JsExpression result;
099            if (accessBlock.getStatements().size() == 1) {
100                result = new JsBinaryOperation(JsBinaryOperator.COMMA, reassignment, getNewValue);
101            }
102            else {
103                context().getCurrentBlock().getStatements().addAll(accessBlock.getStatements());
104                result = getNewValue;
105            }
106            MetadataProperties.setSynthetic(result, true);
107            return result;
108        }
109    
110        //TODO: decide if this expression can be optimised in case of direct access (not property)
111        @NotNull
112        private JsExpression asPostfix() {
113            // code fragment: expr(a++)
114            // generate: expr( (t1 = a, t2 = t1, a = t1.inc(), t2) )
115            TemporaryVariable t1 = context().declareTemporary(accessTranslator.translateAsGet());
116            accessBlock.getStatements().add(t1.assignmentStatement());
117            JsExpression variableReassignment = variableReassignment(t1.reference());
118            accessBlock.getStatements().add(JsAstUtils.asSyntheticStatement(variableReassignment));
119    
120            JsExpression result;
121            if (accessBlock.getStatements().size() == 2) {
122                result = AstUtil.newSequence(t1.assignmentExpression(), variableReassignment, t1.reference());
123            }
124            else {
125                context().getCurrentBlock().getStatements().addAll(accessBlock.getStatements());
126                result = t1.reference();
127            }
128    
129            MetadataProperties.setSynthetic(result, true);
130            return result;
131        }
132    
133        @NotNull
134        private JsExpression variableReassignment(@NotNull JsExpression toCallMethodUpon) {
135            JsExpression overloadedMethodCallOnPropertyGetter = operationExpression(toCallMethodUpon);
136            return accessTranslator.translateAsSet(overloadedMethodCallOnPropertyGetter);
137        }
138    
139        @NotNull
140        abstract JsExpression operationExpression(@NotNull JsExpression receiver);
141    
142        private static boolean isDynamic(TranslationContext context, KtUnaryExpression expression) {
143            CallableDescriptor operationDescriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression);
144            assert  operationDescriptor != null;
145            return DynamicCallsKt.isDynamic(operationDescriptor);
146        }
147    }