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