/* * Copyright 2000-2013 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 myPlainMappedParams; // one param per arg private final Map> myNestedMappedParams; // one arg sweeps a nested tuple of params private PyStarArgument myTupleArg; // the *arg private PyStarArgument myKwdArg; // the **arg private final List myTupleMappedParams; // params mapped to *arg private final List myKwdMappedParams; // params mapped to **arg private final List myUnmappedParams; private final Map> myArgFlags; // flags of every arg private PyCallExpression.PyMarkedCallee myMarkedCallee; private PyArgumentList myArgumentList; public CallArgumentsMappingImpl(PyArgumentList arglist) { // full of empty containers myPlainMappedParams = new LinkedHashMap(); myNestedMappedParams = new LinkedHashMap>(); myTupleMappedParams = new ArrayList(); myKwdMappedParams = new ArrayList(); myUnmappedParams = new ArrayList(); myArgFlags = new HashMap>(); myMarkedCallee = null; myArgumentList = arglist; } /** * Maps arguments of a call to parameters of a callee. * must contain already resolved callee with flags set appropriately. *
* NOTE: *arg 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 unmatched_subargs = new LinkedList(); // unmatched nested arguments will go here List unmatched_args = verifyArguments(); final List parameters = PyUtil.getParameters(myMarkedCallee.getCallable(), context); // prepare parameter slots Map slots = new LinkedHashMap(); PyNamedParameter kwd_par = null; // **param PyNamedParameter tuple_par = null; // *param Set mapped_args = new HashSet(); 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 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> pair : myNestedMappedParams.entrySet()) { PyExpression arg = pair.getKey(); List 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 parameter_by_name = new LinkedHashMap(); 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 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 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 flags = myArgFlags.get(arg); if (flags == null || flags.isEmpty()) { markArgument(arg, ArgFlag.IS_UNMAPPED); } } } } public List verifyArguments() { List unmatched_args = new LinkedList(); 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 getPlainMappedParams() { return myPlainMappedParams; } @NotNull public Map> 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 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 getKwdMappedParams(){ return myKwdMappedParams; } /** * @return A list of parameters for which no arguments were found ('missing'). */ public @NotNull List 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> getArgumentFlags(){ return myArgFlags; } @Override public boolean hasProblems() { for (Map.Entry> arg_entry : myArgFlags.entrySet()) { EnumSet 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 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 myArgIterator; private final CallArgumentsMappingImpl myResult; private final List myUnmatchedSubargs; private MyParamVisitor(Iterator arg_iterator, CallArgumentsMappingImpl ret) { myArgIterator = arg_iterator; myResult = ret; myUnmatchedSubargs = new ArrayList(5); // arbitrary 'enough' } private Collection 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 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 nested_mapped = new ArrayList(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 } } }