001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.k2js.translate.reference;
018
019import com.google.common.collect.Maps;
020import com.google.dart.compiler.backend.js.ast.*;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
024import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
025import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor;
026import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
027import org.jetbrains.jet.lang.psi.JetCallExpression;
028import org.jetbrains.jet.lang.psi.JetFunction;
029import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
030import org.jetbrains.k2js.translate.context.TemporaryVariable;
031import org.jetbrains.k2js.translate.context.TranslationContext;
032import org.jetbrains.k2js.translate.utils.JsAstUtils;
033import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator;
034import org.jetbrains.k2js.translate.utils.mutator.Mutator;
035
036import java.util.List;
037import java.util.Map;
038
039import static org.jetbrains.k2js.translate.reference.CallParametersResolver.resolveCallParameters;
040import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionForDescriptor;
041import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
042import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
043import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedThisDescriptor;
044
045public final class InlinedCallExpressionTranslator extends AbstractCallExpressionTranslator {
046
047    @SuppressWarnings("UnusedParameters")
048    public static boolean shouldBeInlined(@NotNull JetCallExpression expression, @NotNull TranslationContext context) {
049        //TODO: inlining turned off
050        //ResolvedCall<?> resolvedCall = getResolvedCallForCallExpression(context.bindingContext(), expression);
051        //CallableDescriptor descriptor = resolvedCall.getCandidateDescriptor();
052        //if (descriptor instanceof SimpleFunctionDescriptor) {
053        //    return ((SimpleFunctionDescriptor)descriptor).isInline();
054        //}
055        return false;
056    }
057
058    @NotNull
059    public static JsExpression translate(@NotNull JetCallExpression expression,
060                                         @Nullable JsExpression receiver,
061                                         @NotNull CallType callType,
062                                         @NotNull TranslationContext context) {
063        return (new InlinedCallExpressionTranslator(expression, receiver, callType, context)).translate();
064    }
065
066    private InlinedCallExpressionTranslator(@NotNull JetCallExpression expression, @Nullable JsExpression receiver,
067                                            @NotNull CallType callType, @NotNull TranslationContext context) {
068        super(expression, receiver, callType, context);
069    }
070
071    @NotNull
072    private JsExpression translate() {
073        TranslationContext contextWithAllParametersAliased = createContextForInlining();
074        JsNode translatedBody = translateFunctionBody(getFunctionDescriptor(), getFunctionBody(), contextWithAllParametersAliased);
075        //TODO: declare uninitialized temporary
076        TemporaryVariable temporaryVariable = contextWithAllParametersAliased.declareTemporary(JsLiteral.NULL);
077        JsNode mutatedBody = LastExpressionMutator.mutateLastExpression(translatedBody, new InlineFunctionMutator(temporaryVariable));
078        context().addStatementToCurrentBlock(JsAstUtils.convertToBlock(mutatedBody));
079        return temporaryVariable.reference();
080    }
081
082    @NotNull
083    private JetFunction getFunctionBody() {
084        return getFunctionForDescriptor(bindingContext(), getFunctionDescriptor());
085    }
086
087    @NotNull
088    private SimpleFunctionDescriptor getFunctionDescriptor() {
089        CallableDescriptor descriptor = resolvedCall.getCandidateDescriptor().getOriginal();
090        assert descriptor instanceof SimpleFunctionDescriptor : "Inlined functions should have descriptor of type SimpleFunctionDescriptor";
091        return (SimpleFunctionDescriptor)descriptor;
092    }
093
094    @NotNull
095    private TranslationContext createContextForInlining() {
096        TranslationContext contextForInlining = context();
097        contextForInlining = createContextWithAliasForThisExpression(contextForInlining);
098        return createContextWithAliasesForParameters(contextForInlining);
099    }
100
101    @NotNull
102    private TranslationContext createContextWithAliasesForParameters(@NotNull TranslationContext contextForInlining) {
103        Map<DeclarationDescriptor, JsName> aliases = Maps.newHashMap();
104        for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) {
105            TemporaryVariable aliasForArgument = createAliasForArgument(parameterDescriptor);
106            aliases.put(parameterDescriptor, aliasForArgument.name());
107        }
108        return contextForInlining.innerContextWithDescriptorsAliased(aliases);
109    }
110
111    @NotNull
112    private TranslationContext createContextWithAliasForThisExpression(@NotNull TranslationContext contextForInlining) {
113        TranslationContext contextWithAliasForThisExpression = contextForInlining;
114        SimpleFunctionDescriptor functionDescriptor = getFunctionDescriptor();
115        CallParameters callParameters = resolveCallParameters(receiver, null, functionDescriptor, resolvedCall, contextForInlining);
116        JsExpression receiver = callParameters.getReceiver();
117        if (receiver != null) {
118            contextWithAliasForThisExpression =
119                contextWithAlias(contextWithAliasForThisExpression, receiver, getExpectedReceiverDescriptor(functionDescriptor));
120        }
121        JsExpression thisObject = callParameters.getThisObject();
122        if (thisObject != null) {
123            contextWithAliasForThisExpression =
124                contextWithAlias(contextWithAliasForThisExpression, thisObject, getExpectedThisDescriptor(functionDescriptor));
125        }
126        return contextWithAliasForThisExpression;
127    }
128
129    @NotNull
130    private TranslationContext contextWithAlias(@NotNull TranslationContext contextWithAliasForThisExpression,
131                                                @NotNull JsExpression aliasExpression, @Nullable DeclarationDescriptor descriptorToAlias) {
132        TranslationContext newContext = contextWithAliasForThisExpression;
133        TemporaryVariable aliasForReceiver = context().declareTemporary(aliasExpression);
134        assert descriptorToAlias != null;
135        newContext =
136            newContext.innerContextWithThisAliased(descriptorToAlias, aliasForReceiver.name());
137        newContext.addStatementToCurrentBlock(aliasForReceiver.assignmentExpression().makeStmt());
138        return newContext;
139    }
140
141    @NotNull
142    private TemporaryVariable createAliasForArgument(@NotNull ValueParameterDescriptor parameterDescriptor) {
143        ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex());
144        JsExpression translatedArgument = translateArgument(parameterDescriptor, actualArgument);
145        TemporaryVariable aliasForArgument = context().declareTemporary(translatedArgument);
146        context().addStatementToCurrentBlock(aliasForArgument.assignmentExpression().makeStmt());
147        return aliasForArgument;
148    }
149
150    @NotNull
151    private JsExpression translateArgument(@NotNull ValueParameterDescriptor parameterDescriptor,
152                                           @NotNull ResolvedValueArgument actualArgument) {
153        List<JsExpression> translatedSingleArgument = translateSingleArgument(actualArgument, parameterDescriptor);
154        assert translatedSingleArgument.size() == 1 : "We always wrap varargs in kotlin calls.";
155        return translatedSingleArgument.get(0);
156    }
157
158    @Override
159    public boolean shouldWrapVarargInArray() {
160        return true;
161    }
162
163    private static final class InlineFunctionMutator implements Mutator {
164
165        @NotNull
166        private final TemporaryVariable toAssignTo;
167
168        private InlineFunctionMutator(@NotNull TemporaryVariable to) {
169            toAssignTo = to;
170        }
171
172        @NotNull
173        @Override
174        public JsNode mutate(@NotNull JsNode node) {
175            if (node instanceof JsReturn) {
176                JsExpression returnedExpression = ((JsReturn)node).getExpr();
177                return JsAstUtils.assignment(toAssignTo.name().makeRef(), returnedExpression);
178            }
179            return node;
180        }
181    }
182}