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 }