JS: translate call arguments in the order the occur in source code, not in order of parameters in descriptor. See KT-15506

This commit is contained in:
Alexey Andreev
2016-12-30 14:57:59 +03:00
parent e6b78f68cd
commit 70176dfacc
10 changed files with 161 additions and 162 deletions
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
fun test(a: String, b: String): String {
return a + b;
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
fun box(): String {
var invokeOrder = "";
val expectedResult = "0_1_9"
-3
View File
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
fun box(): String {
var res = "";
var call = test(b = {res += "K"; "K"}(), a = {res+="O"; "O"}(), c = {res += "L"; "L"})
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
fun box(): String {
var res = "";
var call = Z("Z").test(b = {res += "K"; "K"}(), a = {res+="O"; "O"}(), c = {res += "L"; "L"})
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
var res = ""
fun getM(): String {
@@ -323,25 +323,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("arguments.kt")
public void testArguments() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/argumentOrder/arguments.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("captured.kt")
public void testCaptured() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/argumentOrder/captured.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("capturedInExtension.kt")
@@ -413,25 +401,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/argumentOrder/simple.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("simpleInClass.kt")
public void testSimpleInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/argumentOrder/simpleInClass.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
}
@@ -9900,13 +9876,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("throwableParamOrder.kt")
public void testThrowableParamOrder() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/intrinsics/throwableParamOrder.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("tostring.kt")
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.declaration
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableAccessorDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator
import org.jetbrains.kotlin.js.translate.context.Namer
@@ -100,7 +101,10 @@ class DefaultPropertyTranslator(
function: JsFunction
) {
val host = translateHost(getterDescriptor, function)
val delegateContext = context().contextWithPropertyMetadataCreationIntrinsified(delegatedCall, descriptor, host)
val delegateContext = context()
.newDeclarationIfNecessary(getterDescriptor, function)
.contextWithPropertyMetadataCreationIntrinsified(delegatedCall, descriptor, host)
val delegatedJsCall = CallTranslator.translate(delegateContext, delegatedCall, delegateReference)
val returnResult = JsReturn(delegatedJsCall)
@@ -116,7 +120,9 @@ class DefaultPropertyTranslator(
if (delegatedCall != null) {
val host = translateHost(setterDescriptor, function)
val delegateContext = withAliased.contextWithPropertyMetadataCreationIntrinsified(delegatedCall, descriptor, host)
val delegateContext = withAliased
.newDeclarationIfNecessary(setterDescriptor, function)
.contextWithPropertyMetadataCreationIntrinsified(delegatedCall, descriptor, host)
val delegatedJsCall = CallTranslator.translate(delegateContext, delegatedCall, delegateReference)
function.addStatement(delegatedJsCall.makeStmt())
}
@@ -128,6 +134,18 @@ class DefaultPropertyTranslator(
}
}
private fun TranslationContext.newDeclarationIfNecessary(
descriptor: VariableAccessorDescriptor,
function: JsFunction
): TranslationContext {
return if (descriptor.correspondingVariable !is LocalVariableDescriptor) {
newDeclaration(descriptor)
}
else {
innerBlock(function.body)
}
}
private fun translateHost(accessorDescriptor: VariableAccessorDescriptor, function: JsFunction): JsExpression {
return if (accessorDescriptor.isExtension) {
function.addParameter(getReceiverParameterName(), 0).name.makeRef()
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.operation;
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.util.AstUtil;
@@ -28,6 +29,7 @@ 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;
@@ -64,12 +66,15 @@ public abstract class IncrementTranslator extends AbstractTranslator {
@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()).getCached();
this.accessTranslator = getAccessTranslator(baseExpression, context().innerBlock(accessBlock)).getCached();
}
@NotNull
@@ -87,8 +92,17 @@ public abstract class IncrementTranslator extends AbstractTranslator {
// generate: expr(a = a.inc(), a)
JsExpression getExpression = accessTranslator.translateAsGet();
JsExpression reassignment = variableReassignment(getExpression);
accessBlock.getStatements().add(JsAstUtils.asSyntheticStatement(reassignment));
JsExpression getNewValue = accessTranslator.translateAsGet();
JsExpression result = new JsBinaryOperation(JsBinaryOperator.COMMA, reassignment, getNewValue);
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;
}
@@ -99,8 +113,19 @@ public abstract class IncrementTranslator extends AbstractTranslator {
// code fragment: expr(a++)
// generate: expr( (t1 = a, t2 = t1, a = t1.inc(), t2) )
TemporaryVariable t1 = context().declareTemporary(accessTranslator.translateAsGet());
accessBlock.getStatements().add(t1.assignmentStatement());
JsExpression variableReassignment = variableReassignment(t1.reference());
JsExpression result = AstUtil.newSequence(t1.assignmentExpression(), variableReassignment, t1.reference());
accessBlock.getStatements().add(JsAstUtils.asSyntheticStatement(variableReassignment));
JsExpression result;
if (accessBlock.getStatements().size() == 2) {
result = AstUtil.newSequence(t1.assignmentExpression(), variableReassignment, t1.reference());
}
else {
context().getCurrentBlock().getStatements().addAll(accessBlock.getStatements());
result = t1.reference();
}
MetadataProperties.setSynthetic(result, true);
return result;
}
@@ -116,6 +141,7 @@ public abstract class IncrementTranslator extends AbstractTranslator {
private static boolean isDynamic(TranslationContext context, KtUnaryExpression expression) {
CallableDescriptor operationDescriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression);
assert operationDescriptor != null;
return DynamicCallsKt.isDynamic(operationDescriptor);
}
}
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.js.translate.reference
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
import com.intellij.util.SmartList
import org.jetbrains.kotlin.coroutines.isSuspendLambda
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.backend.ast.*
@@ -78,20 +77,16 @@ class CallArgumentTranslator private constructor(
val valueArgumentsByIndex = resolvedCall.valueArgumentsByIndex ?: throw IllegalStateException(
"Failed to arrange value arguments by index: " + resolvedCall.resultingDescriptor)
var argsBeforeVararg: List<JsExpression>? = null
var argumentsShouldBeExtractedToTmpVars = false
val argContexts = SmartList<TranslationContext>()
var concatArguments: MutableList<JsExpression>? = null
val argsToJsExpr = translateUnresolvedArguments(context(), resolvedCall)
for (parameterDescriptor in valueParameters) {
val actualArgument = valueArgumentsByIndex[parameterDescriptor.index]
val argContext = context().innerBlock()
if (actualArgument is VarargValueArgument) {
val arguments = actualArgument.getArguments()
val size = arguments.size
if (!hasSpreadOperator) {
hasSpreadOperator = arguments.any { it.getSpreadElement() != null }
}
@@ -99,35 +94,25 @@ class CallArgumentTranslator private constructor(
if (hasSpreadOperator) {
if (isNativeFunctionCall) {
argsBeforeVararg = result
result = SmartList<JsExpression>()
val list = SmartList<JsExpression>()
translateValueArguments(arguments, list, argContext)
concatArguments = prepareConcatArguments(arguments, list)
result = mutableListOf<JsExpression>()
concatArguments = prepareConcatArguments(arguments, translateResolvedArgument(actualArgument, argsToJsExpr))
}
else {
translateVarargArgument(arguments, result, argContext, size > 1)
result.addAll(translateVarargArgument(actualArgument, argsToJsExpr, actualArgument.arguments.size > 1))
}
}
else {
if (isNativeFunctionCall) {
translateValueArguments(arguments, result, argContext)
result.addAll(translateResolvedArgument(actualArgument, argsToJsExpr))
}
else {
translateVarargArgument(arguments, result, argContext, true)
result.addAll(translateVarargArgument(actualArgument, argsToJsExpr, true))
}
}
}
else {
translateSingleArgument(parameterDescriptor, actualArgument, result, argContext)
result.addAll(translateResolvedArgument(actualArgument, argsToJsExpr))
}
context().moveVarsFrom(argContext)
argContexts.add(argContext)
argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty()
}
if (argumentsShouldBeExtractedToTmpVars) {
extractArguments(result, argContexts, context())
}
if (isNativeFunctionCall && hasSpreadOperator) {
@@ -140,7 +125,7 @@ class CallArgumentTranslator private constructor(
concatArguments.add(0, JsArrayLiteral(argsBeforeVararg).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE })
}
result = SmartList(concatArgumentsIfNeeded(concatArguments))
result = mutableListOf(concatArgumentsIfNeeded(concatArguments))
if (receiver != null) {
cachedReceiver = context().getOrDeclareTemporaryConstVariable(receiver)
@@ -162,6 +147,54 @@ class CallArgumentTranslator private constructor(
return ArgumentsInfo(result, hasSpreadOperator, cachedReceiver)
}
private fun translateUnresolvedArguments(
context: TranslationContext,
resolvedCall: ResolvedCall<*>
): Map<ValueArgument, JsExpression> {
val argsToParameters = resolvedCall.valueArguments
.flatMap { (param, args) -> args.arguments.map { param to it } }
.associate { (param, arg) -> arg to param }
val argumentContexts = resolvedCall.call.valueArguments.associate { it to context.innerBlock() }
var result = resolvedCall.call.valueArguments.associate { arg ->
val argumentContext = argumentContexts[arg]!!
val argumentExpression = KtPsiUtil.deparenthesize(arg.getArgumentExpression())
val lambdaDescriptor = argumentExpression?.let { argumentContext.extractLambda(it) }
val jsExpr = if (lambdaDescriptor?.isSuspendLambda == true) {
val lambdaExpression = (argumentExpression as KtLambdaExpression).functionLiteral
val param = argsToParameters[arg]!!
LiteralFunctionTranslator(argumentContext).translate(lambdaExpression, param.type)
}
else {
Translation.translateAsExpression(arg.getArgumentExpression()!!, argumentContext)
}
arg to jsExpr
}
val resolvedOrder = resolvedCall.valueArgumentsByIndex.orEmpty()
.flatMap { it.arguments }
.withIndex()
.associate { (index, arg) -> arg to index }
val argumentsAreOrdered = resolvedCall.call.valueArguments.withIndex().none { (index, arg) -> resolvedOrder[arg] != index }
if (argumentContexts.values.any { !it.currentBlockIsEmpty() } || !argumentsAreOrdered) {
result = result.map { (arg, expr) ->
val argumentContext = argumentContexts[arg]!!
arg to argumentContext.cacheExpressionIfNeeded(expr)
}.toMap()
}
argumentContexts.values.forEach {
context.moveVarsFrom(it)
context.addStatementsToCurrentBlockFrom(it)
}
return result
}
companion object {
@JvmStatic fun translate(resolvedCall: ResolvedCall<*>, receiver: JsExpression?, context: TranslationContext): ArgumentsInfo {
@@ -184,33 +217,12 @@ class CallArgumentTranslator private constructor(
return result
}
private fun translateSingleArgument(
parameterDescriptor: ValueParameterDescriptor,
actualArgument: ResolvedValueArgument,
result: MutableList<JsExpression>,
context: TranslationContext
) {
val valueArguments = actualArgument.arguments
if (actualArgument is DefaultValueArgument) {
result += Namer.getUndefinedExpression()
return
}
assert(actualArgument is ExpressionValueArgument)
assert(valueArguments.size == 1)
val parenthesizedArgumentExpression = valueArguments[0].getArgumentExpression()!!
val argumentExpression = KtPsiUtil.deparenthesize(parenthesizedArgumentExpression)
val lambdaDescriptor = argumentExpression?.let { context.extractLambda(it) }
if (lambdaDescriptor?.isSuspendLambda == true) {
val lambdaExpression = (argumentExpression as KtLambdaExpression).functionLiteral
result += LiteralFunctionTranslator(context).translate(lambdaExpression, parameterDescriptor.type)
}
else {
result += Translation.translateAsExpression(parenthesizedArgumentExpression, context)
}
private fun translateResolvedArgument(
resolvedArgument: ResolvedValueArgument,
translatedArgs: Map<ValueArgument, JsExpression>
): List<JsExpression> {
if (resolvedArgument is DefaultValueArgument) return listOf(Namer.getUndefinedExpression())
return resolvedArgument.arguments.map { translatedArgs[it]!! }
}
private fun TranslationContext.extractLambda(expression: KtExpression): CallableDescriptor? {
@@ -218,49 +230,30 @@ class CallArgumentTranslator private constructor(
return BindingUtils.getFunctionDescriptor(bindingContext(), lambdaExpression.functionLiteral)
}
private fun translateVarargArgument(arguments: List<ValueArgument>, result: MutableList<JsExpression>,
context: TranslationContext, shouldWrapVarargInArray: Boolean) {
private fun translateVarargArgument(
resolvedArgument: ResolvedValueArgument,
translatedArgs: Map<ValueArgument, JsExpression>,
shouldWrapVarargInArray: Boolean
): List<JsExpression> {
val arguments = resolvedArgument.arguments
if (arguments.isEmpty()) {
if (shouldWrapVarargInArray) {
result.add(JsArrayLiteral(listOf<JsExpression>()).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE })
return if (shouldWrapVarargInArray) {
return listOf(JsArrayLiteral(listOf<JsExpression>()).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE })
}
else {
listOf()
}
return
}
val list: MutableList<JsExpression> = if (shouldWrapVarargInArray) {
if (arguments.size == 1) SmartList<JsExpression>() else ArrayList<JsExpression>(arguments.size)
}
else {
result
}
val list = translateResolvedArgument(resolvedArgument, translatedArgs)
translateValueArguments(arguments, list, context)
if (shouldWrapVarargInArray) {
return if (shouldWrapVarargInArray) {
val concatArguments = prepareConcatArguments(arguments, list)
val concatExpression = concatArgumentsIfNeeded(concatArguments)
result.add(concatExpression)
listOf(concatExpression)
}
else if (result.size == 1) {
result[0] = JsAstUtils.invokeMethod(result[0], "slice")
}
}
private fun translateValueArguments(arguments: List<ValueArgument>, list: MutableList<JsExpression>,
context: TranslationContext) {
val argContexts = SmartList<TranslationContext>()
var argumentsShouldBeExtractedToTmpVars = false
for (argument in arguments) {
val argumentExpression = argument.getArgumentExpression()!!
val argContext = context.innerBlock()
val argExpression = Translation.translateAsExpression(argumentExpression, argContext)
list.add(argExpression)
context.moveVarsFrom(argContext)
argContexts.add(argContext)
argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty()
}
if (argumentsShouldBeExtractedToTmpVars) {
extractArguments(list, argContexts, context)
else {
listOf(JsAstUtils.invokeMethod(list[0], "slice"))
}
}
@@ -280,8 +273,8 @@ class CallArgumentTranslator private constructor(
assert(arguments.isNotEmpty()) { "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 concatArguments = mutableListOf<JsExpression>()
var lastArrayContent = mutableListOf<JsExpression>()
val size = arguments.size
for (index in 0..size - 1) {
@@ -292,7 +285,7 @@ class CallArgumentTranslator private constructor(
if (lastArrayContent.size > 0) {
concatArguments.add(JsArrayLiteral(lastArrayContent).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE })
concatArguments.add(expressionArgument)
lastArrayContent = SmartList<JsExpression>()
lastArrayContent = mutableListOf<JsExpression>()
}
else {
concatArguments.add(expressionArgument)
@@ -308,27 +301,11 @@ class CallArgumentTranslator private constructor(
return concatArguments
}
private fun extractArguments(argExpressions: MutableList<JsExpression>, argContexts: List<TranslationContext>,
context: TranslationContext) {
for (i in argExpressions.indices) {
val argContext = argContexts[i]
val jsArgExpression = argExpressions[i]
if (argContext.currentBlockIsEmpty() && TranslationUtils.isCacheNeeded(jsArgExpression)) {
argExpressions[i] = context.defineTemporary(jsArgExpression)
}
else {
context.addStatementsToCurrentBlockFrom(argContext)
}
}
}
}
}
fun Map<TypeParameterDescriptor, KotlinType>.buildReifiedTypeArgs(context: TranslationContext): List<JsExpression> {
val reifiedTypeArguments = SmartList<JsExpression>()
val reifiedTypeArguments = mutableListOf<JsExpression>()
val patternTranslator = PatternTranslator.newInstance(context)
for (param in keys.sortedBy { it.index }) {
@@ -259,8 +259,31 @@ public final class TranslationUtils {
}
public static boolean isCacheNeeded(@NotNull JsExpression expression) {
return !(expression instanceof JsLiteral.JsValueLiteral) &&
(!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null);
if (expression instanceof JsLiteral.JsValueLiteral) return false;
if (expression instanceof JsNameRef && ((JsNameRef) expression).getQualifier() == null) return false;
if (expression instanceof JsBinaryOperation) {
JsBinaryOperation operation = (JsBinaryOperation) expression;
JsBinaryOperator operator = operation.getOperator();
if (operator.isAssignment() || operator == COMMA) return true;
return isCacheNeeded(operation.getArg1()) || isCacheNeeded(operation.getArg2());
}
if (expression instanceof JsUnaryOperation) {
JsUnaryOperation operation = (JsUnaryOperation) expression;
JsUnaryOperator operator = operation.getOperator();
switch (operator) {
case BIT_NOT:
case NEG:
case POS:
case NOT:
case TYPEOF:
case VOID:
return isCacheNeeded(operation.getArg());
default:
return true;
}
}
return true;
}
@NotNull