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 }