blob: 6cf66bdc7d34cb29136fa960f7e6574393f76253 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.impl;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author yole
*/
public class CallArgumentsMappingImpl implements CallArgumentsMapping {
private final Map<PyExpression, PyNamedParameter> myPlainMappedParams; // one param per arg
private final Map<PyExpression, List<PyNamedParameter>> myNestedMappedParams; // one arg sweeps a nested tuple of params
private PyStarArgument myTupleArg; // the *arg
private PyStarArgument myKwdArg; // the **arg
private final List<PyNamedParameter> myTupleMappedParams; // params mapped to *arg
private final List<PyNamedParameter> myKwdMappedParams; // params mapped to **arg
private final List<PyNamedParameter> myUnmappedParams;
private final Map<PyExpression, EnumSet<ArgFlag>> myArgFlags; // flags of every arg
private PyCallExpression.PyMarkedCallee myMarkedCallee;
private PyArgumentList myArgumentList;
public CallArgumentsMappingImpl(PyArgumentList arglist) {
// full of empty containers
myPlainMappedParams = new LinkedHashMap<PyExpression, PyNamedParameter>();
myNestedMappedParams = new LinkedHashMap<PyExpression, List<PyNamedParameter>>();
myTupleMappedParams = new ArrayList<PyNamedParameter>();
myKwdMappedParams = new ArrayList<PyNamedParameter>();
myUnmappedParams = new ArrayList<PyNamedParameter>();
myArgFlags = new HashMap<PyExpression, EnumSet<ArgFlag>>();
myMarkedCallee = null;
myArgumentList = arglist;
}
/**
* Maps arguments of a call to parameters of a callee.
* must contain already resolved callee with flags set appropriately.
* <br/>
* <i>NOTE:</i> <tt>*arg</tt> of unknown length is considered to be long just enough to fill appropriate
* positional paramaters, but at least one item long.
* @param arguments what to map, get if from call site
* @param resolved_callee what to map parameters of
* @param context optional shared type evaluator / cache.
*/
public void mapArguments(PyCallExpression.PyMarkedCallee resolved_callee, @NotNull TypeEvalContext context) {
PyExpression[] arguments = myArgumentList.getArguments();
myMarkedCallee = resolved_callee;
final List<PyExpression> unmatched_subargs = new LinkedList<PyExpression>(); // unmatched nested arguments will go here
List<PyExpression> unmatched_args = verifyArguments();
final List<PyParameter> parameters = PyUtil.getParameters(myMarkedCallee.getCallable(), context);
// prepare parameter slots
Map<PyNamedParameter, PyExpression> slots = new LinkedHashMap<PyNamedParameter, PyExpression>();
PyNamedParameter kwd_par = null; // **param
PyNamedParameter tuple_par = null; // *param
Set<PyExpression> mapped_args = new HashSet<PyExpression>();
final int implicitOffset = resolved_callee.getImplicitOffset();
int positional_index = 0; // up to this index parameters are positional
// check positional arguments, fill slots
int i = 0;
for (PyParameter par : parameters) {
if (tuple_par == null && kwd_par == null && positional_index < implicitOffset) {
positional_index += 1;
continue;
}
PyNamedParameter n_par = par.getAsNamed();
if (n_par != null) {
if (n_par.isPositionalContainer()) tuple_par = n_par;
else if (n_par.isKeywordContainer()) kwd_par = n_par;
else {
slots.put(n_par, null); // regular parameter that may serve as positional/named
if (tuple_par == null && kwd_par == null) {
positional_index += 1; // only if we're not past *param / **param
}
}
}
else {
PyTupleParameter t_par = par.getAsTuple();
if (t_par != null) positional_index += 1; // tuple can only be positional
// else lone star, skip
}
i += 1;
}
// rule out 'self' or other implicit params
for (i=0; i < implicitOffset && i < parameters.size(); i+=1) {
slots.remove(parameters.get(i).getAsNamed());
positional_index += 1;
}
// now params to the left of positional_index are positional.
// map positional args to positional params.
// we assume that implicitly skipped parameters are never nested tuples. no idea when they could ever be.
int cnt = implicitOffset;
int positional_bound = arguments.length; // to the right of this pos args are verboten
ListIterator<PyExpression> unmatched_arg_iter = unmatched_args.listIterator();
while (unmatched_arg_iter.hasNext()) {
PyExpression arg = unmatched_arg_iter.next();
if (arg instanceof PyStarArgument || arg instanceof PyKeywordArgument) {
positional_bound = cnt;
break;
}
if (cnt < parameters.size() && cnt < positional_index) {
final PyParameter par = parameters.get(cnt);
PyNamedParameter n_par = par.getAsNamed();
if (n_par != null) {
cnt += 1;
slots.put(n_par, PyUtil.peelArgument(arg));
mapped_args.add(arg);
}
else {
PyTupleParameter t_par = par.getAsTuple();
if (t_par != null) {
if (arg instanceof PyParenthesizedExpression) {
mapped_args.add(arg); // tuple itself is always mapped; its insides can fail
}
else {
PyType arg_type = context.getType(arg);
if (arg_type != null && arg_type.isBuiltin() && "list".equals(arg_type.getName())) {
mapped_args.add(arg); // we can't really analyze arbitrary lists statically yet
// but ListLiteralExpressions are handled by visitor
}
}
unmatched_arg_iter.previous();
MyParamVisitor visitor = new MyParamVisitor(unmatched_arg_iter, this);
visitor.enterTuple(t_par.getAsTuple()); // will recur as needed
unmatched_subargs.addAll(visitor.getUnmatchedSubargs()); // what didn't match inside
cnt += 1;
}
// else: goes to *param
}
}
else break;
}
// anything left after mapping of nested-tuple params?
for (Map.Entry<PyExpression, List<PyNamedParameter>> pair : myNestedMappedParams.entrySet()) {
PyExpression arg = pair.getKey();
List<PyNamedParameter> params = pair.getValue();
mapped_args.add(arg);
for (PyNamedParameter n_par : params) slots.remove(n_par);
}
for (PyExpression arg : unmatched_subargs) {
markArgument(arg, ArgFlag.IS_UNMAPPED);
}
boolean seen_named_args = false;
// map named args to named params if possible
Map<String, PyNamedParameter> parameter_by_name = new LinkedHashMap<String, PyNamedParameter>();
for (PyParameter par : parameters) {
PyNamedParameter n_par = par.getAsNamed();
if (n_par != null) parameter_by_name.put(n_par.getName(), n_par);
}
for (PyExpression arg : arguments) {
if (arg instanceof PyKeywordArgument) { // to explicitly named param?
String arg_name = ((PyKeywordArgument)arg).getKeyword();
PyNamedParameter respective_par = parameter_by_name.get(arg_name);
if (respective_par != null && !respective_par.isKeywordContainer() && !respective_par.isPositionalContainer()) {
if (slots.get(respective_par) != null) markArgument(arg, ArgFlag.IS_DUP);
else slots.put(respective_par, arg);
}
else { // to **param?
if (kwd_par != null) {
myPlainMappedParams.put(arg, kwd_par);
mapped_args.add(arg);
}
}
seen_named_args = true;
}
}
// map *arg to positional params if possible
boolean tuple_arg_not_exhausted = false;
boolean tuple_dup_found = false;
if (cnt < parameters.size() && cnt < positional_index && myTupleArg != null) {
// check length of myTupleArg
PyType tuple_arg_type = null;
final PyExpression expression = PsiTreeUtil.getChildOfType(myTupleArg, PyExpression.class);
if (expression != null) {
tuple_arg_type = context.getType(expression);
}
int tuple_length;
boolean tuple_length_known;
if (tuple_arg_type instanceof PyTupleType) {
tuple_length = ((PyTupleType)tuple_arg_type).getElementCount();
tuple_length_known = true;
}
else {
tuple_length = 2000000; // no practical function will have so many positional params
tuple_length_known = false;
}
int mapped_params_count = 0;
while (cnt < parameters.size() && cnt < positional_index && mapped_params_count < tuple_length) {
PyParameter par = parameters.get(cnt);
if (par instanceof PySingleStarParameter) break;
PyNamedParameter n_par = par.getAsNamed();
if (slots.containsKey(n_par)) {
final PyExpression arg_here = slots.get(n_par);
if (arg_here != null) {
if (tuple_length_known) {
final EnumSet<ArgFlag> flags = myArgFlags.get(arg_here);
if (flags == null || flags.isEmpty()) {
markArgument(arg_here, ArgFlag.IS_DUP);
tuple_dup_found = true;
}
}
// else: unknown tuple length is just enough
// the spree is over
break;
}
else if (n_par != null) { // normally always true
myTupleMappedParams.add(n_par);
mapped_args.add(myTupleArg);
slots.remove(n_par);
}
}
else if (n_par == tuple_par) {
mapped_params_count = tuple_length; // we found *param for our *arg, consider it fully mapped
break;
}
cnt += 1;
mapped_params_count += 1;
}
if (
tuple_length_known && (mapped_params_count < tuple_length) || // not exhausted
mapped_params_count == 0 // unknown length must consume at least first param
) {
tuple_arg_not_exhausted = true;
}
}
// map *param to the leftmost chunk of unmapped positional args
// NOTE: ignores the structure of nested-tuple params!
if (tuple_par != null) {
i = 0;
while (i < arguments.length && mapped_args.contains(arguments[i]) && isPositionalArg(arguments[i])) {
i += 1; // skip first mapped args
}
if (i < arguments.length && isPositionalArg(arguments[i])) {
while (i < arguments.length && !mapped_args.contains(arguments[i]) && isPositionalArg(arguments[i])) {
myPlainMappedParams.put(arguments[i], tuple_par);
mapped_args.add(arguments[i]);
i += 1;
}
}
}
// map unmapped *arg to *param
if (myTupleArg != null && tuple_par != null) {
if (!mapped_args.contains(myTupleArg)) {
myTupleMappedParams.add(tuple_par);
mapped_args.add(myTupleArg);
}
else if (! seen_named_args && tuple_arg_not_exhausted) {
// case of (*(1, 2, 3)) -> (a, *b); map the rest of *arg to *param
myTupleMappedParams.add(tuple_par);
mapped_args.add(myTupleArg);
tuple_arg_not_exhausted = false;
}
}
if (tuple_arg_not_exhausted && ! tuple_dup_found) {
markArgument(myTupleArg, ArgFlag.IS_TOO_LONG);
}
// map unmapped named params to **kwarg
if (myKwdArg != null) {
for (int j = implicitOffset; j < parameters.size(); ++j) {
final PyParameter par = parameters.get(j);
final PyNamedParameter namedParameter = par.getAsNamed();
if (namedParameter != null && !namedParameter.isKeywordContainer()
&& !namedParameter.isPositionalContainer() && slots.get(namedParameter) == null) {
slots.put(namedParameter, myKwdArg);
}
}
}
// map unmapped **kwarg to **param
if (myKwdArg != null && kwd_par != null && !mapped_args.contains(myKwdArg)) {
myKwdMappedParams.add(kwd_par);
mapped_args.add(myKwdArg);
}
// fill in ret, mark unmapped named params
for (Map.Entry<PyNamedParameter, PyExpression> pair : slots.entrySet()) {
PyNamedParameter n_par = pair.getKey();
PyExpression arg = pair.getValue();
if (arg == null) {
if (!n_par.hasDefaultValue()) myUnmappedParams.add(n_par);
}
else {
if (arg == myTupleArg) {
myTupleMappedParams.add(n_par);
}
else if (arg == myKwdArg) {
myKwdMappedParams.add(n_par);
}
else {
myPlainMappedParams.put(arg, n_par);
}
}
}
// mark unmapped args
for (PyExpression arg : slots.values()) {
if (arg != null) mapped_args.add(arg);
}
for (PyExpression arg : arguments) {
if (!mapped_args.contains(arg)) {
final EnumSet<ArgFlag> flags = myArgFlags.get(arg);
if (flags == null || flags.isEmpty()) {
markArgument(arg, ArgFlag.IS_UNMAPPED);
}
}
}
}
public List<PyExpression> verifyArguments() {
List<PyExpression> unmatched_args = new LinkedList<PyExpression>();
Collections.addAll(unmatched_args, myArgumentList.getArguments());
// detect starred args
for (PyExpression arg : myArgumentList.getArguments()) {
if (arg instanceof PyStarArgument) {
PyStarArgument star_arg = (PyStarArgument)arg;
if (star_arg.isKeyword()) {
if (myKwdArg == null) myKwdArg = star_arg;
else {
markArgument(arg, ArgFlag.IS_DUP_KWD);
unmatched_args.remove(arg);
}
}
else {
if (myTupleArg == null) myTupleArg = star_arg;
else {
markArgument(arg, ArgFlag.IS_DUP_TUPLE);
unmatched_args.remove(arg);
}
}
}
}
markPastBoundPositionalArguments(myArgumentList.getArguments());
return unmatched_args;
}
private void markPastBoundPositionalArguments(PyExpression[] arguments) {
boolean seenKwArg = false;
boolean seenKeyword = false;
boolean seenStar = false;
for (PyExpression arg : arguments) {
if (arg == myKwdArg) {
seenKwArg = true;
}
else if (arg instanceof PyKeywordArgument) {
seenKeyword = true;
}
else if (arg instanceof PyStarArgument) {
seenStar = true;
}
if (seenKeyword || seenKwArg || seenStar) {
if (!(arg instanceof PyStarArgument) && (seenKwArg || !(arg instanceof PyKeywordArgument))) {
markArgument(arg, ArgFlag.IS_POS_PAST_KWD);
}
}
}
}
private static boolean isPositionalArg(PyExpression arg) {
return !(arg instanceof PyKeywordArgument) && !(arg instanceof PyStarArgument);
}
/**
* @return A mapping argument->parameter for non-starred arguments (but includes starred parameters).
*/
public @NotNull
Map<PyExpression, PyNamedParameter> getPlainMappedParams() {
return myPlainMappedParams;
}
@NotNull
public Map<PyExpression, List<PyNamedParameter>> getNestedMappedParams() {
return myNestedMappedParams;
}
/**
* @return First *arg, or null.
*/
public PyStarArgument getTupleArg(){
return myTupleArg;
}
/**
* @return A list of parameters mapped to an *arg.
*/
public @NotNull List<PyNamedParameter> getTupleMappedParams(){
return myTupleMappedParams;
}
/**
* @return First **arg, or null.
*/
public PyStarArgument getKwdArg(){
return myKwdArg;
}
/**
* @return A list of parameters mapped to an **arg.
*/
public @NotNull List<PyNamedParameter> getKwdMappedParams(){
return myKwdMappedParams;
}
/**
* @return A list of parameters for which no arguments were found ('missing').
*/
public @NotNull
List<PyNamedParameter> getUnmappedParams(){
return myUnmappedParams;
}
/**
* @return result of a resolveCallee() against the function call to which the paramater list belongs.
*/
@Nullable
public PyCallExpression.PyMarkedCallee getMarkedCallee() {
return myMarkedCallee;
}
/**
* @return Lists all args with their flags.
*/
public Map<PyExpression, EnumSet<ArgFlag>> getArgumentFlags(){
return myArgFlags;
}
@Override
public boolean hasProblems() {
for (Map.Entry<PyExpression, EnumSet<CallArgumentsMapping.ArgFlag>> arg_entry : myArgFlags.entrySet()) {
EnumSet<CallArgumentsMapping.ArgFlag> flags = arg_entry.getValue();
if (!flags.isEmpty()) return true;
}
return myUnmappedParams.size() > 0;
}
public PyArgumentList getArgumentList() {
return myArgumentList; // that is, 'outer'
}
protected PyExpression markArgument(PyExpression arg, ArgFlag... flags) {
EnumSet<ArgFlag> argflags = myArgFlags.get(arg);
if (argflags == null) {
argflags = EnumSet.noneOf(ArgFlag.class);
}
ContainerUtil.addAll(argflags, flags);
myArgFlags.put(arg, argflags);
return arg;
}
static class MyParamVisitor extends PyElementVisitor {
private final Iterator<PyExpression> myArgIterator;
private final CallArgumentsMappingImpl myResult;
private final List<PyExpression> myUnmatchedSubargs;
private MyParamVisitor(Iterator<PyExpression> arg_iterator, CallArgumentsMappingImpl ret) {
myArgIterator = arg_iterator;
myResult = ret;
myUnmatchedSubargs = new ArrayList<PyExpression>(5); // arbitrary 'enough'
}
private Collection<PyExpression> getUnmatchedSubargs() {
return myUnmatchedSubargs;
}
@Override
public void visitPyParameter(PyParameter node) {
PyNamedParameter named = node.getAsNamed();
if (named != null) enterNamed(named);
else enterTuple(node.getAsTuple());
}
public void enterTuple(PyTupleParameter param) {
PyExpression arg = null;
if (myArgIterator.hasNext()) arg = myArgIterator.next();
// try to unpack a tuple expr in argument, if there's any
PyExpression[] elements = null;
if (arg instanceof PyParenthesizedExpression) {
PyExpression inner_expr = ((PyParenthesizedExpression)arg).getContainedExpression();
if (inner_expr instanceof PyTupleExpression) elements = ((PyTupleExpression)inner_expr).getElements();
}
else if (arg instanceof PyListLiteralExpression) {
elements = ((PyListLiteralExpression)arg).getElements();
}
final PyParameter[] nested_params = param.getContents();
if (elements != null) { // recursively map expression's tuple to parameter's.
final Iterator<PyExpression> subargs_iterator = Arrays.asList(elements).iterator();
MyParamVisitor visitor = new MyParamVisitor(subargs_iterator, myResult);
for (PyParameter nested : nested_params) nested.accept(visitor);
myUnmatchedSubargs.addAll(visitor.getUnmatchedSubargs());
while (subargs_iterator.hasNext()) { // more args in a tuple than parameters
PyExpression overflown_arg = subargs_iterator.next();
myResult.markArgument(overflown_arg, ArgFlag.IS_UNMAPPED);
}
}
else { // map all what's inside to this arg
final List<PyNamedParameter> nested_mapped = new ArrayList<PyNamedParameter>(nested_params.length);
ParamHelper.walkDownParamArray(
nested_params,
new ParamHelper.ParamVisitor() {
@Override public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
nested_mapped.add(param);
}
}
);
myResult.myNestedMappedParams.put(arg, nested_mapped);
}
}
public void enterNamed(PyNamedParameter param) {
if (myArgIterator.hasNext()) {
PyExpression subarg = myArgIterator.next();
myResult.myPlainMappedParams.put(subarg, param);
}
else {
myResult.myUnmappedParams.add(param);
}
// ...and *arg or **arg just won't parse inside a tuple, no need to handle it here
}
}
}