/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.operation;
import com.intellij.psi.tree.
IElementType;
import org.jetbrains.annotations.
NotNull;
import org.jetbrains.kotlin.descriptors.
CallableDescriptor;
import org.jetbrains.kotlin.js.backend.ast.
JsBinaryOperation;
import org.jetbrains.kotlin.js.backend.ast.
JsBinaryOperator;
import org.jetbrains.kotlin.js.backend.ast.
JsBlock;
import org.jetbrains.kotlin.js.backend.ast.
JsExpression;
import org.jetbrains.kotlin.js.backend.ast.metadata.
MetadataProperties;
import org.jetbrains.kotlin.js.translate.context.
TemporaryVariable;
import org.jetbrains.kotlin.js.translate.context.
TranslationContext;
import org.jetbrains.kotlin.js.translate.general.
AbstractTranslator;
import org.jetbrains.kotlin.js.translate.reference.
AccessTranslator;
import org.jetbrains.kotlin.js.translate.utils.
JsAstUtils;
import org.jetbrains.kotlin.psi.
KtExpression;
import org.jetbrains.kotlin.psi.
KtUnaryExpression;
import org.jetbrains.kotlin.resolve.calls.tasks.
DynamicCallsKt;
import org.jetbrains.kotlin.types.expressions.
OperatorConventions;
import java.util.
Arrays;
import static org.jetbrains.kotlin.js.translate.reference.
AccessTranslationUtils.getAccessTranslator;
import static org.jetbrains.kotlin.js.translate.utils.
BindingUtils.getCallableDescriptorForOperationExpression;
import static org.jetbrains.kotlin.js.translate.utils.
PsiUtils.getBaseExpression;
import static org.jetbrains.kotlin.js.translate.utils.
PsiUtils.isPrefix;
import static org.jetbrains.kotlin.js.translate.utils.
TranslationUtils.hasCorrespondingFunctionIntrinsic;
// TODO: provide better increment translator logic
public abstract class
IncrementTranslator extends
AbstractTranslator {
public static boolean
isIncrement(
IElementType operationToken) {
//noinspection SuspiciousMethodCalls
return
OperatorConventions.
INCREMENT_OPERATIONS.
contains(
operationToken);
}
@
NotNull
public static
JsExpression translate(@
NotNull KtUnaryExpression expression,
@
NotNull TranslationContext context) {
if (
isDynamic(
context,
expression)) {
return
DynamicIncrementTranslator.
doTranslate(
expression,
context);
}
if (
hasCorrespondingFunctionIntrinsic(
context,
expression)) {
return new
IntrinsicIncrementTranslator(
expression,
context).
translateIncrementExpression();
}
return (new
OverloadedIncrementTranslator(
expression,
context)).
translateIncrementExpression();
}
@
NotNull
protected final
KtUnaryExpression expression;
@
NotNull
protected final
AccessTranslator accessTranslator;
@
NotNull
private final
JsBlock accessBlock = new
JsBlock();
protected
IncrementTranslator(@
NotNull KtUnaryExpression expression,
@
NotNull TranslationContext context) {
super(
context);
this.
expression =
expression;
KtExpression baseExpression =
getBaseExpression(
expression);
this.
accessTranslator =
getAccessTranslator(
baseExpression,
context().
innerBlock(
accessBlock)).
getCached();
}
@
NotNull
protected
JsExpression translateIncrementExpression() {
if (
isPrefix(
expression)) {
return
asPrefix();
}
return
asPostfix();
}
//TODO: decide if this expression can be optimised in case of direct access (not property)
@
NotNull
private
JsExpression asPrefix() {
// code fragment: expr(a++)
// generate: expr(a = a.inc(), a)
JsExpression getExpression =
accessTranslator.
translateAsGet().
source(
expression);
JsExpression reassignment =
variableReassignment(
context().
innerBlock(
accessBlock),
getExpression)
.
source(
expression);
accessBlock.
getStatements().
add(
JsAstUtils.
asSyntheticStatement(
reassignment));
JsExpression getNewValue =
accessTranslator.
translateAsGet().
source(
expression);
JsExpression result;
if (
accessBlock.
getStatements().
size() == 1) {
result = new
JsBinaryOperation(
JsBinaryOperator.
COMMA,
reassignment,
getNewValue);
}
else {
context().
getCurrentBlock().
getStatements().
addAll(
accessBlock.
getStatements());
result =
getNewValue;
}
MetadataProperties.
setSynthetic(
result, true);
return
result;
}
//TODO: decide if this expression can be optimised in case of direct access (not property)
@
NotNull
private
JsExpression asPostfix() {
// code fragment: expr(a++)
// generate: expr( (t1 = a, t2 = t1, a = t1.inc(), t2) )
TemporaryVariable t1 =
context().
declareTemporary(
accessTranslator.
translateAsGet().
source(
expression),
expression);
accessBlock.
getStatements().
add(
t1.
assignmentStatement());
JsExpression variableReassignment =
variableReassignment(
context().
innerBlock(
accessBlock),
t1.
reference())
.
source(
expression);
accessBlock.
getStatements().
add(
JsAstUtils.
asSyntheticStatement(
variableReassignment.
source(
expression)));
JsExpression result;
if (
accessBlock.
getStatements().
size() == 2) {
result =
JsAstUtils.
newSequence(
Arrays.
asList(
t1.
assignmentExpression(),
variableReassignment,
t1.
reference()));
}
else {
context().
getCurrentBlock().
getStatements().
addAll(
accessBlock.
getStatements());
result =
t1.
reference();
}
MetadataProperties.
setSynthetic(
result, true);
return
result;
}
@
NotNull
private
JsExpression variableReassignment(@
NotNull TranslationContext context, @
NotNull JsExpression toCallMethodUpon) {
JsExpression overloadedMethodCallOnPropertyGetter =
operationExpression(
context,
toCallMethodUpon);
return
accessTranslator.
translateAsSet(
overloadedMethodCallOnPropertyGetter);
}
@
NotNull
abstract
JsExpression operationExpression(@
NotNull TranslationContext context, @
NotNull JsExpression receiver);
private static boolean
isDynamic(
TranslationContext context,
KtUnaryExpression expression) {
CallableDescriptor operationDescriptor =
getCallableDescriptorForOperationExpression(
context.
bindingContext(),
expression);
assert
operationDescriptor != null;
return
DynamicCallsKt.
isDynamic(
operationDescriptor);
}
}