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.List;
042    
043    import static org.jetbrains.k2js.translate.utils.PsiUtils.getCallee;
044    
045    public 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    }