JS backend: fix default arguments

generate special statements, like this:
 if (arg1 === void(0))
    arg1 = value1
This commit is contained in:
Erokhin Stanislav
2013-09-27 13:20:31 +04:00
parent b2788cad97
commit 8103626121
21 changed files with 251 additions and 134 deletions
@@ -1,54 +0,0 @@
/*
* Copyright 2010-2013 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.k2js.test.semantics;
import junit.framework.Test;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.k2js.test.BasicTest;
import org.jetbrains.k2js.test.SingleFileTranslationTest;
import org.jetbrains.k2js.translate.context.Namer;
@SuppressWarnings("JUnitTestCaseWithNoTests")
public final class AdditionalTest extends SingleFileTranslationTest {
@NotNull
private final String filename;
@SuppressWarnings("JUnitTestCaseWithNonTrivialConstructors")
public AdditionalTest(@NotNull String filename) {
super("additional/");
this.filename = filename;
}
@Override
public void runTest() throws Exception {
runFunctionOutputTest(filename, Namer.getRootNamespaceName(), "box", "OK");
}
public static Test suite() throws Exception {
return TranslatorTestCaseBuilder
.suiteForDirectory(BasicTest.pathToTestFilesRoot() + "additional/cases/", new TranslatorTestCaseBuilder.NamedTestFactory() {
@NotNull
@Override
public Test createTest(@NotNull String filename) {
AdditionalTest examplesTest = new AdditionalTest(filename);
examplesTest.setName(filename);
return examplesTest;
}
});
}
}
@@ -0,0 +1,57 @@
package org.jetbrains.k2js.test.semantics;
import org.jetbrains.k2js.test.SingleFileTranslationTest;
public class DefaultArgumentsTest extends SingleFileTranslationTest {
public DefaultArgumentsTest() {
super("defaultArguments/");
}
public void testConstructorCallWithDefArg1() throws Exception {
checkFooBoxIsOk();
}
public void testConstructorCallWithDefArg2() throws Exception {
checkFooBoxIsOk();
}
public void testDefArgsWithSuperCall() throws Exception {
checkFooBoxIsOk();
}
public void testEnumWithDefArg() throws Exception {
checkFooBoxIsOk();
}
public void testEnumWithOneDefArg() throws Exception {
checkFooBoxIsOk();
}
public void testEnumWithTwoDefArgs() throws Exception {
checkFooBoxIsOk();
}
public void testExtensionFunWithDefArgs() throws Exception {
checkFooBoxIsOk();
}
public void testFunInAbstractClassWithDefArg() throws Exception {
checkFooBoxIsOk();
}
public void testOverloadFunWithDefArg() throws Exception {
checkFooBoxIsOk();
}
public void testOverrideValWithDefaultValue() throws Exception {
checkFooBoxIsOk();
}
public void testVirtualCallWithDefArg() throws Exception {
checkFooBoxIsOk();
}
}
@@ -164,6 +164,8 @@ public final class Namer {
private final JsName objectName;
@NotNull
private final JsName enumEntriesName;
@NotNull
private final JsExpression undefinedExpression;
@NotNull
private final JsName isTypeName;
@@ -181,6 +183,8 @@ public final class Namer {
objectName = kotlinScope.declareName(OBJECT_OBJECT_NAME);
isTypeName = kotlinScope.declareName("isType");
undefinedExpression = new JsPrefixOperation(JsUnaryOperator.VOID, rootScope.getProgram().getNumberLiteral(0));
}
@NotNull
@@ -278,4 +282,9 @@ public final class Namer {
public JsInvocation enumEntriesObjectCreateInvocation() {
return new JsInvocation(enumEntriesCreationMethodReference());
}
@NotNull
public JsExpression getUndefinedExpression() {
return undefinedExpression;
}
}
@@ -20,6 +20,7 @@ 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.jet.lang.descriptors.CallableDescriptor;
import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
@@ -27,11 +28,14 @@ import org.jetbrains.jet.lang.psi.JetClassOrObject;
import org.jetbrains.jet.lang.psi.JetDelegationSpecifier;
import org.jetbrains.jet.lang.psi.JetDelegatorToSuperCall;
import org.jetbrains.jet.lang.psi.JetParameter;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.k2js.translate.context.Namer;
import org.jetbrains.k2js.translate.context.TranslationContext;
import org.jetbrains.k2js.translate.general.AbstractTranslator;
import org.jetbrains.k2js.translate.reference.CallArgumentTranslator;
import java.util.ArrayList;
import java.util.Collections;
@@ -39,8 +43,8 @@ import java.util.List;
import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments;
import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateArgumentList;
public final class ClassInitializerTranslator extends AbstractTranslator {
@NotNull
@@ -66,12 +70,14 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
mayBeAddCallToSuperMethod(result);
new InitializerVisitor(initializerStatements).traverseContainer(classDeclaration, context());
List<JsStatement> statements = result.getBody().getStatements();
statements.addAll(setDefaultValueForArguments(primaryConstructor, context()));
for (JsStatement statement : initializerStatements) {
if (statement instanceof JsBlock) {
result.getBody().getStatements().addAll(((JsBlock) statement).getStatements());
statements.addAll(((JsBlock) statement).getStatements());
}
else {
result.getBody().getStatements().add(statement);
statements.add(statement);
}
}
@@ -116,7 +122,9 @@ public final class ClassInitializerTranslator extends AbstractTranslator {
@NotNull
private List<JsExpression> translateArguments(@NotNull JetDelegatorToSuperCall superCall) {
return translateArgumentList(context(), superCall.getValueArguments());
ResolvedCall<? extends CallableDescriptor> call = context().bindingContext().get(BindingContext.RESOLVED_CALL, superCall.getCalleeExpression());
assert call != null : "ResolvedCall for superCall must be not null";
return CallArgumentTranslator.translate(call, null, context()).getTranslateArguments();
}
@Nullable
@@ -34,8 +34,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.k2js.translate.utils.BindingUtils.getDefaultArgument;
public class CallArgumentTranslator extends AbstractTranslator {
@NotNull
@@ -76,7 +74,6 @@ public class CallArgumentTranslator extends AbstractTranslator {
public static void translateSingleArgument(
@NotNull ResolvedValueArgument actualArgument,
@NotNull ValueParameterDescriptor parameterDescriptor,
@NotNull List<JsExpression> result,
@NotNull TranslationContext context,
boolean shouldWrapVarargInArray
@@ -86,8 +83,7 @@ public class CallArgumentTranslator extends AbstractTranslator {
translateVarargArgument(valueArguments, result, context, shouldWrapVarargInArray);
}
else if (actualArgument instanceof DefaultValueArgument) {
JetExpression defaultArgument = getDefaultArgument(context.bindingContext(), parameterDescriptor);
result.add(Translation.translateAsExpression(defaultArgument, context));
result.add(context.namer().getUndefinedExpression());
}
else {
assert actualArgument instanceof ExpressionValueArgument;
@@ -143,6 +139,15 @@ public class CallArgumentTranslator extends AbstractTranslator {
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();
@@ -170,7 +175,7 @@ public class CallArgumentTranslator extends AbstractTranslator {
}
}
translateSingleArgument(actualArgument, parameterDescriptor, result, context(), !isNativeFunctionCall && !hasSpreadOperator);
translateSingleArgument(actualArgument, result, context(), !isNativeFunctionCall && !hasSpreadOperator);
}
if (isNativeFunctionCall && hasSpreadOperator) {
@@ -187,6 +192,8 @@ public class CallArgumentTranslator extends AbstractTranslator {
result.add(0, JsLiteral.NULL);
}
}
removeLastUndefinedArguments(result);
return new ArgumentsInfo(result, hasSpreadOperator, cachedReceiver);
}
@@ -145,17 +145,16 @@ public final class InlinedCallExpressionTranslator extends AbstractCallExpressio
@NotNull
private TemporaryVariable createAliasForArgument(@NotNull ValueParameterDescriptor parameterDescriptor) {
ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex());
JsExpression translatedArgument = translateArgument(parameterDescriptor, actualArgument);
JsExpression translatedArgument = translateArgument(actualArgument);
TemporaryVariable aliasForArgument = context().declareTemporary(translatedArgument);
context().addStatementToCurrentBlock(aliasForArgument.assignmentExpression().makeStmt());
return aliasForArgument;
}
@NotNull
private JsExpression translateArgument(@NotNull ValueParameterDescriptor parameterDescriptor,
@NotNull ResolvedValueArgument actualArgument) {
private JsExpression translateArgument(@NotNull ResolvedValueArgument actualArgument) {
List<JsExpression> result = new SmartList<JsExpression>();
translateSingleArgument(actualArgument, parameterDescriptor, result, context(), true);
translateSingleArgument(actualArgument, result, context(), true);
assert result.size() == 1 : "We always wrap varargs in kotlin calls.";
return result.get(0);
}
@@ -16,12 +16,10 @@
package org.jetbrains.k2js.translate.utils;
import com.google.dart.compiler.backend.js.ast.JsBlock;
import com.google.dart.compiler.backend.js.ast.JsExpression;
import com.google.dart.compiler.backend.js.ast.JsNode;
import com.google.dart.compiler.backend.js.ast.JsReturn;
import com.google.dart.compiler.backend.js.ast.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody;
import org.jetbrains.jet.lang.psi.JetExpression;
import org.jetbrains.jet.lang.types.JetType;
@@ -31,7 +29,13 @@ import org.jetbrains.k2js.translate.general.AbstractTranslator;
import org.jetbrains.k2js.translate.general.Translation;
import org.jetbrains.k2js.translate.utils.mutator.Mutator;
import java.util.ArrayList;
import java.util.List;
import static org.jetbrains.k2js.translate.utils.BindingUtils.getDefaultArgument;
import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToBlock;
import static org.jetbrains.k2js.translate.utils.JsAstUtils.equality;
import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
public final class FunctionBodyTranslator extends AbstractTranslator {
@@ -43,6 +47,23 @@ public final class FunctionBodyTranslator extends AbstractTranslator {
return (new FunctionBodyTranslator(descriptor, declarationWithBody, functionBodyContext)).translate();
}
@NotNull
public static List<JsStatement> setDefaultValueForArguments(@NotNull FunctionDescriptor descriptor,
@NotNull TranslationContext functionBodyContext) {
List<JsStatement> result = new ArrayList<JsStatement>();
for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
if (valueParameter.hasDefaultValue()) {
JsNameRef jsNameRef = functionBodyContext.getNameForDescriptor(valueParameter).makeRef();
JetExpression defaultArgument = getDefaultArgument(functionBodyContext.bindingContext(), valueParameter);
JsExpression defaultValue = Translation.translateAsExpression(defaultArgument, functionBodyContext);
JsBinaryOperation checkArgIsUndefined = equality(jsNameRef, functionBodyContext.namer().getUndefinedExpression());
JsIf jsIf = new JsIf(checkArgIsUndefined, assignment(jsNameRef, defaultValue).makeStmt());
result.add(jsIf);
}
}
return result;
}
@NotNull
private final FunctionDescriptor descriptor;
@@ -61,7 +82,9 @@ public final class FunctionBodyTranslator extends AbstractTranslator {
private JsBlock translate() {
JetExpression jetBodyExpression = declaration.getBodyExpression();
assert jetBodyExpression != null : "Cannot translate a body of an abstract function.";
return mayBeWrapWithReturn(Translation.translateExpression(jetBodyExpression, context()));
JsBlock jsBlock = new JsBlock(setDefaultValueForArguments(descriptor, context()));
jsBlock.getStatements().addAll(mayBeWrapWithReturn(Translation.translateExpression(jetBodyExpression, context())).getStatements());
return jsBlock;
}
@NotNull
@@ -18,7 +18,6 @@ package org.jetbrains.k2js.translate.utils;
import com.google.dart.compiler.backend.js.ast.*;
import com.intellij.openapi.util.Pair;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
@@ -130,27 +129,6 @@ public final class TranslationUtils {
return new JsConditional(testExpression, thenExpression, elseExpression);
}
@NotNull
public static List<JsExpression> translateArgumentList(@NotNull TranslationContext context,
@NotNull List<? extends ValueArgument> jetArguments) {
if (jetArguments.isEmpty()) {
return Collections.emptyList();
}
List<JsExpression> jsArguments = new SmartList<JsExpression>();
for (ValueArgument argument : jetArguments) {
jsArguments.add(translateArgument(context, argument));
}
return jsArguments;
}
@NotNull
private static JsExpression translateArgument(@NotNull TranslationContext context, @NotNull ValueArgument argument) {
JetExpression jetExpression = argument.getArgumentExpression();
assert jetExpression != null : "Argument with no expression";
return Translation.translateAsExpression(jetExpression, context);
}
@NotNull
public static JsNameRef backingFieldReference(@NotNull TranslationContext context,
@NotNull PropertyDescriptor descriptor) {
@@ -1,15 +0,0 @@
class C() {
fun Any.toMyPrefixedString(prefix: String = "", suffix: String="") : String = prefix + " " + suffix
fun testReceiver() : String {
val res : String = "mama".toMyPrefixedString("111", "222")
return res
}
}
fun box() : String {
if(C().testReceiver() != "111 222") return "fail"
return "OK"
}
@@ -1,20 +0,0 @@
trait A {
fun bar2(arg: Int = 239) : Int
fun bar(arg: Int = 240) : Int = bar2(arg/2)
}
open abstract class B() : A {
override fun bar2(arg: Int) : Int = arg
}
class C() : B() {
}
fun box() : String {
if(C().bar(10) != 5) return "fail"
if(C().bar() != 120) return "fail"
if(C().bar2() != 239) return "fail"
if(C().bar2(10) != 10) return "fail"
return "OK"
}
@@ -0,0 +1,30 @@
package foo
class A(val a: Int = 0)
class B(val a: Int = 0, val b: String = "a")
fun box(): String {
val a0 = A()
val a1 = A(1)
if (a0.a != 0) return "a0.a != 0, it: ${a0.a}"
if (a1.a != 1) return "a1.a != 1, it: ${a1.a}"
val b1 = B()
if (b1.a != 0) return "b1.a != 0, it: ${b1.a}"
if (b1.b != "a") return "b1.b != 'a', it: ${b1.b}"
val b2 = B(1)
if (b2.a != 1) return "b2.a != 1, it: ${b2.a}"
if (b2.b != "a") return "b2.b != 'a', it: ${b2.b}"
val b3 = B(b = "b")
if (b3.a != 0) return "b3.a != 0, it: ${b3.a}"
if (b3.b != "b") return "b3.b != 'b', it: ${b3.b}"
val b4 = B(2, "c")
if (b4.a != 2) return "b4.a != 2, it: ${b4.a}"
if (b4.b != "c") return "b4.b != 'c', it: ${b4.b}"
return "OK"
}
@@ -1,3 +1,5 @@
package foo
class T4(
val c1: Boolean,
val c2: Boolean,
@@ -0,0 +1,12 @@
package foo
open class A(val a: Int = 1, val b: Int = 2)
class B : A(b = 3)
fun box(): String {
val b = B()
if (b.a != 1) return "b.a != 1, it: ${b.a}"
if (b.b != 3) return "b.a != 3, it: ${b.b}"
return "OK"
}
@@ -0,0 +1,12 @@
package foo
enum class A(val a: Int = 1) {
FIRST: A()
SECOND: A(2)
}
fun box(): String {
if (A.FIRST.a == 1 && A.SECOND.a == 2) {
return "OK"
}
return "fail"
}
@@ -0,0 +1,11 @@
package foo
enum class Foo(val a: Int = 1, val b: String) {
B: Foo(2, "b")
C: Foo(b = "b")
}
fun box(): String {
if (Foo.B.a != 2 || Foo.B.b != "b") return "fail1"
if (Foo.C.a != 1 || Foo.C.b != "b") return "fail2"
return "OK"
}
@@ -0,0 +1,15 @@
package foo
enum class Foo(val a: Int = 1, val b: String = "a") {
A: Foo()
B: Foo(2, "b")
C: Foo(b = "b")
D: Foo(a = 2)
}
fun box(): String {
if (Foo.A.a != 1 || Foo.A.b != "a") return "fail1"
if (Foo.B.a != 2 || Foo.B.b != "b") return "fail2"
if (Foo.C.a != 1 || Foo.C.b != "b") return "fail3"
if (Foo.D.a != 2 || Foo.D.b != "a") return "fail4"
return "OK"
}
@@ -1,3 +1,5 @@
package foo
fun <T> T.toPrefixedString(prefix: String = "", suffix: String="") = prefix + toString() + suffix
fun box() : String {
@@ -1,3 +1,5 @@
package foo
open abstract class B() {
fun foo(arg: Int = 239 + 1) : Int = arg
}
@@ -6,7 +8,7 @@ class C() : B() {
}
fun box() : String {
if(C().foo(10) != 10) return "fail"
if(C().foo() != 240) return "fail"
if(C().foo(10) != 10) return "fail1"
if(C().foo() != 240) return "fail2"
return "OK"
}
@@ -1,3 +1,5 @@
package foo
open abstract class B() {
abstract fun foo2(arg: Int = 239) : Int
}
@@ -7,7 +9,7 @@ class C() : B() {
}
fun box() : String {
if(C().foo2() != 239) return "fail"
if(C().foo2(10) != 10) return "fail"
if(C().foo2() != 239) return "fail1"
if(C().foo2(10) != 10) return "fail2"
return "OK"
}
@@ -0,0 +1,15 @@
package foo
open class A(open val bar: Int = 2) {
val barA = bar
}
class B(override val bar: Int = 3) : A()
fun box() : String {
val b = B()
if (b.bar != 3) return "b.bar != 3, it: " + b.bar
if (b.barA != 2) return "b.barA != 2, it: " + b.barA
return "OK"
}
@@ -0,0 +1,22 @@
package foo
trait A {
fun bar2(arg: Int = 239) : Int
fun bar(arg: Int = 240) : Int = bar2(arg/2)
}
open abstract class B() : A {
override fun bar2(arg: Int) : Int = arg
}
class C() : B() {
}
fun box() : String {
if(C().bar(10) != 5) return "fail1: ${C().bar(10)}"
if(C().bar() != 120) return "fail2: ${C().bar()}"
if(C().bar2() != 239) return "fail3: ${C().bar2()}"
if(C().bar2(10) != 10) return "fail4: ${C().bar2(10)}"
return "OK"
}