From 8843bac989c3309b1a5c3c005301d1643cd30fb7 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Wed, 28 Mar 2012 15:08:46 +0400 Subject: [PATCH] First stab at inline functions. --- .../k2js/test/semantics/InlineTest.java | 50 ++++++++ .../translate/context/AliasingContext.java | 7 ++ .../translate/context/TranslationContext.java | 5 + .../AbstractCallExpressionTranslator.java | 103 +++++++++++++++++ .../reference/CallExpressionTranslator.java | 83 ++++---------- .../InlinedCallExpressionTranslator.java | 107 ++++++++++++++++++ .../k2js/translate/utils/BindingUtils.java | 8 ++ .../inline/cases/functionWithoutParameters.kt | 5 + 8 files changed, 305 insertions(+), 63 deletions(-) create mode 100644 js/js.tests/test/org/jetbrains/k2js/test/semantics/InlineTest.java create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/reference/AbstractCallExpressionTranslator.java create mode 100644 js/js.translator/src/org/jetbrains/k2js/translate/reference/InlinedCallExpressionTranslator.java create mode 100644 js/js.translator/testFiles/inline/cases/functionWithoutParameters.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 new file mode 100644 index 00000000000..fdf7fb808da --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/InlineTest.java @@ -0,0 +1,50 @@ +/* + * 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.test.semantics; + +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.k2js.test.SingleFileTranslationTest; + +import java.io.File; + +/** + * @author Pavel Talanov + */ +public final class InlineTest extends SingleFileTranslationTest { + public InlineTest() { + super("inline/"); + } + + public void testFunctionWithoutParameters() throws Exception { + String filename = "functionWithoutParameters.kt"; + checkFooBoxIsTrue(filename); + String generatedJSFilePath = getOutputFilePath(filename); + String outputFileText = FileUtil.loadFile(new File(generatedJSFilePath)); + assertTrue(countOccurrences(outputFileText, "myInlineFun") == 1); + } + + private static int countOccurrences(@NotNull String str, @NotNull String subStr) { + int count = 0; + String s = str; + while (s.contains(subStr)) { + s = s.replaceFirst(subStr, ""); + count++; + } + return count; + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/AliasingContext.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/AliasingContext.java index e07aba523c6..f53c1f1b9fe 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/AliasingContext.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/AliasingContext.java @@ -79,6 +79,13 @@ public class AliasingContext { return newContext; } + @NotNull + public AliasingContext withDescriptorsAliased(@NotNull Map aliases) { + AliasingContext newContext = new AliasingContext(this); + newContext.aliasesForDescriptors.putAll(aliases); + return newContext; + } + @NotNull private AliasingContext getParent() { assert parent != null; 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 e1ed70f8e1e..8111e33ffd2 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 @@ -93,6 +93,11 @@ public final class TranslationContext { return new TranslationContext(staticContext, dynamicContext, aliasingContext.withAliasesForExpressions(aliases)); } + @NotNull + public TranslationContext innerContextWithDescriptorsAliased(@NotNull Map aliases) { + return new TranslationContext(staticContext, dynamicContext, aliasingContext.withDescriptorsAliased(aliases)); + } + @NotNull public JsBlock getBlockForDescriptor(@NotNull DeclarationDescriptor descriptor) { if (descriptor instanceof CallableDescriptor) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/AbstractCallExpressionTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/AbstractCallExpressionTranslator.java new file mode 100644 index 00000000000..02bf303717d --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/AbstractCallExpressionTranslator.java @@ -0,0 +1,103 @@ +/* + * 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.reference; + +import com.google.common.collect.Lists; +import com.google.dart.compiler.backend.js.ast.JsArrayLiteral; +import com.google.dart.compiler.backend.js.ast.JsExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; +import org.jetbrains.jet.lang.psi.JetCallExpression; +import org.jetbrains.jet.lang.psi.JetExpression; +import org.jetbrains.jet.lang.psi.ValueArgument; +import org.jetbrains.jet.lang.resolve.calls.*; +import org.jetbrains.k2js.translate.context.TranslationContext; +import org.jetbrains.k2js.translate.general.AbstractTranslator; +import org.jetbrains.k2js.translate.general.Translation; + +import java.util.Arrays; +import java.util.List; + +import static org.jetbrains.k2js.translate.utils.BindingUtils.getDefaultArgument; +import static org.jetbrains.k2js.translate.utils.BindingUtils.getResolvedCallForCallExpression; + +/** + * @author Pavel Talanov + */ +public abstract class AbstractCallExpressionTranslator extends AbstractTranslator { + + @NotNull + protected final JetCallExpression expression; + @NotNull + protected final ResolvedCall resolvedCall; + @Nullable + protected final JsExpression receiver; + @NotNull + protected final CallType callType; + + protected AbstractCallExpressionTranslator(@NotNull JetCallExpression expression, + @Nullable JsExpression receiver, + @NotNull CallType type, @NotNull TranslationContext context) { + super(context); + this.expression = expression; + this.resolvedCall = getResolvedCallForCallExpression(bindingContext(), expression); + this.receiver = receiver; + callType = type; + } + + abstract public boolean shouldWrapVarargInArray(); + + @NotNull + protected List translateSingleArgument(@NotNull ResolvedValueArgument actualArgument, + @NotNull ValueParameterDescriptor parameterDescriptor) { + List valueArguments = actualArgument.getArguments(); + if (actualArgument instanceof VarargValueArgument) { + return translateVarargArgument(valueArguments); + } + if (actualArgument instanceof DefaultValueArgument) { + JetExpression defaultArgument = getDefaultArgument(bindingContext(), parameterDescriptor); + return Arrays.asList(Translation.translateAsExpression(defaultArgument, context())); + } + assert actualArgument instanceof ExpressionValueArgument; + assert valueArguments.size() == 1; + JetExpression argumentExpression = valueArguments.get(0).getArgumentExpression(); + assert argumentExpression != null; + return Arrays.asList(Translation.translateAsExpression(argumentExpression, context())); + } + + @NotNull + private List translateVarargArgument(@NotNull List arguments) { + List translatedArgs = Lists.newArrayList(); + for (ValueArgument argument : arguments) { + JetExpression argumentExpression = argument.getArgumentExpression(); + assert argumentExpression != null; + translatedArgs.add(Translation.translateAsExpression(argumentExpression, context())); + } + if (shouldWrapVarargInArray()) { + return wrapInArrayLiteral(translatedArgs); + } + return translatedArgs; + } + + @NotNull + private static List wrapInArrayLiteral(@NotNull List translatedArgs) { + JsArrayLiteral argsWrappedInArray = new JsArrayLiteral(); + argsWrappedInArray.getExpressions().addAll(translatedArgs); + return Arrays.asList(argsWrappedInArray); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallExpressionTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallExpressionTranslator.java index 8c5ddd45d18..016aeb5845d 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallExpressionTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallExpressionTranslator.java @@ -16,8 +16,6 @@ package org.jetbrains.k2js.translate.reference; -import com.google.common.collect.Lists; -import com.google.dart.compiler.backend.js.ast.JsArrayLiteral; import com.google.dart.compiler.backend.js.ast.JsExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,61 +25,51 @@ import org.jetbrains.jet.lang.descriptors.VariableAsFunctionDescriptor; import org.jetbrains.jet.lang.psi.JetCallExpression; import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; -import org.jetbrains.jet.lang.psi.ValueArgument; -import org.jetbrains.jet.lang.resolve.calls.*; +import org.jetbrains.jet.lang.resolve.calls.ExpressionAsFunctionDescriptor; +import org.jetbrains.jet.lang.resolve.calls.ResolvedValueArgument; 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.AnnotationsUtils; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import static org.jetbrains.k2js.translate.utils.BindingUtils.getDefaultArgument; -import static org.jetbrains.k2js.translate.utils.BindingUtils.getResolvedCallForCallExpression; import static org.jetbrains.k2js.translate.utils.PsiUtils.getCallee; /** * @author Pavel Talanov */ -public final class CallExpressionTranslator extends AbstractTranslator { +public final class CallExpressionTranslator extends AbstractCallExpressionTranslator { @NotNull public static JsExpression translate(@NotNull JetCallExpression expression, @Nullable JsExpression receiver, @NotNull CallType callType, @NotNull TranslationContext context) { - return (new CallExpressionTranslator(expression, receiver, context)).translate(callType); + if (InlinedCallExpressionTranslator.shouldBeInlined(expression, context)) { + return InlinedCallExpressionTranslator.translate(expression, receiver, callType, context); + } + return (new CallExpressionTranslator(expression, receiver, callType, context)).translate(); } - @NotNull - private final JetCallExpression expression; - @NotNull - private final ResolvedCall resolvedCall; private final boolean isNativeFunctionCall; - @Nullable - private final JsExpression receiver; private CallExpressionTranslator(@NotNull JetCallExpression expression, @Nullable JsExpression receiver, - @NotNull TranslationContext context) { - super(context); - this.expression = expression; - this.resolvedCall = getResolvedCallForCallExpression(bindingContext(), expression); - this.receiver = receiver; + @NotNull CallType callType, @NotNull TranslationContext context) { + super(expression, receiver, callType, context); this.isNativeFunctionCall = AnnotationsUtils.isNativeObject(resolvedCall.getCandidateDescriptor()); } @NotNull - private JsExpression translate(@NotNull CallType callType) { + private JsExpression translate() { return CallBuilder.build(context()) - .receiver(receiver) - .callee(getCalleeExpression()) - .args(translateArguments()) - .resolvedCall(resolvedCall) - .type(callType) - .translate(); + .receiver(receiver) + .callee(getCalleeExpression()) + .args(translateArguments()) + .resolvedCall(resolvedCall) + .type(callType) + .translate(); } @Nullable @@ -100,7 +88,7 @@ public final class CallExpressionTranslator extends AbstractTranslator { private JsExpression translateVariableAsFunction() { JetExpression callee = getCallee(expression); assert callee instanceof JetSimpleNameExpression; - return ReferenceTranslator.getAccessTranslator((JetSimpleNameExpression) callee, receiver, context()).translateAsGet(); + return ReferenceTranslator.getAccessTranslator((JetSimpleNameExpression)callee, receiver, context()).translateAsGet(); } @NotNull @@ -111,7 +99,6 @@ public final class CallExpressionTranslator extends AbstractTranslator { @NotNull private List translateArguments() { List result = new ArrayList(); - ResolvedCall resolvedCall = getResolvedCallForCallExpression(bindingContext(), expression); for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) { ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex()); result.addAll(translateSingleArgument(actualArgument, parameterDescriptor)); @@ -119,38 +106,8 @@ public final class CallExpressionTranslator extends AbstractTranslator { return result; } - @NotNull - private List translateSingleArgument(@NotNull ResolvedValueArgument actualArgument, - @NotNull ValueParameterDescriptor parameterDescriptor) { - List valueArguments = actualArgument.getArguments(); - if (actualArgument instanceof VarargValueArgument) { - return translateVarargArgument(valueArguments); - } - if (actualArgument instanceof DefaultValueArgument) { - JetExpression defaultArgument = getDefaultArgument(bindingContext(), parameterDescriptor); - return Arrays.asList(Translation.translateAsExpression(defaultArgument, context())); - } - assert actualArgument instanceof ExpressionValueArgument; - assert valueArguments.size() == 1; - return Arrays.asList(Translation.translateAsExpression(valueArguments.get(0).getArgumentExpression(), context())); - } - - @NotNull - private List translateVarargArgument(@NotNull List arguments) { - List translatedArgs = Lists.newArrayList(); - for (ValueArgument argument : arguments) { - translatedArgs.add(Translation.translateAsExpression(argument.getArgumentExpression(), context())); - } - if (isNativeFunctionCall) { - return translatedArgs; - } - return wrapInArrayLiteral(translatedArgs); - } - - @NotNull - private static List wrapInArrayLiteral(@NotNull List translatedArgs) { - JsArrayLiteral argsWrappedInArray = new JsArrayLiteral(); - argsWrappedInArray.getExpressions().addAll(translatedArgs); - return Arrays.asList(argsWrappedInArray); + @Override + public boolean shouldWrapVarargInArray() { + return !isNativeFunctionCall; } } 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 new file mode 100644 index 00000000000..e9909e3f7ac --- /dev/null +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/InlinedCallExpressionTranslator.java @@ -0,0 +1,107 @@ +/* + * 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.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 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.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 java.util.List; +import java.util.Map; + +import static org.jetbrains.k2js.translate.utils.BindingUtils.getResolvedCallForCallExpression; + +/** + * @author Pavel Talanov + */ +public final class InlinedCallExpressionTranslator extends AbstractCallExpressionTranslator { + + public static boolean shouldBeInlined(@NotNull JetCallExpression expression, @NotNull TranslationContext context) { + ResolvedCall resolvedCall = getResolvedCallForCallExpression(context.bindingContext(), expression); + CallableDescriptor descriptor = resolvedCall.getCandidateDescriptor(); + if (descriptor instanceof SimpleFunctionDescriptor) { + return ((SimpleFunctionDescriptor)descriptor).isInline(); + } + return false; + } + + @NotNull + public static JsExpression translate(@NotNull JetCallExpression expression, + @Nullable JsExpression receiver, + @NotNull CallType callType, + @NotNull TranslationContext context) { + return (new InlinedCallExpressionTranslator(expression, receiver, callType, context)).translate(); + } + + private InlinedCallExpressionTranslator(@NotNull JetCallExpression expression, @Nullable JsExpression receiver, + @NotNull CallType callType, @NotNull TranslationContext context) { + super(expression, receiver, callType, context); + } + + @NotNull + private JsExpression translate() { + TranslationContext contextWithAllParametersAliased = createContextWithAllParametersAliased(); + JetFunction function = BindingUtils.getFunctionForDescriptor(bindingContext(), getFunctionDescriptor()); + return Translation.translateAsExpression(function.getBodyExpression(), contextWithAllParametersAliased); + } + + @NotNull + private SimpleFunctionDescriptor getFunctionDescriptor() { + CallableDescriptor descriptor = resolvedCall.getCandidateDescriptor().getOriginal(); + assert descriptor instanceof SimpleFunctionDescriptor : "Inlined functions should have descriptor of type SimpleFunctionDescriptor"; + return (SimpleFunctionDescriptor)descriptor; + } + + private TranslationContext createContextWithAllParametersAliased() { + 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); + aliases.put(parameterDescriptor, aliasForArgument.name()); + context().addStatementToCurrentBlock(aliasForArgument.assignmentExpression().makeStmt()); + } + return context().innerContextWithDescriptorsAliased(aliases); + } + + @NotNull + private JsExpression translateArgument(@NotNull ValueParameterDescriptor parameterDescriptor, + @NotNull ResolvedValueArgument actualArgument) { + List translatedSingleArgument = translateSingleArgument(actualArgument, parameterDescriptor); + assert translatedSingleArgument.size() == 1 : "We always wrap varargs in kotlin calls."; + return translatedSingleArgument.get(0); + } + + @Override + public boolean shouldWrapVarargInArray() { + return true; + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java index 55e2b65d12d..30d51c13e66 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/BindingUtils.java @@ -93,6 +93,14 @@ public final class BindingUtils { return (JetClass)result; } + @NotNull + public static JetFunction getFunctionForDescriptor(@NotNull BindingContext context, + @NotNull SimpleFunctionDescriptor descriptor) { + PsiElement result = context.get(BindingContext.DESCRIPTOR_TO_DECLARATION, descriptor); + assert result instanceof JetFunction : "SimpleFunctionDescriptor should have declaration of type JetFunction"; + return (JetFunction)result; + } + @NotNull public static List getDeclarationsForNamespace(@NotNull BindingContext bindingContext, @NotNull NamespaceDescriptor namespace) { diff --git a/js/js.translator/testFiles/inline/cases/functionWithoutParameters.kt b/js/js.translator/testFiles/inline/cases/functionWithoutParameters.kt new file mode 100644 index 00000000000..7cdce9f9f42 --- /dev/null +++ b/js/js.translator/testFiles/inline/cases/functionWithoutParameters.kt @@ -0,0 +1,5 @@ +package foo + +fun box() = myInlineFun() + +inline fun myInlineFun() = true