diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java index abed174f0abd..bd3fb2e8ffd3 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java @@ -140,17 +140,33 @@ public interface PyCallExpression extends PyCallSiteExpression { boolean isCallee(@NotNull FQNamesProvider... name); class PyArgumentsMapping { + @NotNull private final PyCallExpression myCallExpression; @Nullable private final PyMarkedCallee myCallee; @NotNull private final Map myMappedParameters; @NotNull private final List myUnmappedParameters; @NotNull private final List myUnmappedArguments; + @NotNull private final List myParametersMappedToVariadicPositionalArguments; + @NotNull private final List myParametersMappedToVariadicKeywordArguments; - public PyArgumentsMapping(@Nullable PyMarkedCallee markedCallee, @NotNull Map mappedParameters, - @NotNull List unmappedParameters, @NotNull List unmappedArguments) { + public PyArgumentsMapping(@NotNull PyCallExpression expression, + @Nullable PyMarkedCallee markedCallee, + @NotNull Map mappedParameters, + @NotNull List unmappedParameters, + @NotNull List unmappedArguments, + @NotNull List parametersMappedToVariadicPositionalArguments, + @NotNull List parametersMappedToVariadicKeywordArguments) { + myCallExpression = expression; myCallee = markedCallee; myMappedParameters = mappedParameters; myUnmappedParameters = unmappedParameters; myUnmappedArguments = unmappedArguments; + myParametersMappedToVariadicPositionalArguments = parametersMappedToVariadicPositionalArguments; + myParametersMappedToVariadicKeywordArguments = parametersMappedToVariadicKeywordArguments; + } + + @NotNull + public PyCallExpression getCallExpression() { + return myCallExpression; } @Nullable @@ -172,6 +188,16 @@ public interface PyCallExpression extends PyCallSiteExpression { public List getUnmappedArguments() { return myUnmappedArguments; } + + @NotNull + public List getParametersMappedToVariadicPositionalArguments() { + return myParametersMappedToVariadicPositionalArguments; + } + + @NotNull + public List getParametersMappedToVariadicKeywordArguments() { + return myParametersMappedToVariadicKeywordArguments; + } } /** diff --git a/python/src/com/jetbrains/python/PyParameterInfoHandler.java b/python/src/com/jetbrains/python/PyParameterInfoHandler.java index 28ab2002f027..dde0bd473021 100644 --- a/python/src/com/jetbrains/python/PyParameterInfoHandler.java +++ b/python/src/com/jetbrains/python/PyParameterInfoHandler.java @@ -26,6 +26,7 @@ import com.intellij.util.text.CharArrayUtil; import com.intellij.xml.util.XmlStringUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.ParamHelper; +import com.jetbrains.python.psi.impl.PyCallExpressionHelper; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; @@ -37,29 +38,37 @@ import static com.jetbrains.python.psi.PyCallExpression.PyMarkedCallee; /** * @author dcheryasov */ -public class PyParameterInfoHandler implements ParameterInfoHandler { +public class PyParameterInfoHandler implements ParameterInfoHandler { private static final String NO_PARAMS_MSG = CodeInsightBundle.message("parameter.info.no.parameters"); + @Override public boolean couldShowInLookup() { return true; } + + @Override public Object[] getParametersForLookup(final LookupElement item, final ParameterInfoContext context) { - return ArrayUtil.EMPTY_OBJECT_ARRAY; // we don't + return ArrayUtil.EMPTY_OBJECT_ARRAY; } - public Object[] getParametersForDocumentation(final CallArgumentsMapping p, final ParameterInfoContext context) { - return ArrayUtil.EMPTY_OBJECT_ARRAY; // we don't + @Override + public Object[] getParametersForDocumentation(final PyCallExpression.PyArgumentsMapping p, final ParameterInfoContext context) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; } + @Override public PyArgumentList findElementForParameterInfo(@NotNull final CreateParameterInfoContext context) { - PyArgumentList arglist = findArgumentList(context); - if (arglist != null) { - final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(arglist.getProject(), arglist.getContainingFile()); - final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext); - CallArgumentsMapping result = arglist.analyzeCall(resolveContext); - if (result.getMarkedCallee() != null) { - context.setItemsToShow(new Object[] { result }); - return arglist; + PyArgumentList argumentList = findArgumentList(context); + if (argumentList != null) { + final PyCallExpression callExpr = argumentList.getCallExpression(); + if (callExpr != null) { + final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(argumentList.getProject(), argumentList.getContainingFile()); + final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext); + final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments(resolveContext); + if (mapping.getMarkedCallee() != null) { + context.setItemsToShow(new Object[] { mapping }); + return argumentList; + } } } return null; @@ -69,10 +78,12 @@ public class PyParameterInfoHandler implements ParameterInfoHandler flattenedArgs = PyUtil.flattenedParensAndLists(argList.getArguments()); - int lastParamIndex = collectHighlights(argumentsMapping, parameterList, parameterToIndex, hintFlags, flattenedArgs, currentParamOffset); + final List flattenedArgs = PyUtil.flattenedParensAndLists(callExpression.getArguments()); + int lastParamIndex = collectHighlights(mapping, parameterList, parameterToIndex, hintFlags, flattenedArgs, currentParamOffset); highlightNext(marked, parameterList, namedParameters, parameterToIndex, hintFlags, flattenedArgs.isEmpty(), lastParamIndex); @@ -227,50 +238,49 @@ public class PyParameterInfoHandler implements ParameterInfoHandler parameterList, @NotNull final Map parameterToIndex, @NotNull final Map> hintFlags, @NotNull final List flatArgs, int currentParamOffset) { - final PyMarkedCallee callee = argumentsMapping.getMarkedCallee(); + final PyMarkedCallee callee = mapping.getMarkedCallee(); assert callee != null; int lastParamIndex = callee.getImplicitOffset(); + final Map mappedParameters = mapping.getMappedParameters(); for (PyExpression arg : flatArgs) { final boolean mustHighlight = arg.getTextRange().contains(currentParamOffset); PsiElement seeker = arg; - while (!(seeker instanceof PyArgumentList) && seeker instanceof PyExpression && !argumentsMapping.getPlainMappedParams().containsKey(seeker)) { + while (!(seeker instanceof PyArgumentList) && seeker instanceof PyExpression && !mappedParameters.containsKey(seeker)) { seeker = seeker.getParent(); // flattener may have flattened a tuple arg that is mapped to a plain param; find it. } if (seeker instanceof PyExpression) { - final PyNamedParameter parameter = argumentsMapping.getPlainMappedParams().get((PyExpression)seeker); + final PyNamedParameter parameter = mappedParameters.get((PyExpression)seeker); lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter)); if (parameter != null) { highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight); } } - else if (arg == argumentsMapping.getTupleArg()) { - // mark all params that map to *arg - for (PyNamedParameter parameter : argumentsMapping.getTupleMappedParams()) { + else if (PyCallExpressionHelper.isVariadicPositionalArgument(arg)) { + for (PyNamedParameter parameter : mapping.getParametersMappedToVariadicPositionalArguments()) { lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter)); highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight); } } - else if (arg == argumentsMapping.getKwdArg()) { - // mark all n_params that map to **arg - for (PyNamedParameter parameter : argumentsMapping.getKwdMappedParams()) { + else if (PyCallExpressionHelper.isVariadicKeywordArgument(arg)) { + for (PyNamedParameter parameter : mapping.getParametersMappedToVariadicKeywordArguments()) { lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter)); highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight); } } else { - // maybe it's mapped to a nested tuple? - final List namedParameters = argumentsMapping.getNestedMappedParams().get(arg); - if (namedParameters != null) { - for (PyNamedParameter parameter : namedParameters) { - lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter)); - highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight); - } - } + // TODO: Handle arguments that are mapped to tuple parameters + //final List namedParameters = mapping.getNestedMappedParams().get(arg); + //if (namedParameters != null) { + // for (PyNamedParameter parameter : namedParameters) { + // lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter)); + // highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight); + // } + //} } // else: stay unhighlighted } diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index fd7b45db558c..fc7599288040 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -640,8 +640,9 @@ public class PyCallExpressionHelper { final PyCallExpression.PyMarkedCallee markedCallee = callExpression.resolveCallee(resolveContext, implicitOffset); if (markedCallee == null || argumentList == null) { - return new PyCallExpression.PyArgumentsMapping(null, Collections.emptyMap(), - Collections.emptyList(), Collections.emptyList()); + return new PyCallExpression.PyArgumentsMapping(callExpression, null, Collections.emptyMap(), + Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(),Collections.emptyList()); } boolean seenSingleStar = false; @@ -649,6 +650,8 @@ public class PyCallExpressionHelper { final TypeEvalContext context = resolveContext.getTypeEvalContext(); final Map mappedParameters = new LinkedHashMap(); final List unmappedParameters = new ArrayList(); + final List parametersMappedToVariadicKeywordArguments = new ArrayList(); + final List parametersMappedToVariadicPositionalArguments = new ArrayList(); final List allParameters = PyUtil.getParameters(markedCallee.getCallable(), context); final List parameters = dropImplicitParameters(allParameters, markedCallee.getImplicitOffset()); @@ -658,7 +661,7 @@ public class PyCallExpressionHelper { final List keywordArguments = filterKeywordArguments(arguments); final Pair, List> variadicPositionalArgumentsAndTheirComponents = filterVariadicPositionalArguments(arguments); final List variadicPositionalArguments = variadicPositionalArgumentsAndTheirComponents.getFirst(); - final List positionalComponentsOfVariadicArguments = variadicPositionalArgumentsAndTheirComponents.getSecond(); + final Set positionalComponentsOfVariadicArguments = new LinkedHashSet(variadicPositionalArgumentsAndTheirComponents.getSecond()); final List variadicKeywordArguments = filterVariadicKeywordArguments(arguments); final List allPositionalArguments = new ArrayList(); @@ -670,6 +673,9 @@ public class PyCallExpressionHelper { final PyNamedParameter namedParameter = (PyNamedParameter)parameter; final String parameterName = namedParameter.getName(); if (namedParameter.isPositionalContainer()) { + for (PyExpression argument : allPositionalArguments) { + mappedParameters.put(argument, namedParameter); + } if (variadicPositionalArguments.size() == 1) { mappedParameters.put(variadicPositionalArguments.get(0), namedParameter); } @@ -677,6 +683,9 @@ public class PyCallExpressionHelper { variadicPositionalArguments.clear(); } else if (namedParameter.isKeywordContainer()) { + for (PyKeywordArgument argument : keywordArguments) { + mappedParameters.put(argument, namedParameter); + } if (variadicKeywordArguments.size() == 1) { mappedParameters.put(variadicKeywordArguments.get(0), namedParameter); } @@ -688,8 +697,13 @@ public class PyCallExpressionHelper { if (keywordArgument != null) { mappedParameters.put(keywordArgument, namedParameter); } - else if (variadicKeywordArguments.isEmpty() && !namedParameter.hasDefaultValue()) { - unmappedParameters.add(namedParameter); + else if (variadicKeywordArguments.isEmpty()) { + if (!namedParameter.hasDefaultValue()) { + unmappedParameters.add(namedParameter); + } + } + else { + parametersMappedToVariadicKeywordArguments.add(namedParameter); } } else { @@ -702,6 +716,12 @@ public class PyCallExpressionHelper { unmappedParameters.add(namedParameter); } else { + if (!variadicPositionalArguments.isEmpty()) { + parametersMappedToVariadicPositionalArguments.add(namedParameter); + } + if (!variadicKeywordArguments.isEmpty()) { + parametersMappedToVariadicKeywordArguments.add(namedParameter); + } mappedVariadicArgumentsToParameters = true; } } @@ -709,6 +729,9 @@ public class PyCallExpressionHelper { final PyExpression positionalArgument = next(allPositionalArguments); if (positionalArgument != null) { mappedParameters.put(positionalArgument, namedParameter); + if (positionalComponentsOfVariadicArguments.contains(positionalArgument)) { + parametersMappedToVariadicPositionalArguments.add(namedParameter); + } } else if (!namedParameter.hasDefaultValue()) { unmappedParameters.add(namedParameter); @@ -750,7 +773,9 @@ public class PyCallExpressionHelper { unmappedArguments.addAll(variadicPositionalArguments); unmappedArguments.addAll(variadicKeywordArguments); - return new PyCallExpression.PyArgumentsMapping(markedCallee, mappedParameters, unmappedParameters, unmappedArguments); + return new PyCallExpression.PyArgumentsMapping(callExpression, markedCallee, mappedParameters, unmappedParameters, unmappedArguments, + parametersMappedToVariadicPositionalArguments, + parametersMappedToVariadicKeywordArguments); } @Nullable @@ -831,11 +856,11 @@ public class PyCallExpressionHelper { return results; } - private static boolean isVariadicKeywordArgument(@NotNull PyExpression argument) { + public static boolean isVariadicKeywordArgument(@NotNull PyExpression argument) { return argument instanceof PyStarArgument && ((PyStarArgument)argument).isKeyword(); } - private static boolean isVariadicPositionalArgument(@NotNull PyExpression argument) { + public static boolean isVariadicPositionalArgument(@NotNull PyExpression argument) { return argument instanceof PyStarArgument && !((PyStarArgument)argument).isKeyword(); } diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index 2d311e59d2e3..08c4181f7ece 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -29,8 +29,8 @@ import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.jetbrains.python.fixtures.LightMarkedTestCase; -import com.jetbrains.python.psi.CallArgumentsMapping; import com.jetbrains.python.psi.PyArgumentList; +import com.jetbrains.python.psi.PyCallExpression; import junit.framework.Assert; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -422,7 +422,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { if (collector.getParameterOwner() != null) { Assert.assertEquals("Collected one analysis result", 1, collector.myItems.length); handler.updateParameterInfo((PyArgumentList)collector.getParameterOwner(), collector); // moves offset to correct parameter - handler.updateUI((CallArgumentsMapping)collector.getItemsToShow()[0], collector); // sets hint text and flags + handler.updateUI((PyCallExpression.PyArgumentsMapping)collector.getItemsToShow()[0], collector); // sets hint text and flags } return collector; }