diff --git a/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java b/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java index 1df8ec4b5d62..05025e5fb9c6 100644 --- a/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java @@ -3,9 +3,7 @@ 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.PyTupleType; -import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -75,266 +73,282 @@ public class CallArgumentsMappingImpl implements CallArgumentsMapping { } } } - // prepare parameter slots - final PyParameter[] parameters = myMarkedCallee.getCallable().getParameterList().getParameters(); - Map slots = new LinkedHashMap(); - PyNamedParameter kwd_par = null; // **param - PyNamedParameter tuple_par = null; // *param - Set mapped_args = new HashSet(); - final int implicit_offset = 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 < implicit_offset) { - 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 + PyType type = context.getType(myMarkedCallee.getCallable()); + if (type instanceof PyUnionType) { + type = ((PyUnionType)type).excludeNull(); + } + if (type instanceof PyCallableType) { + final PyCallableType callableType = (PyCallableType)type; + final List callableTypeParameters = callableType.getParameters(context); + if (callableTypeParameters != null) { + final List parameters = new ArrayList(); + for (PyCallableParameter callableParameter : callableTypeParameters) { + final PyParameter parameter = callableParameter.getParameter(); + if (parameter == null) { + return; } + parameters.add(parameter); } - } - 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 < implicit_offset && i < parameters.length; i+=1) { - slots.remove(parameters[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 = implicit_offset; - 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.length && cnt < positional_index) { - final PyParameter par = parameters[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 - } + // prepare parameter slots + Map slots = new LinkedHashMap(); + PyNamedParameter kwd_par = null; // **param + PyNamedParameter tuple_par = null; // *param + Set mapped_args = new HashSet(); + final int implicit_offset = 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 < implicit_offset) { + 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 { - PyType arg_type = context.getType(arg); - if (arg_type != null && arg_type.isBuiltin(context) && "list".equals(arg_type.getName())) { - mapped_args.add(arg); // we can't really analyze arbitrary lists statically yet - // but ListLiteralExpressions are handled by visitor + 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 } } - 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); - } - // mark past-bound positional args - i = positional_bound; - while (i 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); + else { + PyTupleParameter t_par = par.getAsTuple(); + if (t_par != null) positional_index += 1; // tuple can only be positional + // else lone star, skip } - } - 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.length && 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.length && cnt < positional_index && mapped_params_count < tuple_length) { - PyParameter par = parameters[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 (PyParameter par : parameters) { - PyNamedParameter n_par = par.getAsNamed(); - if (n_par != null && !n_par.isKeywordContainer() && !n_par.isPositionalContainer() && slots.get(n_par) == null) { - slots.put(n_par, myKwdArg); + // rule out 'self' or other implicit params + for (i=0; i < implicit_offset && i < parameters.size(); i+=1) { + slots.remove(parameters.get(i).getAsNamed()); + positional_index += 1; } - } - } - // 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); + // 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 = implicit_offset; + 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(context) && "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; } - else if (arg == myKwdArg) { - myKwdMappedParams.add(n_par); + // 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); } - 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()) { + for (PyExpression arg : unmatched_subargs) { markArgument(arg, ArgFlag.IS_UNMAPPED); } + // mark past-bound positional args + i = positional_bound; + while (i 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 (PyParameter par : parameters) { + PyNamedParameter n_par = par.getAsNamed(); + if (n_par != null && !n_par.isKeywordContainer() && !n_par.isPositionalContainer() && slots.get(n_par) == null) { + slots.put(n_par, 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); + } + } + } } - } }