001    /*
002     * Copyright 2010-2015 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.kotlin.resolve.calls;
018    
019    import com.google.common.collect.Maps;
020    import com.google.common.collect.Sets;
021    import com.intellij.psi.impl.source.tree.LeafPsiElement;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor;
025    import org.jetbrains.kotlin.descriptors.CallableDescriptor;
026    import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor;
027    import org.jetbrains.kotlin.diagnostics.Diagnostic;
028    import org.jetbrains.kotlin.name.Name;
029    import org.jetbrains.kotlin.psi.*;
030    import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage;
031    import org.jetbrains.kotlin.resolve.calls.model.*;
032    import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy;
033    import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage;
034    
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Set;
038    
039    import static org.jetbrains.kotlin.diagnostics.Errors.*;
040    import static org.jetbrains.kotlin.diagnostics.Errors.BadNamedArgumentsTarget.INVOKE_ON_FUNCTION_TYPE;
041    import static org.jetbrains.kotlin.diagnostics.Errors.BadNamedArgumentsTarget.NON_KOTLIN_FUNCTION;
042    import static org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET;
043    import static org.jetbrains.kotlin.resolve.calls.ValueArgumentsToParametersMapper.Status.*;
044    
045    public class ValueArgumentsToParametersMapper {
046    
047        public enum Status {
048            ERROR(false),
049            WEAK_ERROR(false),
050            OK(true);
051    
052            private final boolean success;
053    
054            Status(boolean success) {
055                this.success = success;
056            }
057    
058            public boolean isSuccess() {
059                return success;
060            }
061    
062            public Status compose(Status other) {
063                if (this == ERROR || other == ERROR) return ERROR;
064                if (this == WEAK_ERROR || other == WEAK_ERROR) return WEAK_ERROR;
065                return this;
066            }
067        }
068        public static <D extends CallableDescriptor> Status mapValueArgumentsToParameters(
069                @NotNull Call call,
070                @NotNull TracingStrategy tracing,
071                @NotNull MutableResolvedCall<D> candidateCall,
072                @NotNull Set<ValueArgument> unmappedArguments
073        ) {
074            //return new ValueArgumentsToParametersMapper().process(call, tracing, candidateCall, unmappedArguments);
075            Processor<D> processor = new Processor<D>(call, candidateCall, tracing);
076            processor.process();
077            unmappedArguments.addAll(processor.unmappedArguments);
078            return processor.status;
079        }
080    
081        private static class Processor<D extends CallableDescriptor> {
082            private final Call call;
083            private final TracingStrategy tracing;
084            private final MutableResolvedCall<D> candidateCall;
085    
086            private final Map<Name,ValueParameterDescriptor> parameterByName;
087    
088            private final Set<ValueArgument> unmappedArguments = Sets.newHashSet();
089            private final Map<ValueParameterDescriptor, VarargValueArgument> varargs = Maps.newHashMap();
090            private final Set<ValueParameterDescriptor> usedParameters = Sets.newHashSet();
091            private Status status = OK;
092    
093            private Processor(@NotNull Call call, @NotNull MutableResolvedCall<D> candidateCall, @NotNull TracingStrategy tracing) {
094                this.call = call;
095                this.tracing = tracing;
096                this.candidateCall = candidateCall;
097    
098                this.parameterByName = Maps.newHashMap();
099                for (ValueParameterDescriptor valueParameter : candidateCall.getCandidateDescriptor().getValueParameters()) {
100                    parameterByName.put(valueParameter.getName(), valueParameter);
101                }
102            }
103    
104            // We saw only positioned arguments so far
105            private final ProcessorState positionedOnly = new ProcessorState() {
106    
107                private int currentParameter = 0;
108    
109                @Nullable
110                public ValueParameterDescriptor nextValueParameter() {
111                    List<ValueParameterDescriptor> parameters = candidateCall.getCandidateDescriptor().getValueParameters();
112                    if (currentParameter >= parameters.size()) return null;
113    
114                    ValueParameterDescriptor head = parameters.get(currentParameter);
115    
116                    // If we found a vararg parameter, we are stuck with it forever
117                    if (head.getVarargElementType() == null) {
118                        currentParameter++;
119                    }
120    
121                    return head;
122                }
123    
124                @Override
125                public ProcessorState processNamedArgument(@NotNull ValueArgument argument) {
126                    return positionedThenNamed.processNamedArgument(argument);
127                }
128    
129                @Override
130                public ProcessorState processPositionedArgument(@NotNull ValueArgument argument, int index) {
131                    ValueParameterDescriptor valueParameterDescriptor = nextValueParameter();
132    
133                    if (valueParameterDescriptor != null) {
134                        usedParameters.add(valueParameterDescriptor);
135                        putVararg(valueParameterDescriptor, argument);
136                    }
137                    else {
138                        report(TOO_MANY_ARGUMENTS.on(argument.asElement(), candidateCall.getCandidateDescriptor()));
139                        unmappedArguments.add(argument);
140                        setStatus(WEAK_ERROR);
141                    }
142    
143                    return positionedOnly;
144                }
145            };
146    
147            // We saw zero or more positioned arguments and then a named one
148            private final ProcessorState positionedThenNamed = new ProcessorState() {
149                @Override
150                public ProcessorState processNamedArgument(@NotNull ValueArgument argument) {
151                    assert argument.isNamed();
152    
153                    D candidate = candidateCall.getCandidateDescriptor();
154    
155                    ValueArgumentName argumentName = argument.getArgumentName();
156                    assert argumentName != null;
157                    ValueParameterDescriptor valueParameterDescriptor = parameterByName.get(argumentName.getAsName());
158                    JetReferenceExpression nameReference = argumentName.getReferenceExpression();
159                    if (!candidate.hasStableParameterNames() && nameReference != null) {
160                        report(NAMED_ARGUMENTS_NOT_ALLOWED.on(
161                                nameReference,
162                                candidate instanceof FunctionInvokeDescriptor ? INVOKE_ON_FUNCTION_TYPE : NON_KOTLIN_FUNCTION
163                        ));
164                    }
165    
166                    if (valueParameterDescriptor == null) {
167                        if (nameReference != null) {
168                            report(NAMED_PARAMETER_NOT_FOUND.on(nameReference, nameReference));
169                        }
170                        unmappedArguments.add(argument);
171                        setStatus(WEAK_ERROR);
172                    }
173                    else {
174                        if (nameReference != null) {
175                            candidateCall.getTrace().record(REFERENCE_TARGET, nameReference, valueParameterDescriptor);
176                        }
177                        if (!usedParameters.add(valueParameterDescriptor)) {
178                            if (nameReference != null) {
179                                report(ARGUMENT_PASSED_TWICE.on(nameReference));
180                            }
181                            unmappedArguments.add(argument);
182                            setStatus(WEAK_ERROR);
183                        }
184                        else {
185                            putVararg(valueParameterDescriptor, argument);
186                        }
187                    }
188    
189                    return positionedThenNamed;
190                }
191    
192                @Override
193                public ProcessorState processPositionedArgument(
194                        @NotNull ValueArgument argument, int index
195                ) {
196                    report(MIXING_NAMED_AND_POSITIONED_ARGUMENTS.on(argument.asElement()));
197                    setStatus(WEAK_ERROR);
198                    unmappedArguments.add(argument);
199    
200                    return positionedThenNamed;
201                }
202            };
203    
204            public void process() {
205                ProcessorState state = positionedOnly;
206                List<? extends ValueArgument> argumentsInParentheses = CallUtilPackage.getValueArgumentsInParentheses(call);
207                for (int i = 0; i < argumentsInParentheses.size(); i++) {
208                    ValueArgument valueArgument = argumentsInParentheses.get(i);
209                    if (valueArgument.isNamed()) {
210                        state = state.processNamedArgument(valueArgument);
211                    }
212                    else {
213                        state = state.processPositionedArgument(valueArgument, i);
214                    }
215                }
216    
217                for (Map.Entry<ValueParameterDescriptor, VarargValueArgument> entry : varargs.entrySet()) {
218                    candidateCall.recordValueArgument(entry.getKey(), entry.getValue());
219                }
220    
221                processFunctionLiteralArguments();
222                reportUnmappedParameters();
223            }
224    
225            private void processFunctionLiteralArguments() {
226                D candidate = candidateCall.getCandidateDescriptor();
227                List<ValueParameterDescriptor> valueParameters = candidate.getValueParameters();
228    
229                List<? extends FunctionLiteralArgument> functionLiteralArguments = call.getFunctionLiteralArguments();
230                if (!functionLiteralArguments.isEmpty()) {
231                    FunctionLiteralArgument functionLiteralArgument = functionLiteralArguments.get(0);
232                    JetExpression possiblyLabeledFunctionLiteral = functionLiteralArgument.getArgumentExpression();
233    
234                    if (valueParameters.isEmpty()) {
235                        report(TOO_MANY_ARGUMENTS.on(possiblyLabeledFunctionLiteral, candidate));
236                        setStatus(ERROR);
237                    }
238                    else {
239                        ValueParameterDescriptor valueParameterDescriptor = valueParameters.get(valueParameters.size() - 1);
240                        if (valueParameterDescriptor.getVarargElementType() != null) {
241                            report(VARARG_OUTSIDE_PARENTHESES.on(possiblyLabeledFunctionLiteral));
242                            setStatus(ERROR);
243                        }
244                        else {
245                            if (!usedParameters.add(valueParameterDescriptor)) {
246                                report(TOO_MANY_ARGUMENTS.on(possiblyLabeledFunctionLiteral, candidate));
247                                setStatus(WEAK_ERROR);
248                            }
249                            else {
250                                putVararg(valueParameterDescriptor, functionLiteralArgument);
251                            }
252                        }
253                    }
254    
255                    for (int i = 1; i < functionLiteralArguments.size(); i++) {
256                        JetExpression argument = functionLiteralArguments.get(i).getArgumentExpression();
257                        report(MANY_FUNCTION_LITERAL_ARGUMENTS.on(argument));
258                        setStatus(WEAK_ERROR);
259                    }
260                }
261            }
262    
263            private void reportUnmappedParameters() {
264                List<ValueParameterDescriptor> valueParameters = candidateCall.getCandidateDescriptor().getValueParameters();
265                for (ValueParameterDescriptor valueParameter : valueParameters) {
266                    if (!usedParameters.contains(valueParameter)) {
267                        if (DescriptorUtilPackage.hasDefaultValue(valueParameter)) {
268                            candidateCall.recordValueArgument(valueParameter, DefaultValueArgument.DEFAULT);
269                        }
270                        else if (valueParameter.getVarargElementType() != null) {
271                            candidateCall.recordValueArgument(valueParameter, new VarargValueArgument());
272                        }
273                        else {
274                            tracing.noValueForParameter(candidateCall.getTrace(), valueParameter);
275                            setStatus(ERROR);
276                        }
277                    }
278                }
279            }
280    
281            private void putVararg(
282                    ValueParameterDescriptor valueParameterDescriptor,
283                    ValueArgument valueArgument
284            ) {
285                if (valueParameterDescriptor.getVarargElementType() != null) {
286                    VarargValueArgument vararg = varargs.get(valueParameterDescriptor);
287                    if (vararg == null) {
288                        vararg = new VarargValueArgument();
289                        varargs.put(valueParameterDescriptor, vararg);
290                    }
291                    vararg.addArgument(valueArgument);
292                }
293                else {
294                    LeafPsiElement spread = valueArgument.getSpreadElement();
295                    if (spread != null) {
296                        candidateCall.getTrace().report(NON_VARARG_SPREAD.on(spread));
297                        setStatus(WEAK_ERROR);
298                    }
299                    ResolvedValueArgument argument = new ExpressionValueArgument(valueArgument);
300                    candidateCall.recordValueArgument(valueParameterDescriptor, argument);
301                }
302            }
303    
304            private void setStatus(@NotNull Status newStatus) {
305                status = status.compose(newStatus);
306            }
307    
308            private void report(Diagnostic diagnostic) {
309                candidateCall.getTrace().report(diagnostic);
310            }
311    
312            private interface ProcessorState {
313                ProcessorState processNamedArgument(@NotNull ValueArgument argument);
314                ProcessorState processPositionedArgument(@NotNull ValueArgument argument, int index);
315            }
316    
317        }
318    
319        private ValueArgumentsToParametersMapper() {}
320    }