PyParameterInfoHandler uses PyCallExpression.mapArguments() instead of CallArgumentsMapping

Switched everything in PyParameterInfoHandler but Python 2 tuple
parameters handling: 4 tests are still failing.
This commit is contained in:
Andrey Vlasovskikh
2015-08-21 20:21:26 +03:00
parent ec340c602b
commit f153504bce
4 changed files with 112 additions and 51 deletions
@@ -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<PyExpression, PyNamedParameter> myMappedParameters;
@NotNull private final List<PyParameter> myUnmappedParameters;
@NotNull private final List<PyExpression> myUnmappedArguments;
@NotNull private final List<PyNamedParameter> myParametersMappedToVariadicPositionalArguments;
@NotNull private final List<PyNamedParameter> myParametersMappedToVariadicKeywordArguments;
public PyArgumentsMapping(@Nullable PyMarkedCallee markedCallee, @NotNull Map<PyExpression, PyNamedParameter> mappedParameters,
@NotNull List<PyParameter> unmappedParameters, @NotNull List<PyExpression> unmappedArguments) {
public PyArgumentsMapping(@NotNull PyCallExpression expression,
@Nullable PyMarkedCallee markedCallee,
@NotNull Map<PyExpression, PyNamedParameter> mappedParameters,
@NotNull List<PyParameter> unmappedParameters,
@NotNull List<PyExpression> unmappedArguments,
@NotNull List<PyNamedParameter> parametersMappedToVariadicPositionalArguments,
@NotNull List<PyNamedParameter> 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<PyExpression> getUnmappedArguments() {
return myUnmappedArguments;
}
@NotNull
public List<PyNamedParameter> getParametersMappedToVariadicPositionalArguments() {
return myParametersMappedToVariadicPositionalArguments;
}
@NotNull
public List<PyNamedParameter> getParametersMappedToVariadicKeywordArguments() {
return myParametersMappedToVariadicKeywordArguments;
}
}
/**
@@ -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<PyArgumentList, CallArgumentsMapping> {
public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentList, PyCallExpression.PyArgumentsMapping> {
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<PyArgumentLi
return ParameterInfoUtils.findParentOfType(context.getFile(), context.getOffset(), PyArgumentList.class);
}
@Override
public void showParameterInfo(@NotNull final PyArgumentList element, @NotNull final CreateParameterInfoContext context) {
context.showHint(element, element.getTextOffset(), this);
}
@Override
public PyArgumentList findElementForUpdatingParameterInfo(@NotNull final UpdateParameterInfoContext context) {
return findArgumentList(context);
}
@@ -125,15 +136,15 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
}
@Override
public void updateUI(final CallArgumentsMapping prevResult, @NotNull final ParameterInfoUIContext context) {
if (prevResult == null) return;
final PyArgumentList argList = prevResult.getArgumentList();
if (!argList.isValid()) return;
public void updateUI(final PyCallExpression.PyArgumentsMapping oldMapping, @NotNull final ParameterInfoUIContext context) {
if (oldMapping == null) return;
final PyCallExpression callExpression = oldMapping.getCallExpression();
if (!callExpression.isValid()) return;
// really we need to redo analysis every UI update; findElementForParameterInfo isn't called while typing
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(argList.getProject(), argList.getContainingFile());
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(callExpression.getProject(), callExpression.getContainingFile());
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext);
final CallArgumentsMapping argumentsMapping = argList.analyzeCall(resolveContext);
final PyMarkedCallee marked = argumentsMapping.getMarkedCallee();
final PyCallExpression.PyArgumentsMapping mapping = callExpression.mapArguments(resolveContext);
final PyMarkedCallee marked = mapping.getMarkedCallee();
if (marked == null) return; // resolution failed
final PyCallable callable = marked.getCallable();
@@ -154,8 +165,8 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
hintFlags.get(parameterToIndex.get(namedParameters.get(i))).add(ParameterInfoUIContextEx.Flag.DISABLE); // show but mark as absent
}
final List<PyExpression> flattenedArgs = PyUtil.flattenedParensAndLists(argList.getArguments());
int lastParamIndex = collectHighlights(argumentsMapping, parameterList, parameterToIndex, hintFlags, flattenedArgs, currentParamOffset);
final List<PyExpression> 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<PyArgumentLi
*
* @return index of last parameter
*/
private static int collectHighlights(@NotNull final CallArgumentsMapping argumentsMapping,
private static int collectHighlights(@NotNull final PyCallExpression.PyArgumentsMapping mapping,
@NotNull final List<PyParameter> parameterList,
@NotNull final Map<PyNamedParameter, Integer> parameterToIndex,
@NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
@NotNull final List<PyExpression> flatArgs, int currentParamOffset) {
final PyMarkedCallee callee = argumentsMapping.getMarkedCallee();
final PyMarkedCallee callee = mapping.getMarkedCallee();
assert callee != null;
int lastParamIndex = callee.getImplicitOffset();
final Map<PyExpression, PyNamedParameter> 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<PyNamedParameter> 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<PyNamedParameter> 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
}
@@ -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.<PyExpression, PyNamedParameter>emptyMap(),
Collections.<PyParameter>emptyList(), Collections.<PyExpression>emptyList());
return new PyCallExpression.PyArgumentsMapping(callExpression, null, Collections.<PyExpression, PyNamedParameter>emptyMap(),
Collections.<PyParameter>emptyList(), Collections.<PyExpression>emptyList(),
Collections.<PyNamedParameter>emptyList(),Collections.<PyNamedParameter>emptyList());
}
boolean seenSingleStar = false;
@@ -649,6 +650,8 @@ public class PyCallExpressionHelper {
final TypeEvalContext context = resolveContext.getTypeEvalContext();
final Map<PyExpression, PyNamedParameter> mappedParameters = new LinkedHashMap<PyExpression, PyNamedParameter>();
final List<PyParameter> unmappedParameters = new ArrayList<PyParameter>();
final List<PyNamedParameter> parametersMappedToVariadicKeywordArguments = new ArrayList<PyNamedParameter>();
final List<PyNamedParameter> parametersMappedToVariadicPositionalArguments = new ArrayList<PyNamedParameter>();
final List<PyParameter> allParameters = PyUtil.getParameters(markedCallee.getCallable(), context);
final List<PyParameter> parameters = dropImplicitParameters(allParameters, markedCallee.getImplicitOffset());
@@ -658,7 +661,7 @@ public class PyCallExpressionHelper {
final List<PyKeywordArgument> keywordArguments = filterKeywordArguments(arguments);
final Pair<List<PyExpression>, List<PyExpression>> variadicPositionalArgumentsAndTheirComponents = filterVariadicPositionalArguments(arguments);
final List<PyExpression> variadicPositionalArguments = variadicPositionalArgumentsAndTheirComponents.getFirst();
final List<PyExpression> positionalComponentsOfVariadicArguments = variadicPositionalArgumentsAndTheirComponents.getSecond();
final Set<PyExpression> positionalComponentsOfVariadicArguments = new LinkedHashSet<PyExpression>(variadicPositionalArgumentsAndTheirComponents.getSecond());
final List<PyExpression> variadicKeywordArguments = filterVariadicKeywordArguments(arguments);
final List<PyExpression> allPositionalArguments = new ArrayList<PyExpression>();
@@ -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();
}
@@ -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;
}