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    }