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.dart.compiler.backend.js.ast.*;
020import com.intellij.util.SmartList;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
024import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
025import org.jetbrains.jet.lang.psi.JetCallExpression;
026import org.jetbrains.jet.lang.psi.JetExpression;
027import org.jetbrains.jet.lang.psi.JetSimpleNameExpression;
028import org.jetbrains.jet.lang.psi.ValueArgument;
029import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
030import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
031import org.jetbrains.jet.lang.resolve.calls.model.VarargValueArgument;
032import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
033import org.jetbrains.jet.lang.resolve.calls.util.ExpressionAsFunctionDescriptor;
034import org.jetbrains.k2js.translate.context.TemporaryConstVariable;
035import org.jetbrains.k2js.translate.context.TranslationContext;
036import org.jetbrains.k2js.translate.general.Translation;
037import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
038import org.jetbrains.k2js.translate.utils.PsiUtils;
039
040import java.util.ArrayList;
041import java.util.List;
042
043import static org.jetbrains.k2js.translate.utils.PsiUtils.getCallee;
044
045public final class CallExpressionTranslator extends AbstractCallExpressionTranslator {
046
047    @NotNull
048    public static JsExpression translate(@NotNull JetCallExpression expression,
049            @Nullable JsExpression receiver,
050            @NotNull CallType callType,
051            @NotNull TranslationContext context) {
052        if (InlinedCallExpressionTranslator.shouldBeInlined(expression, context)) {
053            return InlinedCallExpressionTranslator.translate(expression, receiver, callType, context);
054        }
055        return (new CallExpressionTranslator(expression, receiver, callType, context)).translate();
056    }
057
058    private final boolean isNativeFunctionCall;
059    private boolean hasSpreadOperator = false;
060    private TemporaryConstVariable cachedReceiver = null;
061    private List<JsExpression> translatedArguments = null;
062    private JsExpression translatedReceiver = null;
063    private JsExpression translatedCallee = null;
064
065    private CallExpressionTranslator(@NotNull JetCallExpression expression,
066            @Nullable JsExpression receiver,
067            @NotNull CallType callType, @NotNull TranslationContext context) {
068        super(expression, receiver, callType, context);
069        this.isNativeFunctionCall = AnnotationsUtils.isNativeObject(resolvedCall.getCandidateDescriptor());
070    }
071
072    @NotNull
073    private JsExpression translate() {
074        prepareToBuildCall();
075
076        return CallBuilder.build(context())
077                .receiver(translatedReceiver)
078                .callee(translatedCallee)
079                .args(translatedArguments)
080                .resolvedCall(getResolvedCall())
081                .type(callType)
082                .translate();
083    }
084
085    private void prepareToBuildCall() {
086        translatedArguments = translateArguments();
087        translatedReceiver = getReceiver();
088        translatedCallee = getCalleeExpression();
089    }
090
091    @NotNull
092    private ResolvedCall<?> getResolvedCall() {
093        if (resolvedCall instanceof VariableAsFunctionResolvedCall) {
094            return ((VariableAsFunctionResolvedCall) resolvedCall).getFunctionCall();
095        }
096        return resolvedCall;
097    }
098
099    @Nullable
100    private JsExpression getReceiver() {
101        assert translatedArguments != null : "the results of this function depends on the results of translateArguments()";
102        if (receiver == null) {
103            return null;
104        }
105        if (cachedReceiver != null) {
106            return cachedReceiver.assignmentExpression();
107        }
108        return receiver;
109    }
110
111    @Nullable
112    private JsExpression getCalleeExpression() {
113        assert translatedArguments != null : "the results of this function depends on the results of translateArguments()";
114        if (isNativeFunctionCall && hasSpreadOperator) {
115            String functionName = resolvedCall.getCandidateDescriptor().getOriginal().getName().getIdentifier();
116            return new JsNameRef("apply", functionName);
117        }
118        CallableDescriptor candidateDescriptor = resolvedCall.getCandidateDescriptor();
119        if (candidateDescriptor instanceof ExpressionAsFunctionDescriptor) {
120            return translateExpressionAsFunction();
121        }
122        if (resolvedCall instanceof VariableAsFunctionResolvedCall) {
123            return translateVariableForVariableAsFunctionResolvedCall();
124        }
125        return null;
126    }
127
128    @NotNull
129    //TODO: looks hacky and should be modified soon
130    private JsExpression translateVariableForVariableAsFunctionResolvedCall() {
131        JetExpression callee = PsiUtils.getCallee(expression);
132        if (callee instanceof JetSimpleNameExpression) {
133            return ReferenceTranslator.getAccessTranslator((JetSimpleNameExpression) callee, receiver, context()).translateAsGet();
134        }
135        assert receiver != null;
136        return Translation.translateAsExpression(callee, context());
137    }
138
139    @NotNull
140    private JsExpression translateExpressionAsFunction() {
141        return Translation.translateAsExpression(getCallee(expression), context());
142    }
143
144    @NotNull
145    private List<JsExpression> translateArguments() {
146        List<JsExpression> result = new ArrayList<JsExpression>();
147        List<JsExpression> argsBeforeVararg = null;
148        for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) {
149            ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex());
150
151            if (actualArgument instanceof VarargValueArgument) {
152                assert !hasSpreadOperator;
153
154                List<ValueArgument> arguments = actualArgument.getArguments();
155                hasSpreadOperator = arguments.size() == 1 && arguments.get(0).getSpreadElement() != null;
156
157                if (isNativeFunctionCall && hasSpreadOperator) {
158                    assert argsBeforeVararg == null;
159                    argsBeforeVararg = result;
160                    result = new SmartList<JsExpression>();
161                }
162            }
163
164            result.addAll(translateSingleArgument(actualArgument, parameterDescriptor));
165        }
166
167        if (isNativeFunctionCall && hasSpreadOperator) {
168            assert argsBeforeVararg != null;
169            if (!argsBeforeVararg.isEmpty()) {
170                JsInvocation concatArguments = new JsInvocation(new JsNameRef("concat", new JsArrayLiteral(argsBeforeVararg)), result);
171                result = new SmartList<JsExpression>(concatArguments);
172            }
173
174            if (receiver != null) {
175                cachedReceiver = context().getOrDeclareTemporaryConstVariable(receiver);
176                result.add(0, cachedReceiver.reference());
177            }
178            else {
179                result.add(0, JsLiteral.NULL);
180            }
181        }
182
183        return result;
184    }
185
186    @Override
187    public boolean shouldWrapVarargInArray() {
188        return !isNativeFunctionCall && !hasSpreadOperator;
189    }
190}