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    
017    package org.jetbrains.k2js.translate.reference;
018    
019    import com.google.common.collect.Maps;
020    import com.google.dart.compiler.backend.js.ast.*;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
024    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
025    import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor;
026    import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
027    import org.jetbrains.jet.lang.psi.JetCallExpression;
028    import org.jetbrains.jet.lang.psi.JetFunction;
029    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
030    import org.jetbrains.k2js.translate.context.TemporaryVariable;
031    import org.jetbrains.k2js.translate.context.TranslationContext;
032    import org.jetbrains.k2js.translate.utils.JsAstUtils;
033    import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator;
034    import org.jetbrains.k2js.translate.utils.mutator.Mutator;
035    
036    import java.util.List;
037    import java.util.Map;
038    
039    import static org.jetbrains.k2js.translate.reference.CallParametersResolver.resolveCallParameters;
040    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionForDescriptor;
041    import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
042    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
043    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedThisDescriptor;
044    
045    public 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    }