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.ValueParameterDescriptor;
024 import org.jetbrains.jet.lang.psi.JetExpression;
025 import org.jetbrains.jet.lang.psi.ValueArgument;
026 import org.jetbrains.jet.lang.resolve.calls.model.*;
027 import org.jetbrains.k2js.translate.context.TemporaryConstVariable;
028 import org.jetbrains.k2js.translate.context.TemporaryVariable;
029 import org.jetbrains.k2js.translate.context.TranslationContext;
030 import org.jetbrains.k2js.translate.general.AbstractTranslator;
031 import org.jetbrains.k2js.translate.general.Translation;
032 import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
033 import org.jetbrains.k2js.translate.utils.JsAstUtils;
034 import org.jetbrains.k2js.translate.utils.TranslationUtils;
035
036 import java.util.ArrayList;
037 import java.util.Collections;
038 import java.util.List;
039
040 public class CallArgumentTranslator extends AbstractTranslator {
041
042 @NotNull
043 public static ArgumentsInfo translate(
044 @NotNull ResolvedCall<?> resolvedCall,
045 @Nullable JsExpression receiver,
046 @NotNull TranslationContext context
047 ) {
048 return translate(resolvedCall, receiver, context, context.dynamicContext().jsBlock());
049 }
050
051 @NotNull
052 public static ArgumentsInfo translate(
053 @NotNull ResolvedCall<?> resolvedCall,
054 @Nullable JsExpression receiver,
055 @NotNull TranslationContext context,
056 @NotNull JsBlock block
057 ) {
058 TranslationContext innerContext = context.innerBlock(block);
059 CallArgumentTranslator argumentTranslator = new CallArgumentTranslator(resolvedCall, receiver, innerContext);
060 ArgumentsInfo result = argumentTranslator.translate();
061 context.moveVarsFrom(innerContext);
062 return result;
063 }
064
065 public static class ArgumentsInfo {
066 private final List<JsExpression> translateArguments;
067 private final boolean hasSpreadOperator;
068 private final TemporaryConstVariable cachedReceiver;
069
070 public ArgumentsInfo(List<JsExpression> arguments, boolean operator, TemporaryConstVariable receiver) {
071 translateArguments = arguments;
072 hasSpreadOperator = operator;
073 cachedReceiver = receiver;
074 }
075
076 @NotNull
077 public List<JsExpression> getTranslateArguments() {
078 return translateArguments;
079 }
080
081 public boolean isHasSpreadOperator() {
082 return hasSpreadOperator;
083 }
084
085 @Nullable
086 public TemporaryConstVariable getCachedReceiver() {
087 return cachedReceiver;
088 }
089 }
090
091 private static enum ArgumentsKind { HAS_EMPTY_EXPRESSION_ARGUMENT, HAS_NOT_EMPTY_EXPRESSION_ARGUMENT }
092
093 @NotNull
094 public static ArgumentsKind translateSingleArgument(
095 @NotNull ResolvedValueArgument actualArgument,
096 @NotNull List<JsExpression> result,
097 @NotNull TranslationContext context,
098 boolean shouldWrapVarargInArray
099 ) {
100 List<ValueArgument> valueArguments = actualArgument.getArguments();
101 if (actualArgument instanceof VarargValueArgument) {
102 return translateVarargArgument(valueArguments, result, context, shouldWrapVarargInArray);
103 }
104 else if (actualArgument instanceof DefaultValueArgument) {
105 result.add(context.namer().getUndefinedExpression());
106 return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
107 }
108 else {
109 assert actualArgument instanceof ExpressionValueArgument;
110 assert valueArguments.size() == 1;
111 JetExpression argumentExpression = valueArguments.get(0).getArgumentExpression();
112 assert argumentExpression != null;
113 JsExpression jsExpression = Translation.translateAsExpression(argumentExpression, context);
114 result.add(jsExpression);
115 if (JsAstUtils.isEmptyExpression(jsExpression)) {
116 return ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT;
117 }
118 else {
119 return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
120 }
121 }
122 }
123
124 @NotNull
125 private static ArgumentsKind translateVarargArgument(
126 @NotNull List<ValueArgument> arguments,
127 @NotNull List<JsExpression> result,
128 @NotNull TranslationContext context,
129 boolean shouldWrapVarargInArray
130 ) {
131 ArgumentsKind resultKind = ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
132 if (arguments.isEmpty()) {
133 if (shouldWrapVarargInArray) {
134 result.add(new JsArrayLiteral(Collections.<JsExpression>emptyList()));
135 }
136 return ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
137 }
138
139 List<JsExpression> list;
140 if (shouldWrapVarargInArray) {
141 list = arguments.size() == 1 ? new SmartList<JsExpression>() : new ArrayList<JsExpression>(arguments.size());
142 result.add(new JsArrayLiteral(list));
143 }
144 else {
145 list = result;
146 }
147 List<TranslationContext> argContexts = new SmartList<TranslationContext>();
148 boolean argumentsShouldBeExtractedToTmpVars = false;
149 for (ValueArgument argument : arguments) {
150 JetExpression argumentExpression = argument.getArgumentExpression();
151 assert argumentExpression != null;
152 TranslationContext argContext = context.innerBlock();
153 JsExpression argExpression = Translation.translateAsExpression(argumentExpression, argContext);
154 list.add(argExpression);
155 context.moveVarsFrom(argContext);
156 argContexts.add(argContext);
157 argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty();
158 if (JsAstUtils.isEmptyExpression(argExpression)) {
159 resultKind = ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT;
160 break;
161 }
162 }
163 if (argumentsShouldBeExtractedToTmpVars) {
164 extractArguments(list, argContexts, context, resultKind == ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT);
165 }
166 return resultKind;
167 }
168
169 private static void extractArguments(
170 @NotNull List<JsExpression> argExpressions,
171 @NotNull List<TranslationContext> argContexts,
172 @NotNull TranslationContext context,
173 boolean toTmpVars
174 ) {
175 for(int i=0; i<argExpressions.size(); i++) {
176 TranslationContext argContext = argContexts.get(i);
177 JsExpression jsArgExpression = argExpressions.get(i);
178 if (argContext.currentBlockIsEmpty() && TranslationUtils.isCacheNeeded(jsArgExpression)) {
179 if (toTmpVars) {
180 TemporaryVariable temporaryVariable = context.declareTemporary(jsArgExpression);
181 context.addStatementToCurrentBlock(temporaryVariable.assignmentExpression().makeStmt());
182 argExpressions.set(i, temporaryVariable.reference());
183 }
184 else {
185 context.addStatementToCurrentBlock(jsArgExpression.makeStmt());
186 }
187 } else {
188 context.addStatementsToCurrentBlockFrom(argContext);
189 }
190 }
191 }
192
193 @NotNull
194 private final ResolvedCall<?> resolvedCall;
195 @Nullable
196 private final JsExpression receiver;
197 private final boolean isNativeFunctionCall;
198
199 private CallArgumentTranslator(
200 @NotNull ResolvedCall<?> resolvedCall,
201 @Nullable JsExpression receiver,
202 @NotNull TranslationContext context
203 ) {
204 super(context);
205 this.resolvedCall = resolvedCall;
206 this.receiver = receiver;
207 this.isNativeFunctionCall = AnnotationsUtils.isNativeObject(resolvedCall.getCandidateDescriptor());
208 }
209
210 private void removeLastUndefinedArguments(@NotNull List<JsExpression> result) {
211 int i;
212 for (i = result.size() - 1; i >= 0; i--) {
213 if (result.get(i) != context().namer().getUndefinedExpression()) {
214 break;
215 }
216 }
217 result.subList(i + 1, result.size()).clear();
218 }
219
220 private ArgumentsInfo translate() {
221 List<ValueParameterDescriptor> valueParameters = resolvedCall.getResultingDescriptor().getValueParameters();
222 if (valueParameters.isEmpty()) {
223 return new ArgumentsInfo(Collections.<JsExpression>emptyList(), false, null);
224 }
225 boolean hasSpreadOperator = false;
226 TemporaryConstVariable cachedReceiver = null;
227
228 List<JsExpression> result = new ArrayList<JsExpression>(valueParameters.size());
229 List<ResolvedValueArgument> valueArgumentsByIndex = resolvedCall.getValueArgumentsByIndex();
230 if (valueArgumentsByIndex == null) {
231 throw new IllegalStateException("Failed to arrange value arguments by index: " + resolvedCall.getResultingDescriptor());
232 }
233 List<JsExpression> argsBeforeVararg = null;
234 boolean argumentsShouldBeExtractedToTmpVars = false;
235 List<TranslationContext> argContexts = new SmartList<TranslationContext>();
236 ArgumentsKind kind = ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT;
237
238 for (ValueParameterDescriptor parameterDescriptor : valueParameters) {
239 ResolvedValueArgument actualArgument = valueArgumentsByIndex.get(parameterDescriptor.getIndex());
240
241 if (actualArgument instanceof VarargValueArgument) {
242 assert !hasSpreadOperator;
243
244 List<ValueArgument> arguments = actualArgument.getArguments();
245 hasSpreadOperator = arguments.size() == 1 && arguments.get(0).getSpreadElement() != null;
246
247 if (isNativeFunctionCall && hasSpreadOperator) {
248 argsBeforeVararg = result;
249 result = new SmartList<JsExpression>();
250 }
251 }
252 TranslationContext argContext = context().innerBlock();
253 kind = translateSingleArgument(actualArgument, result, argContext, !isNativeFunctionCall && !hasSpreadOperator);
254 context().moveVarsFrom(argContext);
255 argContexts.add(argContext);
256 argumentsShouldBeExtractedToTmpVars = argumentsShouldBeExtractedToTmpVars || !argContext.currentBlockIsEmpty();
257
258 if (kind == ArgumentsKind.HAS_EMPTY_EXPRESSION_ARGUMENT) break;
259 }
260
261 if (argumentsShouldBeExtractedToTmpVars) {
262 extractArguments(result, argContexts, context(), kind == ArgumentsKind.HAS_NOT_EMPTY_EXPRESSION_ARGUMENT);
263 }
264
265 if (isNativeFunctionCall && hasSpreadOperator) {
266 if (!argsBeforeVararg.isEmpty()) {
267 JsInvocation concatArguments = new JsInvocation(new JsNameRef("concat", new JsArrayLiteral(argsBeforeVararg)), result);
268 result = new SmartList<JsExpression>(concatArguments);
269 }
270
271 if (receiver != null) {
272 cachedReceiver = context().getOrDeclareTemporaryConstVariable(receiver);
273 result.add(0, cachedReceiver.reference());
274 }
275 else {
276 result.add(0, JsLiteral.NULL);
277 }
278 }
279
280 removeLastUndefinedArguments(result);
281 return new ArgumentsInfo(result, hasSpreadOperator, cachedReceiver);
282 }
283
284 }