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.JsExpression;
021    import com.google.dart.compiler.backend.js.ast.JsLiteral;
022    import com.google.dart.compiler.backend.js.ast.JsNode;
023    import com.google.dart.compiler.backend.js.ast.JsReturn;
024    import com.intellij.util.SmartList;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.annotations.Nullable;
027    import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
028    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
029    import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor;
030    import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
031    import org.jetbrains.jet.lang.psi.JetCallExpression;
032    import org.jetbrains.jet.lang.psi.JetFunction;
033    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
034    import org.jetbrains.k2js.translate.callTranslator.CallInfo;
035    import org.jetbrains.k2js.translate.callTranslator.CallTranslatorPackage;
036    import org.jetbrains.k2js.translate.context.TemporaryVariable;
037    import org.jetbrains.k2js.translate.context.TranslationContext;
038    import org.jetbrains.k2js.translate.utils.JsAstUtils;
039    import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator;
040    import org.jetbrains.k2js.translate.utils.mutator.Mutator;
041    
042    import java.util.List;
043    import java.util.Map;
044    
045    import static org.jetbrains.k2js.translate.reference.CallArgumentTranslator.translateSingleArgument;
046    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionForDescriptor;
047    import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
048    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
049    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedThisDescriptor;
050    
051    public final class InlinedCallExpressionTranslator extends AbstractCallExpressionTranslator {
052    
053        @SuppressWarnings("UnusedParameters")
054        public static boolean shouldBeInlined(@NotNull JetCallExpression expression, @NotNull TranslationContext context) {
055            //TODO: inlining turned off
056            //ResolvedCall<?> resolvedCall = getResolvedCallForCallExpression(context.bindingContext(), expression);
057            //CallableDescriptor descriptor = resolvedCall.getCandidateDescriptor();
058            //if (descriptor instanceof SimpleFunctionDescriptor) {
059            //    return ((SimpleFunctionDescriptor)descriptor).isInline();
060            //}
061            return false;
062        }
063    
064        @NotNull
065        public static JsExpression translate(
066                @NotNull JetCallExpression expression,
067                @Nullable JsExpression receiver,
068                @NotNull TranslationContext context
069        ) {
070            return (new InlinedCallExpressionTranslator(expression, receiver, context)).translate();
071        }
072    
073        private InlinedCallExpressionTranslator(
074                @NotNull JetCallExpression expression, @Nullable JsExpression receiver,
075                @NotNull TranslationContext context
076        ) {
077            super(expression, receiver, context);
078        }
079    
080        @NotNull
081        private JsExpression translate() {
082            TranslationContext contextWithAllParametersAliased = createContextForInlining();
083            JsNode translatedBody = translateFunctionBody(getFunctionDescriptor(), getFunctionBody(), contextWithAllParametersAliased);
084            //TODO: declare uninitialized temporary
085            TemporaryVariable temporaryVariable = contextWithAllParametersAliased.declareTemporary(JsLiteral.NULL);
086            JsNode mutatedBody = LastExpressionMutator.mutateLastExpression(translatedBody, new InlineFunctionMutator(temporaryVariable));
087            context().addStatementToCurrentBlock(JsAstUtils.convertToBlock(mutatedBody));
088            return temporaryVariable.reference();
089        }
090    
091        @NotNull
092        private JetFunction getFunctionBody() {
093            return getFunctionForDescriptor(bindingContext(), getFunctionDescriptor());
094        }
095    
096        @NotNull
097        private SimpleFunctionDescriptor getFunctionDescriptor() {
098            CallableDescriptor descriptor = resolvedCall.getCandidateDescriptor().getOriginal();
099            assert descriptor instanceof SimpleFunctionDescriptor : "Inlined functions should have descriptor of type SimpleFunctionDescriptor";
100            return (SimpleFunctionDescriptor)descriptor;
101        }
102    
103        @NotNull
104        private TranslationContext createContextForInlining() {
105            TranslationContext contextForInlining = context();
106            contextForInlining = createContextWithAliasForThisExpression(contextForInlining);
107            return createContextWithAliasesForParameters(contextForInlining);
108        }
109    
110        @NotNull
111        private TranslationContext createContextWithAliasesForParameters(@NotNull TranslationContext contextForInlining) {
112            Map<DeclarationDescriptor, JsExpression> aliases = Maps.newHashMap();
113            for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) {
114                TemporaryVariable aliasForArgument = createAliasForArgument(parameterDescriptor);
115                aliases.put(parameterDescriptor, aliasForArgument.name().makeRef());
116            }
117            return contextForInlining.innerContextWithDescriptorsAliased(aliases);
118        }
119    
120        @NotNull
121        private TranslationContext createContextWithAliasForThisExpression(@NotNull TranslationContext contextForInlining) {
122            TranslationContext contextWithAliasForThisExpression = contextForInlining;
123            SimpleFunctionDescriptor functionDescriptor = getFunctionDescriptor();
124            CallInfo callInfo = CallTranslatorPackage.getCallInfo(contextForInlining, resolvedCall, receiver);
125            JsExpression receiver = callInfo.getReceiverObject();
126            if (receiver != null) {
127                contextWithAliasForThisExpression =
128                    contextWithAlias(contextWithAliasForThisExpression, receiver, getExpectedReceiverDescriptor(functionDescriptor));
129            }
130            JsExpression thisObject = callInfo.getThisObject();
131            if (thisObject != null) {
132                contextWithAliasForThisExpression =
133                    contextWithAlias(contextWithAliasForThisExpression, thisObject, getExpectedThisDescriptor(functionDescriptor));
134            }
135            return contextWithAliasForThisExpression;
136        }
137    
138        @NotNull
139        private TranslationContext contextWithAlias(@NotNull TranslationContext contextWithAliasForThisExpression,
140                                                    @NotNull JsExpression aliasExpression, @Nullable DeclarationDescriptor descriptorToAlias) {
141            TemporaryVariable aliasForReceiver = context().declareTemporary(aliasExpression);
142            assert descriptorToAlias != null;
143            TranslationContext newContext =
144                    contextWithAliasForThisExpression.innerContextWithAliased(descriptorToAlias, aliasForReceiver.reference());
145            newContext.addStatementToCurrentBlock(aliasForReceiver.assignmentExpression().makeStmt());
146            return newContext;
147        }
148    
149        @NotNull
150        private TemporaryVariable createAliasForArgument(@NotNull ValueParameterDescriptor parameterDescriptor) {
151            ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex());
152            JsExpression translatedArgument = translateArgument(actualArgument);
153            TemporaryVariable aliasForArgument = context().declareTemporary(translatedArgument);
154            context().addStatementToCurrentBlock(aliasForArgument.assignmentExpression().makeStmt());
155            return aliasForArgument;
156        }
157    
158        @NotNull
159        private JsExpression translateArgument(@NotNull ResolvedValueArgument actualArgument) {
160            List<JsExpression> result = new SmartList<JsExpression>();
161            translateSingleArgument(actualArgument, result, context(), true);
162            assert result.size() == 1 : "We always wrap varargs in kotlin calls.";
163            return result.get(0);
164        }
165    
166        private static final class InlineFunctionMutator implements Mutator {
167    
168            @NotNull
169            private final TemporaryVariable toAssignTo;
170    
171            private InlineFunctionMutator(@NotNull TemporaryVariable to) {
172                toAssignTo = to;
173            }
174    
175            @NotNull
176            @Override
177            public JsNode mutate(@NotNull JsNode node) {
178                if (node instanceof JsReturn) {
179                    JsExpression returnedExpression = ((JsReturn)node).getExpression();
180                    return JsAstUtils.assignment(toAssignTo.name().makeRef(), returnedExpression);
181                }
182                return node;
183            }
184        }
185    }