From 9b2dc6f5841e28cb42f456d9002e2b6b810981a6 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Wed, 28 Mar 2012 16:38:59 +0400 Subject: [PATCH] More cases supported for inline functions: namespace level functions with parameters (not blocks) --- .../k2js/test/semantics/InlineTest.java | 27 +++++- .../translate/context/TranslationContext.java | 4 + .../expression/FunctionTranslator.java | 42 ++------ .../InlinedCallExpressionTranslator.java | 90 ++++++++++++++--- .../k2js/translate/utils/DescriptorUtils.java | 13 +++ .../utils/FunctionBodyTranslator.java | 97 +++++++++++++++++++ .../utils/mutator/LastExpressionMutator.java | 2 + .../inline/cases/functionWithBlockBody.kt | 7 ++ .../inline/cases/functionWithOneParameter.kt | 9 ++ .../inline/cases/functionWithTwoParameters.kt | 13 +++ .../testFiles/inline/cases/method.kt | 11 +++ .../inline/cases/methodWithReferenceToThis.kt | 11 +++ 12 files changed, 275 insertions(+), 51 deletions(-) create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/utils/FunctionBodyTranslator.java create mode 100644 js/js.translator/testFiles/inline/cases/functionWithBlockBody.kt create mode 100644 js/js.translator/testFiles/inline/cases/functionWithOneParameter.kt create mode 100644 js/js.translator/testFiles/inline/cases/functionWithTwoParameters.kt create mode 100644 js/js.translator/testFiles/inline/cases/method.kt create mode 100644 js/js.translator/testFiles/inline/cases/methodWithReferenceToThis.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/InlineTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/InlineTest.java index fdf7fb808da..e927a9fbca9 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/InlineTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/InlineTest.java @@ -31,11 +31,34 @@ public final class InlineTest extends SingleFileTranslationTest { } public void testFunctionWithoutParameters() throws Exception { - String filename = "functionWithoutParameters.kt"; + checkFooBoxIsTrueAndFunctionNameIsNotReferenced("functionWithoutParameters.kt", "myInlineFun"); + } + + public void testFunctionWithBlockBody() throws Exception { + checkFooBoxIsTrueAndFunctionNameIsNotReferenced("functionWithBlockBody.kt", "myInlineFun"); + } + + public void testFunctionWithOneParameter() throws Exception { + checkFooBoxIsTrueAndFunctionNameIsNotReferenced("functionWithOneParameter.kt", "myInlineFun"); + } + + public void testFunctionWithTwoParameters() throws Exception { + checkFooBoxIsTrueAndFunctionNameIsNotReferenced("functionWithTwoParameters.kt", "myInlineFun"); + } + + public void testMethod() throws Exception { + checkFooBoxIsTrueAndFunctionNameIsNotReferenced("method.kt", "myInlineMethod"); + } + + public void testMethodWithReferenceToThis() throws Exception { + checkFooBoxIsTrueAndFunctionNameIsNotReferenced("methodWithReferenceToThis.kt", "myInlineMethod"); + } + + private void checkFooBoxIsTrueAndFunctionNameIsNotReferenced(@NotNull String filename, String funName) throws Exception { checkFooBoxIsTrue(filename); String generatedJSFilePath = getOutputFilePath(filename); String outputFileText = FileUtil.loadFile(new File(generatedJSFilePath)); - assertTrue(countOccurrences(outputFileText, "myInlineFun") == 1); + assertTrue(countOccurrences(outputFileText, funName) == 1); } private static int countOccurrences(@NotNull String str, @NotNull String subStr) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java index 8111e33ffd2..2ffea662b1d 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/TranslationContext.java @@ -131,6 +131,10 @@ public final class TranslationContext { @NotNull public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) { + JsName alias = aliasingContext.getAliasForDescriptor(descriptor); + if (alias != null) { + return alias; + } return staticContext.getNameForDescriptor(descriptor); } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java index b29d02fc459..07da21232a2 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java @@ -35,6 +35,7 @@ import org.jetbrains.k2js.translate.general.AbstractTranslator; import org.jetbrains.k2js.translate.general.Translation; import org.jetbrains.k2js.translate.reference.ReferenceTranslator; import org.jetbrains.k2js.translate.utils.DescriptorUtils; +import org.jetbrains.k2js.translate.utils.FunctionBodyTranslator; import org.jetbrains.k2js.translate.utils.TranslationUtils; import org.jetbrains.k2js.translate.utils.closure.ClosureContext; import org.jetbrains.k2js.translate.utils.closure.ClosureUtils; @@ -45,6 +46,7 @@ import java.util.List; import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor; import static org.jetbrains.k2js.translate.utils.DescriptorUtils.*; +import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody; import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToBlock; import static org.jetbrains.k2js.translate.utils.JsAstUtils.setParameters; import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression; @@ -64,7 +66,7 @@ public final class FunctionTranslator extends AbstractTranslator { @NotNull private final TranslationContext functionBodyContext; @Nullable - private TemporaryVariable aliasForContaingClassThis = null; + private TemporaryVariable aliasForContainingClassThis = null; @NotNull private final JetDeclarationWithBody functionDeclaration; @Nullable @@ -106,8 +108,8 @@ public final class FunctionTranslator extends AbstractTranslator { return getContextWithFunctionBodyBlock(); } JsExpression thisQualifier = TranslationUtils.getThisObject(context(), containingClass); - aliasForContaingClassThis = context().declareTemporary(thisQualifier); - return getContextWithFunctionBodyBlock().innerContextWithThisAliased(containingClass, aliasForContaingClassThis.name()); + aliasForContainingClassThis = context().declareTemporary(thisQualifier); + return getContextWithFunctionBodyBlock().innerContextWithThisAliased(containingClass, aliasForContainingClassThis.name()); } @NotNull @@ -152,8 +154,8 @@ public final class FunctionTranslator extends AbstractTranslator { if (!shouldAliasThisObject()) { return functionObject; } - assert aliasForContaingClassThis != null; - return AstUtil.newSequence(aliasForContaingClassThis.assignmentExpression(), functionObject); + assert aliasForContainingClassThis != null; + return AstUtil.newSequence(aliasForContainingClassThis.assignmentExpression(), functionObject); } private boolean shouldAliasThisObject() { @@ -197,37 +199,9 @@ public final class FunctionTranslator extends AbstractTranslator { assert descriptor.getModality().equals(Modality.ABSTRACT); return; } - JsNode realBody = Translation.translateExpression(jetBodyExpression, functionBodyContext); - functionBody.getStatements().add(mayBeWrapWithReturn(realBody)); + functionBody.getStatements().add(translateFunctionBody(descriptor, functionDeclaration, functionBodyContext)); } - @NotNull - private JsBlock mayBeWrapWithReturn(@NotNull JsNode body) { - if (!mustAddReturnToGeneratedFunctionBody()) { - return convertToBlock(body); - } - return convertToBlock(lastExpressionReturned(body)); - } - - private boolean mustAddReturnToGeneratedFunctionBody() { - JetType functionReturnType = descriptor.getReturnType(); - assert functionReturnType != null : "Function return typed type must be resolved."; - return (!functionDeclaration.hasBlockBody()) && (!JetStandardClasses.isUnit(functionReturnType)); - } - - @NotNull - private static JsNode lastExpressionReturned(@NotNull JsNode body) { - return mutateLastExpression(body, new Mutator() { - @Override - @NotNull - public JsNode mutate(@NotNull JsNode node) { - if (!(node instanceof JsExpression)) { - return node; - } - return new JsReturn((JsExpression)node); - } - }); - } @NotNull private List translateParameters() { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/InlinedCallExpressionTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/InlinedCallExpressionTranslator.java index e9909e3f7ac..d6e9f9c2a2b 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/InlinedCallExpressionTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/InlinedCallExpressionTranslator.java @@ -19,25 +19,28 @@ package org.jetbrains.k2js.translate.reference; import com.google.common.collect.Maps; import com.google.dart.compiler.backend.js.ast.JsExpression; import com.google.dart.compiler.backend.js.ast.JsName; +import com.google.dart.compiler.backend.js.ast.JsNode; +import com.google.dart.compiler.backend.js.ast.JsReturn; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.descriptors.CallableDescriptor; -import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; -import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor; -import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; +import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.JetCallExpression; import org.jetbrains.jet.lang.psi.JetFunction; import org.jetbrains.jet.lang.resolve.calls.ResolvedCall; import org.jetbrains.jet.lang.resolve.calls.ResolvedValueArgument; import org.jetbrains.k2js.translate.context.TemporaryVariable; import org.jetbrains.k2js.translate.context.TranslationContext; -import org.jetbrains.k2js.translate.general.Translation; -import org.jetbrains.k2js.translate.utils.BindingUtils; +import org.jetbrains.k2js.translate.utils.DescriptorUtils; +import org.jetbrains.k2js.translate.utils.JsAstUtils; +import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator; +import org.jetbrains.k2js.translate.utils.mutator.Mutator; import java.util.List; import java.util.Map; +import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionForDescriptor; import static org.jetbrains.k2js.translate.utils.BindingUtils.getResolvedCallForCallExpression; +import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody; /** * @author Pavel Talanov @@ -68,9 +71,18 @@ public final class InlinedCallExpressionTranslator extends AbstractCallExpressio @NotNull private JsExpression translate() { - TranslationContext contextWithAllParametersAliased = createContextWithAllParametersAliased(); - JetFunction function = BindingUtils.getFunctionForDescriptor(bindingContext(), getFunctionDescriptor()); - return Translation.translateAsExpression(function.getBodyExpression(), contextWithAllParametersAliased); + TranslationContext contextWithAllParametersAliased = createContextForInlining(); + JsNode translatedBody = translateFunctionBody(getFunctionDescriptor(), getFunctionBody(), contextWithAllParametersAliased); + //TODO: declare uninitialized temporary + TemporaryVariable temporaryVariable = contextWithAllParametersAliased.declareTemporary(program().getNullLiteral()); + JsNode mutatedBody = LastExpressionMutator.mutateLastExpression(translatedBody, new InlineFunctionMutator(temporaryVariable)); + context().addStatementToCurrentBlock(JsAstUtils.convertToBlock(mutatedBody)); + return temporaryVariable.reference(); + } + + @NotNull + private JetFunction getFunctionBody() { + return getFunctionForDescriptor(bindingContext(), getFunctionDescriptor()); } @NotNull @@ -80,16 +92,44 @@ public final class InlinedCallExpressionTranslator extends AbstractCallExpressio return (SimpleFunctionDescriptor)descriptor; } - private TranslationContext createContextWithAllParametersAliased() { + @NotNull + private TranslationContext createContextForInlining() { + TranslationContext contextForInlining = context(); + contextForInlining = createContextWithAliasForThisExpression(contextForInlining); + return createContextWithAliasesForParameters(contextForInlining); + } + + @NotNull + private TranslationContext createContextWithAliasesForParameters(@NotNull TranslationContext contextForInlining) { Map aliases = Maps.newHashMap(); for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) { - ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex()); - JsExpression translatedArgument = translateArgument(parameterDescriptor, actualArgument); - TemporaryVariable aliasForArgument = context().declareTemporary(translatedArgument); + TemporaryVariable aliasForArgument = createAliasForArgument(parameterDescriptor); aliases.put(parameterDescriptor, aliasForArgument.name()); - context().addStatementToCurrentBlock(aliasForArgument.assignmentExpression().makeStmt()); } - return context().innerContextWithDescriptorsAliased(aliases); + return contextForInlining.innerContextWithDescriptorsAliased(aliases); + } + + @NotNull + private TranslationContext createContextWithAliasForThisExpression(@NotNull TranslationContext contextForInlining) { + ClassDescriptor classDescriptorForMethod = DescriptorUtils.getClassDescriptorForMethod(getFunctionDescriptor()); + if (classDescriptorForMethod == null) { + return contextForInlining; + } + //TODO + TranslationContext contextWithAliasForThisExpression = contextForInlining; + TemporaryVariable aliasForThis = contextForInlining.declareTemporary(receiver); + contextWithAliasForThisExpression = contextForInlining.innerContextWithThisAliased(classDescriptorForMethod, aliasForThis.name()); + contextWithAliasForThisExpression.addStatementToCurrentBlock(aliasForThis.assignmentExpression().makeStmt()); + return contextWithAliasForThisExpression; + } + + @NotNull + private TemporaryVariable createAliasForArgument(@NotNull ValueParameterDescriptor parameterDescriptor) { + ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex()); + JsExpression translatedArgument = translateArgument(parameterDescriptor, actualArgument); + TemporaryVariable aliasForArgument = context().declareTemporary(translatedArgument); + context().addStatementToCurrentBlock(aliasForArgument.assignmentExpression().makeStmt()); + return aliasForArgument; } @NotNull @@ -104,4 +144,24 @@ public final class InlinedCallExpressionTranslator extends AbstractCallExpressio public boolean shouldWrapVarargInArray() { return true; } + + private static final class InlineFunctionMutator implements Mutator { + + @NotNull + private final TemporaryVariable toAssignTo; + + private InlineFunctionMutator(@NotNull TemporaryVariable to) { + toAssignTo = to; + } + + @NotNull + @Override + public JsNode mutate(@NotNull JsNode node) { + if (node instanceof JsReturn) { + JsExpression returnedExpression = ((JsReturn)node).getExpr(); + return JsAstUtils.assignment(toAssignTo.name().makeRef(), returnedExpression); + } + return node; + } + } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/DescriptorUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/DescriptorUtils.java index 8f67d12c6c8..0478674c523 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/DescriptorUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/DescriptorUtils.java @@ -288,4 +288,17 @@ public final class DescriptorUtils { } return result; } + + @Nullable + public static ClassDescriptor getClassDescriptorForMethod(@NotNull SimpleFunctionDescriptor functionDescriptor) { + if (!functionDescriptor.getExpectedThisObject().exists()) { + return null; + } + ClassifierDescriptor descriptor = + functionDescriptor.getExpectedThisObject().getType().getConstructor().getDeclarationDescriptor(); + if (descriptor instanceof ClassDescriptor) { + return (ClassDescriptor)descriptor.getOriginal(); + } + return null; + } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/FunctionBodyTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/FunctionBodyTranslator.java new file mode 100644 index 00000000000..e37a6a1e4da --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/FunctionBodyTranslator.java @@ -0,0 +1,97 @@ +/* + * Copyright 2010-2012 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.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 org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; +import org.jetbrains.jet.lang.psi.JetDeclarationWithBody; +import org.jetbrains.jet.lang.psi.JetExpression; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.lang.JetStandardClasses; +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.mutator.Mutator; + +import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToBlock; +import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression; + +/** + * @author Pavel Talanov + */ +public final class FunctionBodyTranslator extends AbstractTranslator { + + @NotNull + public static JsBlock translateFunctionBody(@NotNull FunctionDescriptor descriptor, + @NotNull JetDeclarationWithBody declarationWithBody, + @NotNull TranslationContext functionBodyContext) { + return (new FunctionBodyTranslator(descriptor, declarationWithBody, functionBodyContext)).translate(); + } + + + @NotNull + private final FunctionDescriptor descriptor; + @NotNull + private final JetDeclarationWithBody declaration; + + private FunctionBodyTranslator(@NotNull FunctionDescriptor descriptor, + @NotNull JetDeclarationWithBody declaration, + @NotNull TranslationContext context) { + super(context); + this.descriptor = descriptor; + this.declaration = declaration; + } + + @NotNull + private JsBlock translate() { + JetExpression jetBodyExpression = declaration.getBodyExpression(); + assert jetBodyExpression != null : "Cannot translate a body of an abstract function."; + return mayBeWrapWithReturn(Translation.translateExpression(jetBodyExpression, context())); + } + + @NotNull + private JsBlock mayBeWrapWithReturn(@NotNull JsNode body) { + if (!mustAddReturnToGeneratedFunctionBody()) { + return convertToBlock(body); + } + return convertToBlock(lastExpressionReturned(body)); + } + + private boolean mustAddReturnToGeneratedFunctionBody() { + JetType functionReturnType = descriptor.getReturnType(); + assert functionReturnType != null : "Function return typed type must be resolved."; + return (!declaration.hasBlockBody()) && (!JetStandardClasses.isUnit(functionReturnType)); + } + + @NotNull + private static JsNode lastExpressionReturned(@NotNull JsNode body) { + return mutateLastExpression(body, new Mutator() { + @Override + @NotNull + public JsNode mutate(@NotNull JsNode node) { + if (!(node instanceof JsExpression)) { + return node; + } + return new JsReturn((JsExpression)node); + } + }); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/mutator/LastExpressionMutator.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/mutator/LastExpressionMutator.java index e6c733ef282..8d5bd36f690 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/mutator/LastExpressionMutator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/mutator/LastExpressionMutator.java @@ -39,6 +39,8 @@ public final class LastExpressionMutator { this.mutator = mutator; } + //TODO: visitor? + //TODO: when expression? @NotNull private JsNode apply(@NotNull JsNode node) { if (node instanceof JsBlock) { diff --git a/js/js.translator/testFiles/inline/cases/functionWithBlockBody.kt b/js/js.translator/testFiles/inline/cases/functionWithBlockBody.kt new file mode 100644 index 00000000000..57eb6cfa0a5 --- /dev/null +++ b/js/js.translator/testFiles/inline/cases/functionWithBlockBody.kt @@ -0,0 +1,7 @@ +package foo + +fun box() = !myInlineFun() + +inline fun myInlineFun() : Boolean { + return false +} diff --git a/js/js.translator/testFiles/inline/cases/functionWithOneParameter.kt b/js/js.translator/testFiles/inline/cases/functionWithOneParameter.kt new file mode 100644 index 00000000000..a14c70cd95b --- /dev/null +++ b/js/js.translator/testFiles/inline/cases/functionWithOneParameter.kt @@ -0,0 +1,9 @@ +package foo + +fun box() : Boolean { + val t = myInlineFun(1 == 2) + val f = myInlineFun(1 == 2) + return t and f +} + +inline fun myInlineFun(t : Boolean) = !t diff --git a/js/js.translator/testFiles/inline/cases/functionWithTwoParameters.kt b/js/js.translator/testFiles/inline/cases/functionWithTwoParameters.kt new file mode 100644 index 00000000000..862cc5d1be4 --- /dev/null +++ b/js/js.translator/testFiles/inline/cases/functionWithTwoParameters.kt @@ -0,0 +1,13 @@ +package foo + +fun box() : Boolean { + if (myInlineFun(3, 2) != 1) { + return false; + } + if (myInlineFun(100, -100) != 200) { + return false; + } + return true; +} + +inline fun myInlineFun(l : Int, r : Int) = l - r diff --git a/js/js.translator/testFiles/inline/cases/method.kt b/js/js.translator/testFiles/inline/cases/method.kt new file mode 100644 index 00000000000..fdf57de7c2c --- /dev/null +++ b/js/js.translator/testFiles/inline/cases/method.kt @@ -0,0 +1,11 @@ +package foo + +fun box() : Boolean { + return (!(A().myInlineMethod())) +} + +class A() { + inline fun myInlineMethod() : Boolean { + return false + } +} \ No newline at end of file diff --git a/js/js.translator/testFiles/inline/cases/methodWithReferenceToThis.kt b/js/js.translator/testFiles/inline/cases/methodWithReferenceToThis.kt new file mode 100644 index 00000000000..fbffdf28b88 --- /dev/null +++ b/js/js.translator/testFiles/inline/cases/methodWithReferenceToThis.kt @@ -0,0 +1,11 @@ +package foo + +fun box() : Boolean { + return !(A(false).myInlineMethod()) and A(true).myInlineMethod() +} + +class A(val a : Boolean) { + inline fun myInlineMethod() : Boolean { + return a + } +} \ No newline at end of file