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