EA-100059 Fixed: IOOBE: PyParameterInfoHandler.highlightNext

EA-100300 Fixed: IOOBE: PyParameterInfoHandler.updateUI

Update PyParameterInfoHandler to correctly process named parameters that could be separated by single star parameter.

Example (not reproducible in tests):
```
def foo(arg1, *, arg2): pass
foo("abc", arg2="abc")
```
This commit is contained in:
Semyon Proshev
2017-05-22 19:58:20 +03:00
parent f23409e0d7
commit e9fa975a33
@@ -114,7 +114,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
return findArgumentList(context, context.getParameterListStart());
}
/**
/*
<b>Note: instead of parameter index, we directly store parameter's offset for later use.</b><br/>
We cannot store an index since we cannot determine what is an argument until we actually map arguments to parameters.
This is because a tuple in arguments may be a whole argument or map to a tuple parameter.
@@ -182,26 +182,32 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
if (markedCallee == null) return;
final List<PyParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), typeEvalContext);
final List<PyNamedParameter> namedParameters = new ArrayList<>(parameters.size());
final Map<Integer, PyNamedParameter> indexToNamedParameter = new HashMap<>();
// param -> hint index. indexes are not contiguous, because some hints are parentheses.
final Map<PyNamedParameter, Integer> parameterToIndex = new HashMap<>();
final Map<PyNamedParameter, Integer> parameterToHintIndex = new HashMap<>();
// formatting of hints: hint index -> flags. this includes flags for parens.
final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags = new HashMap<>();
final List<String> hintsList = buildParameterListHint(parameters, namedParameters, parameterToIndex, hintFlags, typeEvalContext);
final List<String> hintsList =
buildParameterListHint(parameters, indexToNamedParameter, parameterToHintIndex, hintFlags, typeEvalContext);
final int currentParamOffset = context.getCurrentParameterIndex(); // in Python mode, we get an offset here, not an index!
// gray out enough first parameters as implicit (self, cls, ...)
for (int i = 0; i < markedCallee.getImplicitOffset(); i++) {
hintFlags.get(parameterToIndex.get(namedParameters.get(i))).add(ParameterInfoUIContextEx.Flag.DISABLE); // show but mark as absent
if (indexToNamedParameter.containsKey(i)) {
final PyNamedParameter parameter = indexToNamedParameter.get(i);
hintFlags.get(parameterToHintIndex.get(parameter)).add(ParameterInfoUIContextEx.Flag.DISABLE); // show but mark as absent
}
}
final List<PyExpression> flattenedArguments = PyUtil.flattenedParensAndLists(callExpression.getArguments());
final int lastParamIndex = collectHighlights(mapping, parameters, parameterToIndex, hintFlags, flattenedArguments, currentParamOffset);
final int lastParamIndex =
collectHighlights(mapping, parameters, parameterToHintIndex, hintFlags, flattenedArguments, currentParamOffset);
highlightNext(markedCallee, parameters, namedParameters, parameterToIndex, hintFlags, flattenedArguments.isEmpty(), lastParamIndex);
highlightNext(markedCallee, parameters, indexToNamedParameter, parameterToHintIndex, hintFlags, flattenedArguments.isEmpty(),
lastParamIndex);
String[] hints = ArrayUtil.toStringArray(hintsList);
if (context instanceof ParameterInfoUIContextEx) {
@@ -232,14 +238,15 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
private static void highlightNext(@NotNull final PyMarkedCallee marked,
@NotNull final List<PyParameter> parameterList,
@NotNull final List<PyNamedParameter> namedParameters,
@NotNull final Map<PyNamedParameter, Integer> parameterToIndex,
@NotNull final Map<Integer, PyNamedParameter> indexToNamedParameter,
@NotNull final Map<PyNamedParameter, Integer> parameterToHintIndex,
@NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
boolean isArgsEmpty, int lastParamIndex) {
boolean canOfferNext = true; // can we highlight next unfilled parameter
for (EnumSet<ParameterInfoUIContextEx.Flag> set : hintFlags.values()) {
if (set.contains(ParameterInfoUIContextEx.Flag.HIGHLIGHT))
if (set.contains(ParameterInfoUIContextEx.Flag.HIGHLIGHT)) {
canOfferNext = false;
}
}
// highlight the next parameter to be filled
if (canOfferNext) {
@@ -248,7 +255,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
highlightIndex = marked.getImplicitOffset(); // no args, highlight first (PY-3690)
}
else if (lastParamIndex < parameterList.size() - 1) { // lastParamIndex not at end, or no args
if (namedParameters.get(lastParamIndex).isPositionalContainer()) {
if (indexToNamedParameter.get(lastParamIndex).isPositionalContainer()) {
highlightIndex = lastParamIndex; // stick to *arg
}
else {
@@ -256,12 +263,13 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
}
}
else if (lastParamIndex == parameterList.size() - 1) { // we're right after the end of param list
if (namedParameters.get(lastParamIndex).isPositionalContainer() || namedParameters.get(lastParamIndex).isKeywordContainer()) {
final PyNamedParameter parameter = indexToNamedParameter.get(lastParamIndex);
if (parameter.isPositionalContainer() || parameter.isKeywordContainer()) {
highlightIndex = lastParamIndex; // stick to *arg
}
}
if (highlightIndex < namedParameters.size()) {
hintFlags.get(parameterToIndex.get(namedParameters.get(highlightIndex))).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
if (indexToNamedParameter.containsKey(highlightIndex)) {
hintFlags.get(parameterToHintIndex.get(indexToNamedParameter.get(highlightIndex))).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
}
@@ -273,9 +281,10 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
*/
private static int collectHighlights(@NotNull final PyCallExpression.PyArgumentsMapping mapping,
@NotNull final List<PyParameter> parameterList,
@NotNull final Map<PyNamedParameter, Integer> parameterToIndex,
@NotNull final Map<PyNamedParameter, Integer> parameterHintToIndex,
@NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
@NotNull final List<PyExpression> flatArgs, int currentParamOffset) {
@NotNull final List<PyExpression> flatArgs,
int currentParamOffset) {
final PyMarkedCallee callee = mapping.getMarkedCallee();
assert callee != null;
int lastParamIndex = callee.getImplicitOffset();
@@ -292,19 +301,19 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
final PyNamedParameter parameter = mappedParameters.get((PyExpression)seeker);
lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter));
if (parameter != null) {
highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight);
highlightParameter(parameter, parameterHintToIndex, hintFlags, mustHighlight);
}
}
else if (PyCallExpressionHelper.isVariadicPositionalArgument(arg)) {
for (PyNamedParameter parameter : mapping.getParametersMappedToVariadicPositionalArguments()) {
lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter));
highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight);
highlightParameter(parameter, parameterHintToIndex, hintFlags, mustHighlight);
}
}
else if (PyCallExpressionHelper.isVariadicKeywordArgument(arg)) {
for (PyNamedParameter parameter : mapping.getParametersMappedToVariadicKeywordArguments()) {
lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter));
highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight);
highlightParameter(parameter, parameterHintToIndex, hintFlags, mustHighlight);
}
}
else {
@@ -312,7 +321,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
if (tupleParameter != null) {
for (PyNamedParameter parameter : getFlattenedTupleParameterComponents(tupleParameter)) {
lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter));
highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight);
highlightParameter(parameter, parameterHintToIndex, hintFlags, mustHighlight);
}
}
}
@@ -335,30 +344,31 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
}
private static void highlightParameter(@NotNull final PyNamedParameter parameter,
@NotNull final Map<PyNamedParameter, Integer> parameterToIndex,
@NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
boolean mustHighlight) {
final Integer parameterIndex = parameterToIndex.get(parameter);
if (mustHighlight && parameterIndex != null && parameterIndex < hintFlags.size()) {
hintFlags.get(parameterIndex).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
@NotNull final Map<PyNamedParameter, Integer> parameterToHintIndex,
@NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
boolean mustHighlight) {
final Integer hintIndex = parameterToHintIndex.get(parameter);
if (mustHighlight && hintIndex != null && hintFlags.containsKey(hintIndex)) {
hintFlags.get(hintIndex).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
/**
* builds the textual picture and the list of named parameters
*
* @param parameters parameters of a callable
* @param namedParameters used to collect all named parameters of callable
* @param parameterToIndex used to collect info about parameter indexes
* @param hintFlags mark parameter as deprecated/highlighted/strikeout
* @param context context to be used to get parameter representation
* @param parameters parameters of a callable
* @param indexToNamedParameter used to collect all named parameters of callable
* @param parameterToHintIndex used to collect info about parameter hints
* @param hintFlags mark parameter as deprecated/highlighted/strikeout
* @param context context to be used to get parameter representation
*/
private static List<String> buildParameterListHint(@NotNull List<PyParameter> parameters,
@NotNull final List<PyNamedParameter> namedParameters,
@NotNull final Map<PyNamedParameter, Integer> parameterToIndex,
@NotNull final Map<Integer, PyNamedParameter> indexToNamedParameter,
@NotNull final Map<PyNamedParameter, Integer> parameterToHintIndex,
@NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
@NotNull TypeEvalContext context) {
final List<String> hintsList = new ArrayList<>();
final int[] currentParameterIndex = new int[]{0};
ParamHelper.walkDownParamArray(
parameters.toArray(new PyParameter[parameters.size()]),
new ParamHelper.ParamWalker() {
@@ -373,19 +383,21 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
}
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
namedParameters.add(param);
indexToNamedParameter.put(currentParameterIndex[0], param);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(param.getRepr(true, context));
if (!last) stringBuilder.append(", ");
int hintIndex = hintsList.size();
parameterToIndex.put(param, hintIndex);
parameterToHintIndex.put(param, hintIndex);
hintFlags.put(hintIndex, EnumSet.noneOf(ParameterInfoUIContextEx.Flag.class));
hintsList.add(stringBuilder.toString());
currentParameterIndex[0]++;
}
public void visitSingleStarParameter(PySingleStarParameter param, boolean first, boolean last) {
hintFlags.put(hintsList.size(), EnumSet.noneOf(ParameterInfoUIContextEx.Flag.class));
hintsList.add(last ? "*" : "*, ");
currentParameterIndex[0]++;
}
}
);