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.TranslationContext;
029    import org.jetbrains.k2js.translate.general.AbstractTranslator;
030    import org.jetbrains.k2js.translate.general.Translation;
031    import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
032    
033    import java.util.ArrayList;
034    import java.util.Collections;
035    import java.util.List;
036    
037    public class CallArgumentTranslator extends AbstractTranslator {
038    
039        @NotNull
040        public static ArgumentsInfo translate(
041                @NotNull ResolvedCall<?> resolvedCall,
042                @Nullable JsExpression receiver,
043                @NotNull TranslationContext context
044        ) {
045            CallArgumentTranslator argumentTranslator = new CallArgumentTranslator(resolvedCall, receiver, context);
046            return argumentTranslator.translate();
047        }
048    
049        public static class ArgumentsInfo {
050            private final List<JsExpression> translateArguments;
051            private final boolean hasSpreadOperator;
052            private final TemporaryConstVariable cachedReceiver;
053    
054            public ArgumentsInfo(List<JsExpression> arguments, boolean operator, TemporaryConstVariable receiver) {
055                translateArguments = arguments;
056                hasSpreadOperator = operator;
057                cachedReceiver = receiver;
058            }
059    
060            @NotNull
061            public List<JsExpression> getTranslateArguments() {
062                return translateArguments;
063            }
064    
065            public boolean isHasSpreadOperator() {
066                return hasSpreadOperator;
067            }
068    
069            @Nullable
070            public TemporaryConstVariable getCachedReceiver() {
071                return cachedReceiver;
072            }
073        }
074    
075        public static void translateSingleArgument(
076                @NotNull ResolvedValueArgument actualArgument,
077                @NotNull List<JsExpression> result,
078                @NotNull TranslationContext context,
079                boolean shouldWrapVarargInArray
080        ) {
081            List<ValueArgument> valueArguments = actualArgument.getArguments();
082            if (actualArgument instanceof VarargValueArgument) {
083                translateVarargArgument(valueArguments, result, context, shouldWrapVarargInArray);
084            }
085            else if (actualArgument instanceof DefaultValueArgument) {
086                result.add(context.namer().getUndefinedExpression());
087            }
088            else {
089                assert actualArgument instanceof ExpressionValueArgument;
090                assert valueArguments.size() == 1;
091                JetExpression argumentExpression = valueArguments.get(0).getArgumentExpression();
092                assert argumentExpression != null;
093                result.add(Translation.translateAsExpression(argumentExpression, context));
094            }
095        }
096    
097        private static void translateVarargArgument(
098                @NotNull List<ValueArgument> arguments,
099                @NotNull List<JsExpression> result,
100                @NotNull TranslationContext context,
101                boolean shouldWrapVarargInArray
102        ) {
103            if (arguments.isEmpty()) {
104                if (shouldWrapVarargInArray) {
105                    result.add(new JsArrayLiteral(Collections.<JsExpression>emptyList()));
106                }
107                return;
108            }
109    
110            List<JsExpression> list;
111            if (shouldWrapVarargInArray) {
112                list = arguments.size() == 1 ? new SmartList<JsExpression>() : new ArrayList<JsExpression>(arguments.size());
113                result.add(new JsArrayLiteral(list));
114            }
115            else {
116                list = result;
117            }
118            for (ValueArgument argument : arguments) {
119                JetExpression argumentExpression = argument.getArgumentExpression();
120                assert argumentExpression != null;
121                list.add(Translation.translateAsExpression(argumentExpression, context));
122            }
123        }
124    
125        @NotNull
126        private final ResolvedCall<?> resolvedCall;
127        @Nullable
128        private final JsExpression receiver;
129        private final boolean isNativeFunctionCall;
130    
131        private CallArgumentTranslator(
132                @NotNull ResolvedCall<?> resolvedCall,
133                @Nullable JsExpression receiver,
134                @NotNull TranslationContext context
135        ) {
136            super(context);
137            this.resolvedCall = resolvedCall;
138            this.receiver = receiver;
139            this.isNativeFunctionCall = AnnotationsUtils.isNativeObject(resolvedCall.getCandidateDescriptor());
140        }
141    
142        private void removeLastUndefinedArguments(@NotNull List<JsExpression> result) {
143            int i;
144            for (i = result.size() - 1; i >= 0; i--) {
145                if (result.get(i) != context().namer().getUndefinedExpression()) {
146                    break;
147                }
148            }
149            result.subList(i + 1, result.size()).clear();
150        }
151    
152        private ArgumentsInfo translate() {
153            List<ValueParameterDescriptor> valueParameters = resolvedCall.getResultingDescriptor().getValueParameters();
154            if (valueParameters.isEmpty()) {
155                return new ArgumentsInfo(Collections.<JsExpression>emptyList(), false, null);
156            }
157            boolean hasSpreadOperator = false;
158            TemporaryConstVariable cachedReceiver = null;
159    
160            List<JsExpression> result = new ArrayList<JsExpression>(valueParameters.size());
161            List<ResolvedValueArgument> valueArgumentsByIndex = resolvedCall.getValueArgumentsByIndex();
162            List<JsExpression> argsBeforeVararg = null;
163            for (ValueParameterDescriptor parameterDescriptor : valueParameters) {
164                ResolvedValueArgument actualArgument = valueArgumentsByIndex.get(parameterDescriptor.getIndex());
165    
166                if (actualArgument instanceof VarargValueArgument) {
167                    assert !hasSpreadOperator;
168    
169                    List<ValueArgument> arguments = actualArgument.getArguments();
170                    hasSpreadOperator = arguments.size() == 1 && arguments.get(0).getSpreadElement() != null;
171    
172                    if (isNativeFunctionCall && hasSpreadOperator) {
173                        argsBeforeVararg = result;
174                        result = new SmartList<JsExpression>();
175                    }
176                }
177    
178                translateSingleArgument(actualArgument, result, context(), !isNativeFunctionCall && !hasSpreadOperator);
179            }
180    
181            if (isNativeFunctionCall && hasSpreadOperator) {
182                if (!argsBeforeVararg.isEmpty()) {
183                    JsInvocation concatArguments = new JsInvocation(new JsNameRef("concat", new JsArrayLiteral(argsBeforeVararg)), result);
184                    result = new SmartList<JsExpression>(concatArguments);
185                }
186    
187                if (receiver != null) {
188                    cachedReceiver = context().getOrDeclareTemporaryConstVariable(receiver);
189                    result.add(0, cachedReceiver.reference());
190                }
191                else {
192                    result.add(0, JsLiteral.NULL);
193                }
194            }
195    
196            removeLastUndefinedArguments(result);
197            return new ArgumentsInfo(result, hasSpreadOperator, cachedReceiver);
198        }
199    
200    }