From 2418e23c0475c6340fcdee2be56fed07c6623d88 Mon Sep 17 00:00:00 2001 From: Pavel Talanov Date: Fri, 9 Dec 2011 13:03:40 +0400 Subject: [PATCH] virtual extension functions support and tests. --- .../expression/FunctionTranslator.java | 34 ++++++++++++++----- .../translate/reference/CallTranslator.java | 3 +- .../translate/utils/TranslationUtils.java | 14 ++++++++ .../k2js/test/ExtensionFunctionTest.java | 10 ++++++ .../cases/virtualExtension.kt | 15 ++++++++ .../cases/virtualExtensionOverride.kt | 20 +++++++++++ 6 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 translator/testFiles/extensionFunction/cases/virtualExtension.kt create mode 100644 translator/testFiles/extensionFunction/cases/virtualExtensionOverride.kt diff --git a/translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java b/translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java index 63beec7849b..5e518fae8ed 100644 --- a/translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java +++ b/translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java @@ -10,13 +10,13 @@ import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.general.AbstractTranslator; import org.jetbrains.k2js.translate.general.Translation; +import org.jetbrains.k2js.translate.utils.DescriptorUtils; 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; @@ -29,6 +29,8 @@ public final class FunctionTranslator extends AbstractTranslator { private final JetDeclarationWithBody functionDeclaration; @NotNull private final JsFunction functionObject; + @NotNull + private final TranslationContext functionBodyContext; @NotNull public static FunctionTranslator newInstance(@NotNull JetDeclarationWithBody function, @@ -41,6 +43,7 @@ public final class FunctionTranslator extends AbstractTranslator { super(context); this.functionDeclaration = functionDeclaration; this.functionObject = createFunctionObject(); + this.functionBodyContext = functionBodyContext(); } @NotNull @@ -61,12 +64,18 @@ public final class FunctionTranslator extends AbstractTranslator { @NotNull private JsFunction generateFunctionObject() { - TranslationContext innerContext = functionBodyContext(); - functionObject.setParameters(translateParameters(functionDeclaration.getValueParameters(), innerContext)); - functionObject.setBody(translateBody(innerContext)); + functionObject.setParameters(translateParameters(functionDeclaration.getValueParameters(), functionBodyContext)); + functionObject.setBody(translateBody(functionBodyContext)); + restoreContext(); return functionObject; } + private void restoreContext() { + if (isExtensionFunction()) { + functionBodyContext.removeAliasForThis(); + } + } + private JsFunction createFunctionObject() { if (isDeclaration()) { return functionWithScope @@ -156,13 +165,20 @@ public final class FunctionTranslator extends AbstractTranslator { } 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"); + boolean isExtensionFunction = isExtensionFunction(); + if (isExtensionFunction) { + JsName receiver = functionBodyContext.jsScope().declareName("receiver"); context().aliaser().setAliasForThis(receiver); jsParameters.add(new JsParameter(receiver)); } } + + private boolean isExtensionFunction() { + if (!(functionDeclaration instanceof JetNamedFunction)) { + return false; + } + + FunctionDescriptor functionDescriptor = getFunctionDescriptor(context().bindingContext(), functionDeclaration); + return DescriptorUtils.isExtensionFunction(functionDescriptor); + } } diff --git a/translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java b/translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java index 6b6783bb804..f5ec8a66f57 100644 --- a/translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java +++ b/translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java @@ -161,7 +161,8 @@ public final class CallTranslator extends AbstractTranslator { private JsExpression qualifiedMethodReference(@NotNull DeclarationDescriptor descriptor) { JsExpression methodReference = ReferenceTranslator.translateReference(descriptor, context()); if (isExtensionFunction()) { - AstUtil.setQualifier(methodReference, context().getQualifierForDescriptor(functionDescriptor)); + AstUtil.setQualifier(methodReference, + TranslationUtils.getExtensionFunctionImplicitReceiver(context(), functionDescriptor)); } else if (receiver != null) { AstUtil.setQualifier(methodReference, receiver); } diff --git a/translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java b/translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java index 25e1a6c7821..5929f631485 100644 --- a/translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java +++ b/translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java @@ -205,6 +205,20 @@ public final class TranslationUtils { return TranslationUtils.getThisQualifier(context); } } + return getImplicitReceiverBasedOnOwner(context, referencedDescriptor); + } + + @NotNull + public static JsExpression getExtensionFunctionImplicitReceiver(@NotNull TranslationContext context, + @NotNull FunctionDescriptor descriptor) { + JsExpression receiverBasedOnOwner = getImplicitReceiverBasedOnOwner(context, descriptor); + assert receiverBasedOnOwner != null : "Extension function must have receiver based on owner."; + return receiverBasedOnOwner; + } + + @Nullable + private static JsExpression getImplicitReceiverBasedOnOwner(@NotNull TranslationContext context, + @NotNull DeclarationDescriptor referencedDescriptor) { if (isOwnedByClass(referencedDescriptor)) { return TranslationUtils.getThisQualifier(context); } diff --git a/translator/test/org/jetbrains/k2js/test/ExtensionFunctionTest.java b/translator/test/org/jetbrains/k2js/test/ExtensionFunctionTest.java index 4e64e438af6..680ec0ee425 100644 --- a/translator/test/org/jetbrains/k2js/test/ExtensionFunctionTest.java +++ b/translator/test/org/jetbrains/k2js/test/ExtensionFunctionTest.java @@ -32,4 +32,14 @@ public final class ExtensionFunctionTest extends TranslationTest { public void extensionUsedInsideClass() throws Exception { testFooBoxIsTrue("extensionUsedInsideClass.kt"); } + + @Test + public void virtualExtension() throws Exception { + testFooBoxIsTrue("virtualExtension.kt"); + } + + @Test + public void virtualExtensionOverride() throws Exception { + testFooBoxIsTrue("virtualExtensionOverride.kt"); + } } diff --git a/translator/testFiles/extensionFunction/cases/virtualExtension.kt b/translator/testFiles/extensionFunction/cases/virtualExtension.kt new file mode 100644 index 00000000000..88ccb215549 --- /dev/null +++ b/translator/testFiles/extensionFunction/cases/virtualExtension.kt @@ -0,0 +1,15 @@ +namespace foo + +class A(var a : Int) { + + fun Int.modify() : Int { + return this * 3; + } + + fun eval() = a.modify(); +} + +fun box(): Boolean { + val a = A(4) + return (a.eval() == 12) +} diff --git a/translator/testFiles/extensionFunction/cases/virtualExtensionOverride.kt b/translator/testFiles/extensionFunction/cases/virtualExtensionOverride.kt new file mode 100644 index 00000000000..542de9f69b4 --- /dev/null +++ b/translator/testFiles/extensionFunction/cases/virtualExtensionOverride.kt @@ -0,0 +1,20 @@ +namespace foo + +open class A(var a : Int) { + + open fun Int.modify() : Int { + return this * 3; + } + + fun eval() = a.modify(); +} + +class B(a : Int) : A(a) { + override fun Int.modify() : Int { + return this - 2; + } +} + +fun box(): Boolean { + return (A(4).eval() == 12) && (A(2).eval() == 6) && (B(3).eval() == 1) +}