JS: converted CallArgumentTranslator to Kotlin

This commit is contained in:
Alexey Tsvetkov
2015-03-16 19:59:25 +03:00
parent cdfd88cc2f
commit f0c2aa9eb2
@@ -14,361 +14,310 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.reference;
package org.jetbrains.kotlin.js.translate.reference
import com.google.dart.compiler.backend.js.ast.*;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor;
import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable;
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.general.Translation;
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
import org.jetbrains.kotlin.psi.JetExpression;
import org.jetbrains.kotlin.psi.ValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.*;
import com.google.dart.compiler.backend.js.ast.*
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable
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.general.Translation
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.resolve.calls.model.*
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList
import java.util.Collections
public class CallArgumentTranslator extends AbstractTranslator {
public class CallArgumentTranslator private(private val resolvedCall: ResolvedCall<*>, private val receiver: JsExpression?, context: TranslationContext) : AbstractTranslator(context) {
@NotNull
public static ArgumentsInfo translate(
@NotNull ResolvedCall<?> resolvedCall,
@Nullable JsExpression receiver,
@NotNull TranslationContext context
) {
return translate(resolvedCall, receiver, context, context.dynamicContext().jsBlock());
}
public class ArgumentsInfo(public val translateArguments: List<JsExpression>, private val hasSpreadOperator: Boolean, public val cachedReceiver: TemporaryConstVariable) {
@NotNull
public static ArgumentsInfo translate(
@NotNull ResolvedCall<?> resolvedCall,
@Nullable JsExpression receiver,
@NotNull TranslationContext context,
@NotNull JsBlock block
) {
TranslationContext innerContext = context.innerBlock(block);
CallArgumentTranslator argumentTranslator = new CallArgumentTranslator(resolvedCall, receiver, innerContext);
ArgumentsInfo result = argumentTranslator.translate();
context.moveVarsFrom(innerContext);
return result;
}
public static class ArgumentsInfo {
private final List<JsExpression> translateArguments;
private final boolean hasSpreadOperator;
private final TemporaryConstVariable cachedReceiver;
public ArgumentsInfo(List<JsExpression> arguments, boolean operator, TemporaryConstVariable receiver) {
translateArguments = arguments;
hasSpreadOperator = operator;
cachedReceiver = receiver;
}
@NotNull
public List<JsExpression> getTranslateArguments() {
return translateArguments;
}
public boolean isHasSpreadOperator() {
return hasSpreadOperator;
}
@Nullable
public TemporaryConstVariable getCachedReceiver() {
return cachedReceiver;
public fun isHasSpreadOperator(): Boolean {
return hasSpreadOperator
}
}
private static enum ArgumentsKind { HAS_EMPTY_EXPRESSION_ARGUMENT, HAS_NOT_EMPTY_EXPRESSION_ARGUMENT }
@NotNull
private static ArgumentsKind translateSingleArgument(
@NotNull ResolvedValueArgument actualArgument,
@NotNull List<JsExpression> result,
@NotNull TranslationContext context
) {
List<ValueArgument> valueArguments = actualArgument.getArguments();
if (actualArgument instanceof DefaultValueArgument) {
result.add(context.namer().getUndefinedExpression());
return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
}
assert actualArgument instanceof ExpressionValueArgument;
assert valueArguments.size() == 1;
JetExpression argumentExpression = valueArguments.get(0).getArgumentExpression();
assert argumentExpression != null;
JsExpression jsExpression = Translation.translateAsExpression(argumentExpression, context);
result.add(jsExpression);
if (JsAstUtils.isEmptyExpression(jsExpression)) {
return ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT;
}
else {
return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
}
private enum class ArgumentsKind {
HAS_EMPTY_EXPRESSION_ARGUMENT
HAS_NOT_EMPTY_EXPRESSION_ARGUMENT
}
@NotNull
private static ArgumentsKind translateVarargArgument(
@NotNull List<ValueArgument> arguments,
@NotNull List<JsExpression> result,
@NotNull TranslationContext context,
boolean shouldWrapVarargInArray
) {
if (arguments.isEmpty()) {
if (shouldWrapVarargInArray) {
result.add(new JsArrayLiteral(Collections.<JsExpression>emptyList()));
}
return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
}
private val isNativeFunctionCall: Boolean
List<JsExpression> list;
if (shouldWrapVarargInArray) {
list = arguments.size() == 1 ? new SmartList<JsExpression>() : new ArrayList<JsExpression>(arguments.size());
}
else {
list = result;
}
ArgumentsKind resultKind = translateValueArguments(arguments, list, context);
if (shouldWrapVarargInArray) {
List<JsExpression> concatArguments = prepareConcatArguments(arguments, list);
JsExpression concatExpression = concatArgumentsIfNeeded(concatArguments);
result.add(concatExpression);
}
return resultKind;
{
this.isNativeFunctionCall = AnnotationsUtils.isNativeObject(resolvedCall.getCandidateDescriptor())
}
private static ArgumentsKind translateValueArguments(
@NotNull List<ValueArgument> arguments,
@NotNull List<JsExpression> list,
@NotNull TranslationContext context
) {
ArgumentsKind resultKind = ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
List<TranslationContext> argContexts = new SmartList<TranslationContext>();
boolean argumentsShouldBeExtractedToTmpVars = false;
for (ValueArgument argument : arguments) {
JetExpression argumentExpression = argument.getArgumentExpression();
assert argumentExpression != null;
TranslationContext argContext = context.innerBlock();
JsExpression argExpression = Translation.translateAsExpression(argumentExpression, argContext);
list.add(argExpression);
context.moveVarsFrom(argContext);
argContexts.add(argContext);
argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty();
if (JsAstUtils.isEmptyExpression(argExpression)) {
resultKind = ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT;
break;
}
}
if (argumentsShouldBeExtractedToTmpVars) {
extractArguments(list, argContexts, context, resultKind == ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT);
}
return resultKind;
}
@NotNull
private static JsExpression concatArgumentsIfNeeded(@NotNull List<JsExpression> concatArguments) {
assert concatArguments.size() > 0 : "concatArguments.size should not be 0";
if (concatArguments.size() > 1) {
return new JsInvocation(new JsNameRef("concat", concatArguments.get(0)), concatArguments.subList(1, concatArguments.size()));
}
else {
return concatArguments.get(0);
}
}
@NotNull
private static List<JsExpression> prepareConcatArguments(@NotNull List<ValueArgument> arguments, @NotNull List<JsExpression> list) {
assert arguments.size() != 0 : "arguments.size should not be 0";
assert arguments.size() == list.size() : "arguments.size: " + arguments.size() + " != list.size: " + list.size();
List<JsExpression> concatArguments = new SmartList<JsExpression>();
List<JsExpression> lastArrayContent = new SmartList<JsExpression>();
int size = arguments.size();
for(int index = 0; index < size; index++) {
ValueArgument valueArgument = arguments.get(index);
JsExpression expressionArgument = list.get(index);
if (valueArgument.getSpreadElement() != null) {
if (lastArrayContent.size() > 0) {
concatArguments.add(new JsArrayLiteral(lastArrayContent));
concatArguments.add(expressionArgument);
lastArrayContent = new SmartList<JsExpression>();
private fun removeLastUndefinedArguments(result: List<JsExpression>) {
var i: Int
run {
i = result.size() - 1
while (i >= 0) {
if (result.get(i) != context().namer().getUndefinedExpression()) {
break
}
else {
concatArguments.add(expressionArgument);
}
}
else {
lastArrayContent.add(expressionArgument);
i--
}
}
if (lastArrayContent.size() > 0) {
concatArguments.add(new JsArrayLiteral(lastArrayContent));
}
return concatArguments;
result.subList(i + 1, result.size()).clear()
}
private static void extractArguments(
@NotNull List<JsExpression> argExpressions,
@NotNull List<TranslationContext> argContexts,
@NotNull TranslationContext context,
boolean toTmpVars
) {
for(int i=0; i<argExpressions.size(); i++) {
TranslationContext argContext = argContexts.get(i);
JsExpression jsArgExpression = argExpressions.get(i);
if (argContext.currentBlockIsEmpty() && TranslationUtils.isCacheNeeded(jsArgExpression)) {
if (toTmpVars) {
TemporaryVariable temporaryVariable = context.declareTemporary(jsArgExpression);
context.addStatementToCurrentBlock(temporaryVariable.assignmentExpression().makeStmt());
argExpressions.set(i, temporaryVariable.reference());
}
else {
context.addStatementToCurrentBlock(jsArgExpression.makeStmt());
}
} else {
context.addStatementsToCurrentBlockFrom(argContext);
}
}
}
@NotNull
private final ResolvedCall<?> resolvedCall;
@Nullable
private final JsExpression receiver;
private final boolean isNativeFunctionCall;
private CallArgumentTranslator(
@NotNull ResolvedCall<?> resolvedCall,
@Nullable JsExpression receiver,
@NotNull TranslationContext context
) {
super(context);
this.resolvedCall = resolvedCall;
this.receiver = receiver;
this.isNativeFunctionCall = AnnotationsUtils.isNativeObject(resolvedCall.getCandidateDescriptor());
}
private void removeLastUndefinedArguments(@NotNull List<JsExpression> result) {
int i;
for (i = result.size() - 1; i >= 0; i--) {
if (result.get(i) != context().namer().getUndefinedExpression()) {
break;
}
}
result.subList(i + 1, result.size()).clear();
}
private ArgumentsInfo translate() {
List<ValueParameterDescriptor> valueParameters = resolvedCall.getResultingDescriptor().getValueParameters();
private fun translate(): ArgumentsInfo {
val valueParameters = resolvedCall.getResultingDescriptor().getValueParameters()
if (valueParameters.isEmpty()) {
return new ArgumentsInfo(Collections.<JsExpression>emptyList(), false, null);
return ArgumentsInfo(listOf<JsExpression>(), false, null)
}
boolean hasSpreadOperator = false;
TemporaryConstVariable cachedReceiver = null;
var hasSpreadOperator = false
var cachedReceiver: TemporaryConstVariable? = null
List<JsExpression> result = new ArrayList<JsExpression>(valueParameters.size());
List<ResolvedValueArgument> valueArgumentsByIndex = resolvedCall.getValueArgumentsByIndex();
var result: MutableList<JsExpression> = ArrayList(valueParameters.size())
val valueArgumentsByIndex = resolvedCall.getValueArgumentsByIndex()
if (valueArgumentsByIndex == null) {
throw new IllegalStateException("Failed to arrange value arguments by index: " + resolvedCall.getResultingDescriptor());
throw IllegalStateException("Failed to arrange value arguments by index: " + resolvedCall.getResultingDescriptor())
}
List<JsExpression> argsBeforeVararg = null;
boolean argumentsShouldBeExtractedToTmpVars = false;
List<TranslationContext> argContexts = new SmartList<TranslationContext>();
ArgumentsKind kind = ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
List<JsExpression> concatArguments = null;
var argsBeforeVararg: List<JsExpression>? = null
var argumentsShouldBeExtractedToTmpVars = false
val argContexts = SmartList<TranslationContext>()
var kind = ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT
var concatArguments: MutableList<JsExpression>? = null
for (ValueParameterDescriptor parameterDescriptor : valueParameters) {
ResolvedValueArgument actualArgument = valueArgumentsByIndex.get(parameterDescriptor.getIndex());
for (parameterDescriptor in valueParameters) {
val actualArgument = valueArgumentsByIndex.get(parameterDescriptor.getIndex())
TranslationContext argContext = context().innerBlock();
val argContext = context().innerBlock()
if (actualArgument instanceof VarargValueArgument) {
if (actualArgument is VarargValueArgument) {
List<ValueArgument> arguments = actualArgument.getArguments();
val arguments = actualArgument.getArguments()
int size = arguments.size();
for (int i = 0; i != size; ++i) {
if (arguments.get(i).getSpreadElement() != null) {
hasSpreadOperator = true;
break;
val size = arguments.size()
run {
var i = 0
while (i != size) {
if (arguments.get(i).getSpreadElement() != null) {
hasSpreadOperator = true
break
}
++i
}
}
if (hasSpreadOperator) {
if (isNativeFunctionCall) {
argsBeforeVararg = result;
result = new SmartList<JsExpression>();
List<JsExpression> list = new SmartList<JsExpression>();
kind = translateValueArguments(arguments, list, argContext);
concatArguments = prepareConcatArguments(arguments, list);
argsBeforeVararg = result
result = SmartList<JsExpression>()
val list = SmartList<JsExpression>()
kind = translateValueArguments(arguments, list, argContext)
concatArguments = prepareConcatArguments(arguments, list)
}
else {
kind = translateVarargArgument(arguments, result, argContext, size > 1);
kind = translateVarargArgument(arguments, result, argContext, size > 1)
}
}
else {
kind = translateVarargArgument(arguments, result, argContext, !isNativeFunctionCall);
kind = translateVarargArgument(arguments, result, argContext, !isNativeFunctionCall)
}
}
else {
kind = translateSingleArgument(actualArgument, result, argContext);
kind = translateSingleArgument(actualArgument, result, argContext)
}
context().moveVarsFrom(argContext);
argContexts.add(argContext);
argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty();
context().moveVarsFrom(argContext)
argContexts.add(argContext)
argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty()
if (kind == ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT) break;
if (kind == ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT) break
}
if (argumentsShouldBeExtractedToTmpVars) {
extractArguments(result, argContexts, context(), kind == ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT);
extractArguments(result, argContexts, context(), kind == ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT)
}
if (isNativeFunctionCall && hasSpreadOperator) {
assert argsBeforeVararg != null : "argsBeforeVararg should not be null";
assert concatArguments != null : "concatArguments should not be null";
assert(argsBeforeVararg != null, "argsBeforeVararg should not be null")
assert(concatArguments != null, "concatArguments should not be null")
concatArguments.addAll(result);
concatArguments!!.addAll(result)
if (!argsBeforeVararg.isEmpty()) {
concatArguments.add(0, new JsArrayLiteral(argsBeforeVararg));
if (!argsBeforeVararg!!.isEmpty()) {
concatArguments!!.add(0, JsArrayLiteral(argsBeforeVararg))
}
result = new SmartList<JsExpression>(concatArgumentsIfNeeded(concatArguments));
result = SmartList(concatArgumentsIfNeeded(concatArguments))
if (receiver != null) {
cachedReceiver = context().getOrDeclareTemporaryConstVariable(receiver);
result.add(0, cachedReceiver.reference());
cachedReceiver = context().getOrDeclareTemporaryConstVariable(receiver)
result.add(0, cachedReceiver!!.reference())
}
else {
result.add(0, JsLiteral.NULL);
result.add(0, JsLiteral.NULL)
}
}
removeLastUndefinedArguments(result);
return new ArgumentsInfo(result, hasSpreadOperator, cachedReceiver);
removeLastUndefinedArguments(result)
return ArgumentsInfo(result, hasSpreadOperator, cachedReceiver)
}
companion object {
public fun translate(resolvedCall: ResolvedCall<*>, receiver: JsExpression?, context: TranslationContext): ArgumentsInfo {
return translate(resolvedCall, receiver, context, context.dynamicContext().jsBlock())
}
public fun translate(resolvedCall: ResolvedCall<*>, receiver: JsExpression?, context: TranslationContext, block: JsBlock): ArgumentsInfo {
val innerContext = context.innerBlock(block)
val argumentTranslator = CallArgumentTranslator(resolvedCall, receiver, innerContext)
val result = argumentTranslator.translate()
context.moveVarsFrom(innerContext)
return result
}
private fun translateSingleArgument(actualArgument: ResolvedValueArgument, result: MutableList<JsExpression>, context: TranslationContext): ArgumentsKind {
val valueArguments = actualArgument.getArguments()
if (actualArgument is DefaultValueArgument) {
result.add(context.namer().getUndefinedExpression())
return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT
}
assert(actualArgument is ExpressionValueArgument)
assert(valueArguments.size() == 1)
val argumentExpression = valueArguments.get(0).getArgumentExpression()
assert(argumentExpression != null)
val jsExpression = Translation.translateAsExpression(argumentExpression, context)
result.add(jsExpression)
if (JsAstUtils.isEmptyExpression(jsExpression)) {
return ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT
}
else {
return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT
}
}
private fun translateVarargArgument(arguments: List<ValueArgument>, result: MutableList<JsExpression>, context: TranslationContext, shouldWrapVarargInArray: Boolean): ArgumentsKind {
if (arguments.isEmpty()) {
if (shouldWrapVarargInArray) {
result.add(JsArrayLiteral(listOf<JsExpression>()))
}
return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT
}
val list: MutableList<JsExpression>
if (shouldWrapVarargInArray) {
list = if (arguments.size() == 1) SmartList<JsExpression>() else ArrayList<JsExpression>(arguments.size())
}
else {
list = result
}
val resultKind = translateValueArguments(arguments, list, context)
if (shouldWrapVarargInArray) {
val concatArguments = prepareConcatArguments(arguments, list)
val concatExpression = concatArgumentsIfNeeded(concatArguments)
result.add(concatExpression)
}
return resultKind
}
private fun translateValueArguments(arguments: List<ValueArgument>, list: MutableList<JsExpression>, context: TranslationContext): ArgumentsKind {
var resultKind = ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT
val argContexts = SmartList<TranslationContext>()
var argumentsShouldBeExtractedToTmpVars = false
for (argument in arguments) {
val argumentExpression = argument.getArgumentExpression()
assert(argumentExpression != null)
val argContext = context.innerBlock()
val argExpression = Translation.translateAsExpression(argumentExpression, argContext)
list.add(argExpression)
context.moveVarsFrom(argContext)
argContexts.add(argContext)
argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty()
if (JsAstUtils.isEmptyExpression(argExpression)) {
resultKind = ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT
break
}
}
if (argumentsShouldBeExtractedToTmpVars) {
extractArguments(list, argContexts, context, resultKind == ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT)
}
return resultKind
}
private fun concatArgumentsIfNeeded(concatArguments: List<JsExpression>): JsExpression {
assert(concatArguments.size() > 0, "concatArguments.size should not be 0")
if (concatArguments.size() > 1) {
return JsInvocation(JsNameRef("concat", concatArguments.get(0)), concatArguments.subList(1, concatArguments.size()))
}
else {
return concatArguments.get(0)
}
}
private fun prepareConcatArguments(arguments: List<ValueArgument>, list: List<JsExpression>): MutableList<JsExpression> {
assert(arguments.size() != 0, "arguments.size should not be 0")
assert(arguments.size() == list.size()) { "arguments.size: " + arguments.size() + " != list.size: " + list.size() }
val concatArguments = SmartList<JsExpression>()
var lastArrayContent: MutableList<JsExpression> = SmartList()
val size = arguments.size()
for (index in 0..size - 1) {
val valueArgument = arguments.get(index)
val expressionArgument = list.get(index)
if (valueArgument.getSpreadElement() != null) {
if (lastArrayContent.size() > 0) {
concatArguments.add(JsArrayLiteral(lastArrayContent))
concatArguments.add(expressionArgument)
lastArrayContent = SmartList<JsExpression>()
}
else {
concatArguments.add(expressionArgument)
}
}
else {
lastArrayContent.add(expressionArgument)
}
}
if (lastArrayContent.size() > 0) {
concatArguments.add(JsArrayLiteral(lastArrayContent))
}
return concatArguments
}
private fun extractArguments(argExpressions: MutableList<JsExpression>, argContexts: List<TranslationContext>, context: TranslationContext, toTmpVars: Boolean) {
for (i in argExpressions.indices) {
val argContext = argContexts.get(i)
val jsArgExpression = argExpressions.get(i)
if (argContext.currentBlockIsEmpty() && TranslationUtils.isCacheNeeded(jsArgExpression)) {
if (toTmpVars) {
val temporaryVariable = context.declareTemporary(jsArgExpression)
context.addStatementToCurrentBlock(temporaryVariable.assignmentExpression().makeStmt())
argExpressions.set(i, temporaryVariable.reference())
}
else {
context.addStatementToCurrentBlock(jsArgExpression.makeStmt())
}
}
else {
context.addStatementsToCurrentBlockFrom(argContext)
}
}
}
}
}