From 50c44db27899657013bbe57612470cc0cf9497b7 Mon Sep 17 00:00:00 2001 From: Pavel Talanov Date: Thu, 8 Dec 2011 21:54:49 +0400 Subject: [PATCH] extension functions support and tests. --- .../expression/FunctionTranslator.java | 15 ++++++++ .../translate/reference/CallTranslator.java | 20 ++++++++++- .../k2js/translate/utils/BindingUtils.java | 2 +- .../k2js/translate/utils/DescriptorUtils.java | 4 +++ .../translate/utils/TranslationUtils.java | 17 +++++---- .../k2js/test/ExtensionFunctionTest.java | 35 +++++++++++++++++++ .../testFiles/examples/cases/generic.jet | 22 ++++++++++++ .../cases/extensionFunctionOnExpression.kt | 13 +++++++ .../cases/extensionUsedInsideClass.kt | 15 ++++++++ .../cases/extensionWithImplicitReceiver.kt | 13 +++++++ .../extensionFunction/cases/intExtension.kt | 9 +++++ 11 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 translator/test/org/jetbrains/k2js/test/ExtensionFunctionTest.java create mode 100644 translator/testFiles/examples/cases/generic.jet create mode 100644 translator/testFiles/extensionFunction/cases/extensionFunctionOnExpression.kt create mode 100644 translator/testFiles/extensionFunction/cases/extensionUsedInsideClass.kt create mode 100644 translator/testFiles/extensionFunction/cases/extensionWithImplicitReceiver.kt create mode 100644 translator/testFiles/extensionFunction/cases/intExtension.kt diff --git a/translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java b/translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java index bea2bfb380c..63beec7849b 100644 --- a/translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java +++ b/translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java @@ -4,6 +4,7 @@ import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.util.AstUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; @@ -14,6 +15,8 @@ import java.util.ArrayList; import java.util.List; import static org.jetbrains.k2js.translate.utils.BindingUtils.getDescriptorForElement; +import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor; +import static org.jetbrains.k2js.translate.utils.DescriptorUtils.isExtensionFunction; import static org.jetbrains.k2js.translate.utils.TranslationUtils.functionWithScope; @@ -136,6 +139,7 @@ public final class FunctionTranslator extends AbstractTranslator { private List translateParameters(@NotNull List jetParameters, @NotNull TranslationContext functionBodyContext) { List jsParameters = new ArrayList(); + mayBeAddThisParameterForExtensionFunction(jsParameters); for (JetParameter jetParameter : jetParameters) { JsName parameterName = declareParameter(jetParameter, functionBodyContext); jsParameters.add(new JsParameter(parameterName)); @@ -150,4 +154,15 @@ public final class FunctionTranslator extends AbstractTranslator { getDescriptorForElement(functionBodyContext.bindingContext(), jetParameter); return context().declareLocalVariable(parameterDescriptor); } + + private void mayBeAddThisParameterForExtensionFunction(@NotNull List jsParameters) { + if (!(functionDeclaration instanceof JetNamedFunction)) return; + + FunctionDescriptor functionDescriptor = getFunctionDescriptor(context().bindingContext(), functionDeclaration); + if (isExtensionFunction(functionDescriptor)) { + JsName receiver = context().jsScope().declareName("receiver"); + context().aliaser().setAliasForThis(receiver); + jsParameters.add(new JsParameter(receiver)); + } + } } diff --git a/translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java b/translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java index 6e4f93206de..6b6783bb804 100644 --- a/translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java +++ b/translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java @@ -14,6 +14,7 @@ import org.jetbrains.k2js.translate.intrinsic.FunctionIntrinsic; import org.jetbrains.k2js.translate.utils.DescriptorUtils; import org.jetbrains.k2js.translate.utils.TranslationUtils; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -100,9 +101,24 @@ public final class CallTranslator extends AbstractTranslator { if (isConstructor()) { return constructorCall(); } + if (isExtensionFunction()) { + return extensionFunctionCall(); + } return methodCall(); } + @NotNull + private JsExpression extensionFunctionCall() { + List argumentList = new ArrayList(); + argumentList.add(receiver); + argumentList.addAll(arguments); + return AstUtil.newInvocation(calleeReference(), argumentList); + } + + private boolean isExtensionFunction() { + return DescriptorUtils.isExtensionFunction(functionDescriptor); + } + @NotNull private JsExpression intrinsicInvocation() { FunctionIntrinsic functionIntrinsic = context().intrinsics().getFunctionIntrinsic(functionDescriptor); @@ -144,7 +160,9 @@ public final class CallTranslator extends AbstractTranslator { @NotNull private JsExpression qualifiedMethodReference(@NotNull DeclarationDescriptor descriptor) { JsExpression methodReference = ReferenceTranslator.translateReference(descriptor, context()); - if (receiver != null) { + if (isExtensionFunction()) { + AstUtil.setQualifier(methodReference, context().getQualifierForDescriptor(functionDescriptor)); + } else if (receiver != null) { AstUtil.setQualifier(methodReference, receiver); } return methodReference; diff --git a/translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java b/translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java index 7d434893691..50e1d396813 100644 --- a/translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java +++ b/translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java @@ -52,7 +52,7 @@ public final class BindingUtils { @NotNull public static FunctionDescriptor getFunctionDescriptor(@NotNull BindingContext context, - @NotNull JetNamedFunction declaration) { + @NotNull JetDeclarationWithBody declaration) { return getDescriptorForExpression(context, declaration, FunctionDescriptor.class); } diff --git a/translator/src/org/jetbrains/k2js/translate/utils/DescriptorUtils.java b/translator/src/org/jetbrains/k2js/translate/utils/DescriptorUtils.java index 46d8a70f1d1..a5f479003ea 100644 --- a/translator/src/org/jetbrains/k2js/translate/utils/DescriptorUtils.java +++ b/translator/src/org/jetbrains/k2js/translate/utils/DescriptorUtils.java @@ -117,4 +117,8 @@ public final class DescriptorUtils { assert containing != null : "Should be called on objects that have containing declaration."; return containing; } + + public static boolean isExtensionFunction(@NotNull FunctionDescriptor functionDescriptor) { + return (functionDescriptor.getReceiverParameter().exists()); + } } diff --git a/translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java b/translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java index b573d364880..25e1a6c7821 100644 --- a/translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java +++ b/translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java @@ -15,8 +15,8 @@ import org.jetbrains.k2js.translate.general.Translation; import java.util.ArrayList; import java.util.List; -import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptorForOperationExpression; -import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptor; +import static org.jetbrains.k2js.translate.utils.BindingUtils.*; +import static org.jetbrains.k2js.translate.utils.DescriptorUtils.isExtensionFunction; /** * @author Talanov Pavel @@ -67,10 +67,10 @@ public final class TranslationUtils { public static JsNameRef backingFieldReference(@NotNull TranslationContext context, @NotNull PropertyDescriptor descriptor) { JsName backingFieldName = context.getNameForDescriptor(descriptor); - if (BindingUtils.isOwnedByClass(descriptor)) { + if (isOwnedByClass(descriptor)) { return getThisQualifiedNameReference(context, backingFieldName); } - assert BindingUtils.isOwnedByNamespace(descriptor) + assert isOwnedByNamespace(descriptor) : "Only classes and namespaces may own backing fields."; JsNameRef qualifier = context.getQualifierForDescriptor(descriptor); return AstUtil.qualified(backingFieldName, qualifier); @@ -200,10 +200,15 @@ public final class TranslationUtils { @Nullable public static JsExpression getImplicitReceiver(@NotNull TranslationContext context, @NotNull DeclarationDescriptor referencedDescriptor) { - if (BindingUtils.isOwnedByClass(referencedDescriptor)) { + if (referencedDescriptor instanceof FunctionDescriptor) { + if (isExtensionFunction((FunctionDescriptor) referencedDescriptor)) { + return TranslationUtils.getThisQualifier(context); + } + } + if (isOwnedByClass(referencedDescriptor)) { return TranslationUtils.getThisQualifier(context); } - if (BindingUtils.isOwnedByNamespace(referencedDescriptor)) { + if (isOwnedByNamespace(referencedDescriptor)) { if (context.hasQualifierForDescriptor(referencedDescriptor)) { return context.getQualifierForDescriptor(referencedDescriptor); } diff --git a/translator/test/org/jetbrains/k2js/test/ExtensionFunctionTest.java b/translator/test/org/jetbrains/k2js/test/ExtensionFunctionTest.java new file mode 100644 index 00000000000..4e64e438af6 --- /dev/null +++ b/translator/test/org/jetbrains/k2js/test/ExtensionFunctionTest.java @@ -0,0 +1,35 @@ +package org.jetbrains.k2js.test; + +import org.junit.Test; + +/** + * @author Talanov Pavel + */ +public final class ExtensionFunctionTest extends TranslationTest { + final private static String MAIN = "extensionFunction/"; + + @Override + protected String mainDirectory() { + return MAIN; + } + + @Test + public void intExtension() throws Exception { + testFooBoxIsTrue("intExtension.kt"); + } + + @Test + public void extensionWithImplicitReceiver() throws Exception { + testFooBoxIsTrue("extensionWithImplicitReceiver.kt"); + } + + @Test + public void extensionFunctionOnExpression() throws Exception { + testFooBoxIsTrue("extensionFunctionOnExpression.kt"); + } + + @Test + public void extensionUsedInsideClass() throws Exception { + testFooBoxIsTrue("extensionUsedInsideClass.kt"); + } +} diff --git a/translator/testFiles/examples/cases/generic.jet b/translator/testFiles/examples/cases/generic.jet new file mode 100644 index 00000000000..7b886440dfd --- /dev/null +++ b/translator/testFiles/examples/cases/generic.jet @@ -0,0 +1,22 @@ +import java.util.* + +fun ArrayList.findAll(predicate: fun (T) : Boolean): ArrayList { + val result = ArrayList() + for(val t in this) { + if (predicate(t)) result.add(t) + } + return result +} + + +fun box(): String { + val list: ArrayList = ArrayList() + + list.add(2) + list.add(3) + list.add(5) + + + val m: ArrayList = list.findAll({(name: Int) => name < 4}) + return if (m.size() == 2) "OK" else "fail" +} diff --git a/translator/testFiles/extensionFunction/cases/extensionFunctionOnExpression.kt b/translator/testFiles/extensionFunction/cases/extensionFunctionOnExpression.kt new file mode 100644 index 00000000000..8ff55e64aab --- /dev/null +++ b/translator/testFiles/extensionFunction/cases/extensionFunctionOnExpression.kt @@ -0,0 +1,13 @@ +namespace foo + +fun Int.same() : Int { + return this +} + +fun Int.quadruple() : Int { + return same() * 4; +} + +fun box(): Boolean { + return ((3 + 4).quadruple() == 28) +} diff --git a/translator/testFiles/extensionFunction/cases/extensionUsedInsideClass.kt b/translator/testFiles/extensionFunction/cases/extensionUsedInsideClass.kt new file mode 100644 index 00000000000..cd153bafed5 --- /dev/null +++ b/translator/testFiles/extensionFunction/cases/extensionUsedInsideClass.kt @@ -0,0 +1,15 @@ +namespace foo + +class A(var a : Int) { + fun eval() = f(); +} + +fun A.f() : Int { + a = 3 + return 10 +} + +fun box(): Boolean { + val a = A(4) + return (a.eval() == 10) && (a.a == 3) +} diff --git a/translator/testFiles/extensionFunction/cases/extensionWithImplicitReceiver.kt b/translator/testFiles/extensionFunction/cases/extensionWithImplicitReceiver.kt new file mode 100644 index 00000000000..b3464fe3f7e --- /dev/null +++ b/translator/testFiles/extensionFunction/cases/extensionWithImplicitReceiver.kt @@ -0,0 +1,13 @@ +namespace foo + +fun Int.same() : Int { + return this +} + +fun Int.quadruple() : Int { + return same() * 4; +} + +fun box(): Boolean { + return (3.quadruple() == 12) +} \ No newline at end of file diff --git a/translator/testFiles/extensionFunction/cases/intExtension.kt b/translator/testFiles/extensionFunction/cases/intExtension.kt new file mode 100644 index 00000000000..56547378501 --- /dev/null +++ b/translator/testFiles/extensionFunction/cases/intExtension.kt @@ -0,0 +1,9 @@ +namespace foo + +fun Int.quadruple() : Int { + return this * 4; +} + +fun box(): Boolean { + return (3.quadruple() == 12) +}